diff --git a/frontend/src/pages/OpenApiDocs.tsx b/frontend/src/pages/OpenApiDocs.tsx
index 94e2e43..e3bd85c 100644
--- a/frontend/src/pages/OpenApiDocs.tsx
+++ b/frontend/src/pages/OpenApiDocs.tsx
@@ -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() {
{activePageId === 'overview' ?
: null}
{activePageId === 'auth' ?
: null}
+ {activePageId === 'callback' ?
: null}
+ {activePageId === 'callback-sign' ?
: null}
+ {activePageId === 'callback-retry' ?
: null}
{activeEndpoint ?
: null}
{activePageId === 'status' ?
: null}
每次请求携带 X-App-Key / X-Timestamp / X-Nonce / X-Sign 四个鉴权头。
调用「商品列表」拿到可售 sku,调用「下单」创建订单并扣款。
拿到 order_no 后,可选择返回发货链接,或者用结构化发货接口完成自建页面流程。
+ 在「回调」页配置回调地址并订阅事件,订单状态变化会实时推送,见「回调通知」。
}
/>
@@ -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 (
+
+ 平台通过 POST 方式把订单事件推送到商户配置的回调地址。
+ 商户在后台「回调」页配置一个 URL 并订阅事件,配置保存在 outbox 表中,
+ 订单事务提交后才写入待推送记录,进程重启不会丢失。
+ >
+ }
+ >
+
+ 在商户后台「回调」页填写回调 URL 并订阅事件,保存后获得 CallbackSecret(仅展示一次,可在后台重置)。
+ 接收平台推送:请求头带 X-Event-ID / X-Timestamp / X-Sign,按「签名校验」验证后处理。
+ 返回 HTTP 2xx 即视为投递成功;非 2xx 或超时按「推送与重试」策略重试。
+
+ }
+ />
+
+ {v} },
+ { title: '触发时机', dataIndex: 'trigger', width: 220 },
+ { title: '说明', dataIndex: 'desc' },
+ ]}
+ />
+
+
+
+
+ POST https://你的服务器/callback
+
+
+ application/json
+
+
+ {`POST /callback HTTP/1.1
+Content-Type: application/json
+X-Event-ID: 4751626d-d608-42c1-a453-60ffcafc01fc
+X-Timestamp: 1785723081
+X-Sign: 191892922db64bf41159f58e...`}
+ {callbackPayloadExample}
+
+
+
+ - 返回 2xx(任意状态码)即视为投递成功;返回其他状态码或超时视为失败并进入重试。
+ - 同一事件的 X-Event-ID 全局唯一,请按它做幂等处理,避免重复入账。
+ - 事件里的 occurred_at 是事件发生时间,与请求头 X-Timestamp 不同。
+
+
+
+ )
+}
+
+function CallbackSignSection() {
+ return (
+
+
+ {v} },
+ { title: '说明', dataIndex: 'desc' },
+ ]}
+ />
+
+
+
+
+ CallbackSecret(保存回调配置时展示一次,可在后台「重置密钥」)
+
+
+ sha256hex(body) —— 原始请求体字节的 SHA256 十六进制(小写)
+
+
+ timestamp + "\n" + sha256hex(body),换行符拼接
+
+
+ hex( HMAC-SHA256( secret, 签名串 ) ),小写十六进制
+
+
+
+
+ {`bodyHash = sha256Hex(rawBody)
+content = timestamp + "\\n" + bodyHash
+expected = hmacSHA256Hex(secret, content)
+ok = (X-Sign == expected)`}
+
+
+ 校验用 原始 body 字节,不要重新序列化后再算摘要。
+ 建议校验 X-Timestamp 与当前时间偏差(如 ±5 分钟)后验签。
+ 若在后台重置密钥,旧密钥立即失效,新密钥签名推送。
+
+ }
+ />
+
+ )
+}
+
+function CallbackRetrySection() {
+ return (
+
+ 回调基于数据库 outbox 持久化推送(每 3 秒轮询一批),失败后按固定间隔退避重试,
+ 参考微信/支付宝通知机制。重试次数与间隔可通过环境变量调整:
+ CALLBACK_MAX_ATTEMPTS(默认 16)、
+ CALLBACK_RETRY_SCHEDULE(逗号分隔秒数)。
+ >
+ }
+ >
+
+ {v} },
+ { title: '距上次失败间隔', dataIndex: 'after' },
+ ]}
+ />
+
+
+ 共尝试 16 次(首次 + 15 次重试),全部失败后标记 failed,不再推送。
+ 单次推送超时 15 秒(可配 CALLBACK_PUSH_TIMEOUT_SECONDS)按失败处理。
+ 接收方返回任意 2xx 即停止重试。
+ 未配置回调地址或订阅未匹配事件时不会产生推送。
+
+ }
+ />
+
+ )
+}
+
+function StatusSection() { return (