对接文档新增回调通知章节(事件/签名/推送重试)

- 回调文档三个区块:回调通知(事件列表、推送示例、接收方要求)、签名校验(算法与伪代码)、推送与重试(微信式 16 次退避序列)
- 概述一句话上手补充回调配置步骤
This commit is contained in:
yml2213
2026-08-03 11:33:36 +08:00
parent 27a86f5311
commit 3e8526d5d7
+241 -1
View File
@@ -68,6 +68,14 @@ function buildToc(): TocGroup[] {
})),
})),
},
{
title: '回调',
items: [
{ id: 'callback', label: '回调通知' },
{ id: 'callback-sign', label: '签名校验' },
{ id: 'callback-retry', label: '推送与重试' },
],
},
{
title: '附录',
items: [{ id: 'status', label: '状态说明' }],
@@ -215,6 +223,9 @@ export default function OpenApiDocs() {
<div className="api-docs__inner">
{activePageId === 'overview' ? <OverviewSection /> : null}
{activePageId === 'auth' ? <AuthSection /> : null}
{activePageId === 'callback' ? <CallbackSection /> : null}
{activePageId === 'callback-sign' ? <CallbackSignSection /> : null}
{activePageId === 'callback-retry' ? <CallbackRetrySection /> : null}
{activeEndpoint ? <EndpointSection spec={activeEndpoint} /> : null}
{activePageId === 'status' ? <StatusSection /> : null}
<PageStepper
@@ -312,6 +323,7 @@ function OverviewSection() {
<li> <Text code>X-App-Key / X-Timestamp / X-Nonce / X-Sign</Text> </li>
<li> <Text code>sku</Text></li>
<li> <Text code>order_no</Text> </li>
<li></li>
</ol>
}
/>
@@ -449,8 +461,236 @@ function EndpointSection({ spec }: { spec: EndpointSpec }) {
)
}
function StatusSection() {
// ---------- 回调通知文档 ----------
const callbackEvents = [
{
event: 'order.created',
desc: '订单创建成功(已扣款,order_status=paid',
trigger: '创建订单',
},
{
event: 'order.shipping.updated',
desc: '发货状态变化:提交发货(delivering)、发货成功(delivered)、发货失败(ship_failed',
trigger: '提交发货 / 上游回传发货结果',
},
{
event: 'order.cancelled',
desc: '订单取消并退款(order_status=cancelled',
trigger: '取消订单',
},
]
const callbackPayloadExample = `{
"event_id": "4751626d-d608-42c1-a453-60ffcafc01fc",
"event": "order.shipping.updated",
"occurred_at": "2026-08-03T10:11:20+08:00",
"data": {
"order_no": "FO20260803000123",
"client_order_no": "shop-10001",
"order_status": "delivered",
"can_ship": false,
"product": { "sku": "suit_pink_sheep", "name": "套装-糯粉咩咩" },
"quantity": 1,
"amount": 105,
"currency": "POINT",
"provider_order_no": "PROV-xxxx",
"failure_reason": "",
"created_at": "2026-08-03T10:00:00+08:00",
"delivered_at": "2026-08-03T10:11:20+08:00"
}
}`
const retrySchedule = [
{ attempt: 2, after: '15 秒' },
{ attempt: 3, after: '15 秒' },
{ attempt: 4, after: '30 秒' },
{ attempt: 5, after: '3 分钟' },
{ attempt: 6, after: '10 分钟' },
{ attempt: 7, after: '20 分钟' },
{ attempt: 8, after: '30 分钟' },
{ attempt: 9, after: '30 分钟' },
{ attempt: 10, after: '30 分钟' },
{ attempt: 11, after: '1 小时' },
{ attempt: 12, after: '3 小时' },
{ attempt: 13, after: '3 小时' },
{ attempt: 14, after: '3 小时' },
{ attempt: 15, after: '6 小时' },
{ attempt: 16, after: '6 小时' },
]
function CallbackSection() {
return (
<SectionWrap
id="callback"
title="回调通知"
desc={
<>
<Text code>POST</Text>
URL outbox
</>
}
>
<Alert
className="api-docs__alert"
type="info"
showIcon
message="快速接入"
description={
<ol style={{ margin: 0, paddingLeft: 20 }}>
<li> URL <Text code>CallbackSecret</Text></li>
<li> <Text code>X-Event-ID / X-Timestamp / X-Sign</Text></li>
<li> HTTP 2xx 2xx </li>
</ol>
}
/>
<Card className="api-docs__card" size="small" title="回调事件">
<Table
className="api-docs__table"
size="small"
pagination={false}
rowKey="event"
dataSource={callbackEvents}
columns={[
{ title: '事件', dataIndex: 'event', width: 220, render: (v) => <Text code>{v}</Text> },
{ title: '触发时机', dataIndex: 'trigger', width: 220 },
{ title: '说明', dataIndex: 'desc' },
]}
/>
</Card>
<Card className="api-docs__card" size="small" title="推送请求示例">
<Descriptions size="small" column={1} bordered style={{ marginBottom: 12 }}>
<Descriptions.Item label="URL">
<Text code>POST https://你的服务器/callback</Text>
</Descriptions.Item>
<Descriptions.Item label="Content-Type">
<Text code>application/json</Text>
</Descriptions.Item>
</Descriptions>
<pre className="api-docs__pre">{`POST /callback HTTP/1.1
Content-Type: application/json
X-Event-ID: 4751626d-d608-42c1-a453-60ffcafc01fc
X-Timestamp: 1785723081
X-Sign: 191892922db64bf41159f58e...`}</pre>
<pre className="api-docs__pre">{callbackPayloadExample}</pre>
</Card>
<Card className="api-docs__card" size="small" title="接收方要求">
<ul style={{ margin: 0, paddingLeft: 20 }}>
<li> <Text code>2xx</Text></li>
<li> <Text code>X-Event-ID</Text> </li>
<li> <Text code>occurred_at</Text> <Text code>X-Timestamp</Text> </li>
</ul>
</Card>
</SectionWrap>
)
}
function CallbackSignSection() {
return (
<SectionWrap id="callback-sign" title="签名校验">
<Card className="api-docs__card" size="small" title="请求头">
<Table
className="api-docs__table"
size="small"
pagination={false}
rowKey="header"
dataSource={[
{ header: 'X-Event-ID', desc: '事件唯一 ID,接收方用于幂等' },
{ header: 'X-Timestamp', desc: '推送时的 Unix 秒时间戳' },
{ header: 'X-Sign', desc: 'HMAC-SHA256 签名,见下方算法' },
]}
columns={[
{ title: 'Header', dataIndex: 'header', width: 160, render: (v) => <Text code>{v}</Text> },
{ title: '说明', dataIndex: 'desc' },
]}
/>
</Card>
<Card className="api-docs__card" size="small" title="签名算法">
<Descriptions size="small" column={1} bordered>
<Descriptions.Item label="密钥">
<Text code>CallbackSecret</Text>
</Descriptions.Item>
<Descriptions.Item label="body_hash">
<Text code>sha256hex(body)</Text> SHA256
</Descriptions.Item>
<Descriptions.Item label="签名串">
<Text code>timestamp + "\n" + sha256hex(body)</Text>
</Descriptions.Item>
<Descriptions.Item label="X-Sign">
<Text code>hex( HMAC-SHA256( secret, ) )</Text>
</Descriptions.Item>
</Descriptions>
</Card>
<Card className="api-docs__card" size="small" title="校验伪代码">
<pre className="api-docs__pre">{`bodyHash = sha256Hex(rawBody)
content = timestamp + "\\n" + bodyHash
expected = hmacSHA256Hex(secret, content)
ok = (X-Sign == expected)`}</pre>
</Card>
<Alert
className="api-docs__alert"
type="warning"
showIcon
message="注意"
description={
<ul style={{ margin: 0, paddingLeft: 20 }}>
<li> <Text code> body </Text></li>
<li> <Text code>X-Timestamp</Text> ±5 </li>
<li></li>
</ul>
}
/>
</SectionWrap>
)
}
function CallbackRetrySection() {
return (
<SectionWrap
id="callback-retry"
title="推送与重试"
desc={
<>
outbox 3 退
/
<Text code>CALLBACK_MAX_ATTEMPTS</Text> 16
<Text code>CALLBACK_RETRY_SCHEDULE</Text>
</>
}
>
<Card className="api-docs__card" size="small" title="重试策略(默认)">
<Table
className="api-docs__table"
size="small"
pagination={false}
rowKey="attempt"
dataSource={retrySchedule}
columns={[
{ title: '第 N 次推送', dataIndex: 'attempt', width: 140, render: (v) => <Text code>{v}</Text> },
{ title: '距上次失败间隔', dataIndex: 'after' },
]}
/>
</Card>
<Alert
className="api-docs__alert"
type="info"
showIcon
message="规则"
description={
<ul style={{ margin: 0, paddingLeft: 20 }}>
<li> <Text code>16</Text> + 15 <Text code>failed</Text></li>
<li> <Text code>15 </Text> <Text code>CALLBACK_PUSH_TIMEOUT_SECONDS</Text></li>
<li> <Text code>2xx</Text> </li>
<li></li>
</ul>
}
/>
</SectionWrap>
)
}
function StatusSection() { return (
<SectionWrap id="status" title="状态说明">
<Card className="api-docs__card" size="small" title="订单状态 order_status 与 can_ship">
<Table