在保持 DLC 默认零破坏的前提下,支持 priority/仅乐玩/仅 DLC 履约策略与后台 GUI 配置;同步补齐 Prepare 渠道落库、乐玩订单取消、Webhook 验签与失败重试。 Co-authored-by: Cursor <cursoragent@cursor.com>
102 KiB
开放平台接入文档
版本:V1.2
Base URL:https://shop.cloudtentacles.com
快速开始
先完成以下五步,再参考各接口的参数与示例。下单受理不等于发货成功,请以逐件结果为准。
Base URLhttps://shop.cloudtentacles.com
接口前缀/openapi/v1
鉴权协议HMAC-SHA256 / V1
数据格式UTF-8 JSON
- 开通权限登录用户必须实时具备
openapi:use,然后在开放平台创建应用。 - 配置来源 IP所有 POST 写请求必须命中应用 IP 白名单;GET 查询仍需签名,但不要求白名单。
- 签发密钥妥善保存 AccessKey 与仅显示一次的 SecretKey,调用时不使用浏览器 JWT。
- 查询并下单先读取商品的 accessMode、库存、发货方式与角色累计发货上限,只对 PURCHASE 商品幂等下单。
- 接收异步结果配置 HTTPS Webhook,并同时保留订单查询作为补偿手段。
V1 仅支持 LINK_BINDING 和 EMAIL_BINDING;CDK 商品不会出现在商品列表中,也不能下单。
统一响应包装
成功响应为 { data, requestId };失败响应为 { statusCode, error, message, code, requestId, timestamp, path }。响应头 X-Request-Id 与响应体的 requestId 一致。
应用 ID 在哪里查看
进入开放平台 → 应用密钥,在应用名称下方查看并复制应用 ID(applicationId);签发或轮换密钥的弹窗也会显示该应用 ID。
applicationId 标识应用,与 AccessKey 不同;同一应用可以有多个 AccessKey,但共用一个应用 ID。普通签名请求通过 X-OpenAPI-Access-Key 定位应用,无需另传 applicationId;收到 Webhook 后,按其 applicationId 选择对应应用的 WebhookSecret 验签。
管理接口中,应用列表的 data.items[].id、签发密钥响应的 data.applicationId 都是应用 ID;签发密钥响应的 data.id 是密钥记录 ID,不要混用。
密钥只保存在服务端
SecretKey 仅在签发时显示一次,只能保存在调用方服务端。不要放入网页、移动端、URL、日志或工单。
调用配额与重试
| 查询/分钟 | 下单请求/分钟 | 下单件数/分钟 | 绑定刷新/分钟 |
|---|---|---|---|
| 1800 | 300 | 600 | 600 |
- 表中为单应用按每分钟折算的持续速率,另有短时突发保护。同一用户的多个应用共享用户总额度,全平台另有总量保护,不能把各应用额度直接相加。
- 取消、账号退还及邮箱验证/重发各有独立限制,不占绑定刷新额度。
binding/action与binding/refresh共用绑定刷新额度及单订单冷却,最快每 5 秒一次;优先使用 Webhook,查询用于补偿,不要对全部历史订单持续轮询。- 遇到
429按响应头Retry-After等待并加入少量随机延迟;重试下单保留原callerOrderId,但使用新的时间戳、nonce 和签名。 - 下单请求每次计数;已经受理的相同幂等订单不重复消耗件数额度。入口配额不是渠道履约速度或硬件容量承诺。
鉴权与签名
每个请求都必须携带以下四个请求头。服务端会统一返回 X-Request-Id,请在报错和技术支持场景中保留。
| 请求头 | 规则 |
|---|---|
X-OpenAPI-Access-Key |
应用签发的 AccessKey,16 至 64 位;首位为字母或数字,其余可含下划线和连字符 |
X-OpenAPI-Timestamp |
Unix 秒级时间戳,必须位于服务端时间 ±5 分钟内 |
X-OpenAPI-Nonce |
每次请求唯一的 16 至 64 位字母、数字、下划线或连字符,首位必须是字母或数字 |
X-OpenAPI-Signature |
HMAC-SHA256 小写十六进制,可选带 v1= 前缀 |
六行待签名串
HTTP_METHODPATHRFC3986_QUERYSHA256_HEX_OF_RAW_BODYUNIX_TIMESTAMP_SECONDSNONCE
- HTTP 方法必须转为大写。
- PATH 是完整路径,不包含域名、查询串和片段,例如
/openapi/v1/orders。 - 查询参数按 RFC 3986 编码,再按编码后的键和值使用码点顺序排序;重复键必须保留。
- 规范化基于查询串解析后的键值对;空格统一编码为
%20,不使用+。 - 请求体摘要基于实际发送的原始字节;签名后不能再次格式化 JSON。
- GET 或无请求体 POST 使用空字节摘要
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。 - 六行之间仅使用一个 LF 字符
\n,末尾不追加换行。
signature = hex(HMAC-SHA256(SecretKey, signingString))
Nonce 一旦被同一密钥接受,在防重放窗口内不可再次使用;重试必须生成新 Timestamp、Nonce 和 Signature。业务幂等由 callerOrderId 保证。
固定验签测试向量
以下值与服务端回归测试共用,可直接放入调用方单元测试,用于发现查询串规范化、原始请求体或 Webhook 验签的差异。
这是离线回归向量,固定密钥是公开测试数据,固定时间戳也已不适用于实时请求。不要将这些值用作真实应用凭据或发送到线上接口。
签名版本v1
固定时间戳1788566400
测试 AccessKeyak_test_fixed_20260905
测试 SecretKeysk_test_fixed_20260905
1. GET 查询
| 方法 / PATH | GET /openapi/v1/products |
|---|---|
| 原始查询串 | pageSize=20&page=1 |
| 规范查询串 | page=1&pageSize=20 |
| Nonce | nonce_get_20260905_fixed |
| 请求体 SHA-256 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
原始请求体
(空字符串)
六行待签名串
GET
/openapi/v1/products
page=1&pageSize=20
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1788566400
nonce_get_20260905_fixed
预期请求签名54ea478585e61230be670d664745c81ffb4907b87c8592bf082c9cc894232adf
2. POST JSON
| 方法 / PATH | POST /openapi/v1/orders |
|---|---|
| 原始查询串 | (空字符串) |
| 规范查询串 | (空字符串) |
| Nonce | nonce_post_20260905_fixed |
| 请求体 SHA-256 | 146e687459b70c7fdf2781f72593306a8041aa0d5212207459dc71cd76e59032 |
原始请求体
{"callerOrderId":"DOC-20260905-001","roleId":"1148711973","items":[{"productId":"00000000-0000-4000-8000-000000000101","quantity":2}]}
六行待签名串
POST
/openapi/v1/orders
146e687459b70c7fdf2781f72593306a8041aa0d5212207459dc71cd76e59032
1788566400
nonce_post_20260905_fixed
预期请求签名1e1d3df46b885e76b1e992585215e488c29315e7207e62df4aa26b0124d04968
3. 重复查询参数
| 方法 / PATH | GET /openapi/v1/orders |
|---|---|
| 原始查询串 | status=SUCCEEDED&keyword=%E4%BA%91%20%E8%A7%A6%E8%A7%92&status=FAILED |
| 规范查询串 | keyword=%E4%BA%91%20%E8%A7%A6%E8%A7%92&status=FAILED&status=SUCCEEDED |
| Nonce | nonce_repeat_20260905_fixed |
| 请求体 SHA-256 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
原始请求体
(空字符串)
六行待签名串
GET
/openapi/v1/orders
keyword=%E4%BA%91%20%E8%A7%A6%E8%A7%92&status=FAILED&status=SUCCEEDED
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1788566400
nonce_repeat_20260905_fixed
预期请求签名c1c2be18372e3ac89e8042f3477fff8287126e31777a18a593c0cce7028626d8
4. POST 空请求体
| 方法 / PATH | POST /openapi/v1/orders/00000000-0000-4000-8000-000000000201/account/release |
|---|---|
| 原始查询串 | (空字符串) |
| 规范查询串 | (空字符串) |
| Nonce | nonce_empty_20260905_fixed |
| 请求体 SHA-256 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
原始请求体
(空字符串)
六行待签名串
POST
/openapi/v1/orders/00000000-0000-4000-8000-000000000201/account/release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1788566400
nonce_empty_20260905_fixed
预期请求签名167f6d7c116fc7e51b0d2418e65a0705f08076940025f2c013adbd8f27648908
Webhook 固定向量
| Webhook Secret | whsec_test_fixed_20260905 |
|---|---|
| Timestamp | 1788566400 |
| Application ID | 00000000-0000-4000-8000-000000000301 |
| Event ID | 00000000-0000-4000-8000-000000000302 |
Webhook 原始请求体
{"applicationId":"00000000-0000-4000-8000-000000000301","data":{"callerOrderId":"DOC-20260905-001","failedQuantity":1,"failureCodes":["SUPPLIER_REJECTED"],"orderId":"00000000-0000-4000-8000-000000000303","pendingQuantity":0,"releasedQuantity":0,"status":"PARTIAL_SUCCESS","succeededQuantity":1},"eventId":"00000000-0000-4000-8000-000000000302","occurredAt":"2026-09-05T00:00:00.000Z","orderVersion":7,"schemaVersion":"1.0","type":"ORDER_COMPLETED"}
Webhook 待签名字节
1788566400.00000000-0000-4000-8000-000000000302.{"applicationId":"00000000-0000-4000-8000-000000000301","data":{"callerOrderId":"DOC-20260905-001","failedQuantity":1,"failureCodes":["SUPPLIER_REJECTED"],"orderId":"00000000-0000-4000-8000-000000000303","pendingQuantity":0,"releasedQuantity":0,"status":"PARTIAL_SUCCESS","succeededQuantity":1},"eventId":"00000000-0000-4000-8000-000000000302","occurredAt":"2026-09-05T00:00:00.000Z","orderVersion":7,"schemaVersion":"1.0","type":"ORDER_COMPLETED"}
预期 Webhook 签名v1=ee3968ef467a4f9c19c0a33e7af1bb3118fab8d02c5ebf9cbd8001cc34ebe6be
查询商品
GET/openapi/v1/products
| 参数 | 类型与限制 | 说明 |
|---|---|---|
categoryId |
UUID v4,可选 | 包含指定分类及其后代分类 |
keyword |
1–100 字符,可选 | 匹配商品名称或简介 |
page |
1–10000,默认 1 | 页码 |
pageSize |
1–100,默认 50 | 每页数量 |
- 返回当前账号有查看权限、在售、有库存、媒体可用且渠道映射有效的 LINK / EMAIL 商品。
accessMode=VIEW只能展示;只有accessMode=PURCHASE才能下单,服务端会实时复核。effectiveUnitPrice是当前账号实际支付的最小货币单位,使用十进制字符串防止精度丢失。- 当前
CREDIT的cnyCentRate=1,即 1 CREDIT = 人民币 1 分;下单只传数量,不回传或信任客户端价格。 - 开放 API 下单后直接进入绑定与发货流程,不进入商城背包;其数量限制与商城单次购买上限相互独立。
roleDeliveryLimit为同一 SKU + 经验证角色 ID 的全系统累计发货上限;null表示不限,更换手机号不会重置。thumbnailUrl始终是可直接访问的绝对 HTTPS 地址,不会返回/uploads/...相对路径。updatedAt是基础商品记录的更新时间;productVersion是对该应用所属用户实际可见投影(包括成交价)计算的sha256:版本指纹。
GET/openapi/v1/products/{id}
详情接口与列表使用完全相同的可见性、售卖窗口、库存、媒体与渠道映射条件;不存在或当前不可用统一返回 OPENAPI_PRODUCT_NOT_FOUND。
当前版本不支持 updatedSince 增量同步。请分页全量拉取,以 productVersion 判断单个商品投影是否变化;不要把 updatedAt 当成完整增量游标。
成功响应示例
{
"data": {
"items": [
{
"id": "11111111-1111-4111-8111-111111111111",
"categoryId": "55555555-5555-4555-8555-555555555555",
"categoryName": "永劫无间",
"name": "示例商品",
"summary": "商品简介",
"thumbnailUrl": "https://shop.cloudtentacles.com/api/v1/media/commerce/example.webp",
"stock": 500,
"roleDeliveryLimit": 10,
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"accessMode": "PURCHASE",
"updatedAt": "2026-09-05T00:00:00.000Z",
"productVersion": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"total": 1,
"page": 1,
"pageSize": 50
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
下单、并行与幂等
POST/openapi/v1/orders
请求限制
callerOrderId长度 1–128,只能包含字母、数字、点、下划线、冒号和连字符。roleId长度 1–191,去除首尾空白,不能包含控制字符。items为 1–10 行;单请求最多包含 10 种不同商品,同一productId不允许重复。- 每行
quantity必须是正整数且受 PostgreSQLINTEGER存储边界限制;整单总量还会按下单件数风控策略校验。 - 业务额度按“同一 SKU + 经验证角色 ID”累计发货上限计算;任一商品剩余额度不足时整单立即拒绝。
- 角色累计额度在创建订单时原子预占;明确失败释放,结果未知时继续占用,避免并发绕过。
- 一张订单内必须是同一个分类、同一种发货方式。
处理模型
- 不同订单且使用不同手机号时可并行;同一手机号的发货严格串行。
- 同一 SKU 明确失败会停止其尚未开始的剩余件,其他 SKU 继续。
- 已经开始的单件继续确认,因此完成顺序和 Webhook 顺序不保证与请求 items 顺序一致。
- 若不同系统 SKU 映射到相同的上游 game + productId,为保证安全对账也会串行。
- 渠道提交结果未知时按 2 / 4 / 8 / 16 / 32 秒进行五次对账;仍无法确认时进入人工异常处理,绝不自动重复发货。
请求体
{
"callerOrderId": "merchant-order-20260824-0001",
"roleId": "123456789",
"items": [
{
"productId": "11111111-1111-4111-8111-111111111111",
"quantity": 2
},
{
"productId": "22222222-2222-4222-8222-222222222222",
"quantity": 1
}
]
}
LINK 待绑定响应
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"idempotentReplay": false
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
callerOrderId 幂等规则
它在同一应用内永久不可复用。相同规范化请求重放会返回原订单,并将 idempotentReplay 设为 true;相同单号但请求内容不同返回 CALLER_ORDER_ID_CONFLICT。即使订单终态或超出列表查询窗口,该单号也不会释放给新订单。
收到冲突时禁止自动换号重发。先按原单号精确查单,核对角色、商品、数量和原业务请求;若仍属于原业务,则继续跟踪原单。只有明确确认是一次新的业务发货后,才使用新 callerOrderId。
下单超时时精确查单
调用 GET /openapi/v1/orders/by-caller-order-id?callerOrderId=...。该查询只能读取当前鉴权应用的订单,不受列表接口 90 天时间窗口限制。查到原单就继续跟踪;明确返回 OPENAPI_ORDER_NOT_FOUND 后,才可用原参数和原 callerOrderId 幂等重试。
金额、结算与资金流水
当前换算口径:1 CREDIT = 人民币 1 分,100 CREDIT = 人民币 1 元。所有金额均是最小单位的十进制字符串,请使用整数或 BigInt 计算,禁止浮点运算。
订单与商品行金额字段
| 字段 | 类型 | 口径 |
|---|---|---|
effectiveUnitPrice |
十进制字符串 | 该商品行创建订单时的单价快照 |
grandTotal / orderedAmount |
十进制字符串 | 原始下单金额;商品行为单价 × 数量 |
settledAmount |
十进制字符串 | 发货成功并已结算的金额 |
releasedAmount |
十进制字符串 | FAILED、STOPPED、CANCELLED 单件已退回可用余额的金额;不能只乘 releasedQuantity |
heldAmount |
十进制字符串 | 结果尚未明确、仍被冻结的金额;订单终态必须为 "0" |
feeAmount |
十进制字符串 | 平台手续费;当前固定为 "0" |
对订单和每个商品行都应满足 orderedAmount = settledAmount + releasedAmount + heldAmount。响应同时携带 currency.code、name、scale 和 cnyCentRate;未配置人民币换算的未来币种会返回 cnyCentRate=null,不得当作 1:1。
商品行的 releasedAmount = effectiveUnitPrice × (failedQuantity + releasedQuantity);settledAmount = effectiveUnitPrice × succeededQuantity,heldAmount = effectiveUnitPrice × pendingQuantity。整单金额分别汇总各行,不能用某一商品单价乘整单数量。
GET/openapi/v1/wallets
scope=OWNER_SHARED表示这是应用所属用户的共享钱包,网页商城及同一用户的其他开放应用会共同改变余额。availableBalance为可用余额,heldBalance包含所有业务中的冻结,totalBalance为两者之和。
GET/openapi/v1/wallet-transactions
scope=APPLICATION_ORDERS仅返回当前鉴权应用订单产生的HOLD、SETTLE和RELEASE,不包含其他应用、网页商城、充值或赠送流水。- 可按
effect、currencyCode、startedAt/endedAt筛选;默认 30 天,单次跨度最多 90 天。 - 使用不透明
cursor翻页;hasMore=true时将nextCursor原样传给下一页,不要解析或自行构造。
钱包余额响应
{
"data": {
"scope": "OWNER_SHARED",
"items": [
{
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"active": true,
"cnyCentRate": 1
},
"availableBalance": "125000",
"heldBalance": "4500",
"totalBalance": "129500",
"updatedAt": "2026-09-05T00:00:00.000Z"
}
]
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
当前应用订单资金流水
{
"data": {
"scope": "APPLICATION_ORDERS",
"items": [
{
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"effect": "SETTLE",
"amount": "1000",
"availableDelta": "0",
"heldDelta": "-1000",
"availableBalanceAfter": "125000",
"heldBalanceAfter": "3500",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"active": true,
"cnyCentRate": 1
},
"orderId": "33333333-3333-4333-8333-333333333333",
"callerOrderId": "merchant-order-20260824-0001",
"createdAt": "2026-09-05T00:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null,
"pageSize": 50,
"startedAt": "2026-08-06T00:00:00.000Z",
"endedAt": "2026-09-05T00:00:00.000Z"
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK / EMAIL 绑定流程
下单时系统优先按 roleId 匹配手机号最后一次实时绑定的角色,仍需重新登录并实时核对当前绑定。确认角色一致时 bindingRequired=false,订单直接排队;未匹配才返回绑定动作。
并发幂等下单时,bindingAction 可能暂时返回 allocationInProgress=true。请使用原 callerOrderId 和完全相同的请求体重试下单,但每次生成新的 Timestamp、Nonce 和 Signature;不要创建新业务单号。
重新进入页面与链接恢复
- 先调用
GET /openapi/v1/orders/{id},没有平台 UUID 时按原callerOrderId精确查单。data.bindingAction是随订单保存的只读快照;GET 不生成新链接、不发送验证码、也不查询上游。 - 绑定链接有效期为 3 分钟,从后端取得链接的
bindingUrlIssuedAt起算,以服务端返回的bindingUrlExpiresAt为准。订单仍为WAITING_BINDING、租约和绑定窗口均未结束且链接未过期时,才展示链接或二维码。 - 链接缺失或过期时,调用
POST /openapi/v1/orders/{id}/binding/action,从data.bindingAction取得恢复结果;仍使用原订单。3 分钟内重复调用也复用同一链接,不重新请求渠道、不重置链接到期时间;到期后再次调用才获取新链接,没有自动后台刷新。 - 普通查单不生成新链接;已有链接时,相同
callerOrderId的幂等重放也不替换链接,尚未完成初始化的原单仍可能生成首个链接。链接过期后返回bindingUrl=null、bindingUrlExpired=true,保留签发和到期时间,并提供bindingActionEndpoint恢复入口。不要继续展示已过期二维码,也不要因此创建新业务订单。 - 链接自然到期不会主动增加
orderVersion;即使订单版本未变,也须根据bindingUrlExpiresAt停止展示旧链接。缓存命中不增加版本;实际重新生成时,预留和写入新链接会更新版本。 allocationInProgress=true表示账号还在分配;bindingActionInProgress=true表示绑定链接仍在生成。请等冷却后查询原单或重试原动作,勿并发申请或换新订单号。- EMAIL 已发送时恢复接口返回已有发送状态;需要重新发送时使用
email/resend。验证码业务冷却至少 60 秒,进行中的发送 / 验证会推迟resendAvailableAt;即使倒计时已结束,仍须遵循额外 HTTP 429 的Retry-After。 - 重新生成链接和重发邮箱验证码都不会延长
bindingDeadline或租期。窗口过期后先查原单终态,确认未发货数量及冻结释放结果,再由业务决定是否新建发货单;不能直接自动换号。
| bindingAction 字段 | 类型 / 含义 |
|---|---|
bindingRequired / bindingType |
是否仍需绑定,以及 LINK_BINDING / EMAIL_BINDING;非等待绑定状态的历史时间不代表仍允许操作 |
expiresAt / bindingDeadline |
ISO 8601 绝对时间;前者是手机号租期、未分配时为 null,后者是订单绑定窗口截止时间 |
bindingUrl / bindingUrlIssuedAt |
当前可用的绑定入口和后端取得链接的时间,未生成时为 null;过期仅隐藏链接,保留时间;离开 WAITING_BINDING 后两者隐藏为 null |
bindingUrlExpiresAt |
ISO 8601 到期时间,为 bindingUrlIssuedAt 加 180 秒;没有链接或不再等待绑定时为 null。expiresAt 是租约截止时间,不能代替链接 TTL |
bindingUrlExpired / bindingActionEndpoint |
链接是否已过期;仅 LINK 链接已过期且当前仍可绑定时提供恢复动作的 POST 路径,其余情况为 null。过期判断不会自动向渠道请求新链接,租约或绑定窗口结束后不能恢复 |
emailCodeSent / emailCodeSentAt |
是否已有成功发送记录及其绝对时间;未知时间为 null,不代表验证码仍有效 |
resendAvailableAt |
按已保存状态推导的最早业务重发时间(ISO 8601 或 null);并非所有限流的通过承诺 |
allocationInProgress / bindingActionInProgress |
分别表示账号分配中、LINK 动作生成中;依据订单版本更新显示状态 |
所有时间均为带时区的绝对时间,客户端自行计算倒计时。bindingUrl 来自上游返回的 HTTPS redirectUrl,调用方应直接展示该地址;平台不会把它替换为管理页面。上游可能使用跳转入口,最终游戏直链和固定域名范围尚未获得明确保证,不能自行拼接域名。链接 3 分钟有效期、30 分钟租约和绑定校验有效期相互独立。
LINK_BINDING
- 读取创建订单响应中的
bindingAction.bindingUrl、bindingUrlExpiresAt和租约expiresAt。 - 将链接或二维码展示给最终用户,等待其完成绑定。
- 调用
POST /orders/{id}/binding/refresh刷新最新角色。 matched=true后自动进入发货;不匹配时展示返回的角色信息并允许再次刷新。
EMAIL_BINDING
- 创建订单会尝试发送验证码,并返回
emailCodeSent或发送中状态。 - 用户收到验证码后调用
POST /orders/{id}/email/verify,请求体为{ "code": "123456" }。 - 只有返回角色与订单
roleId一致才会自动进入发货。 - 确需重发时调用
POST /orders/{id}/email/resend,并遵循服务端限频。
绑定窗口最长 30 分钟。超时后系统自动取消未发放商品、释放冻结资产和库存,并推送 BINDING_TIMEOUT。
BINDING_UPDATED表示通过 LINK 显式刷新或 EMAIL 验证获得了新绑定快照;BINDING_VERIFIED表示最新角色已与订单roleId匹配。- 首次按已绑定
roleId直接匹配时也会产生验证事件。平台当前不会在绑定窗口内无限制主动轮询上游;调用方完成 LINK 绑定后仍应调用 refresh,EMAIL 流程则提交 verify。
完整绑定响应示例
LINK 查单:重新进入时读取已保存链接
GET /openapi/v1/orders/{id}
按 callerOrderId 精确查单返回相同结构。GET 的 data 直接是订单,不套 order;这里不会请求上游。
{
"data": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK 查单:链接已过期,使用原单恢复
GET /openapi/v1/orders/{id}
链接过期后不返回旧 URL,保留签发与到期时间,并提供 bindingActionEndpoint;只读查询不会调用渠道生成新链接。
{
"data": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": true,
"bindingActionEndpoint": "/openapi/v1/orders/33333333-3333-4333-8333-333333333333/binding/action",
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK 有效期内再次获取:复用原链接
POST /openapi/v1/orders/{id}/binding/action
3 分钟内复用同一链接、签发时间和到期时间,不请求渠道;订单版本不会因为读取缓存而增加。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK 到期后恢复绑定动作成功
POST /openapi/v1/orders/{id}/binding/action
链接已到期后用户主动调用才重新获取上游绑定地址;等待中的并发请求通过 bindingActionInProgress 表示。示例 URL 是不可用于真实绑定的占位地址。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 4,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:03:02.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:06:02.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:03:02.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:06:02.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK 刷新:角色匹配,开始排队
POST /openapi/v1/orders/{id}/binding/refresh
binding.matched=true 后订单进入 QUEUED;仍须等待发货结果。非等待绑定状态不再返回可使用的绑定 URL。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 6,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "QUEUED",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": false,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": true,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": "123456789",
"roleName": "示例角色",
"roleArea": "示例区服"
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": "2026-08-24T12:02:00.000Z",
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"binding": {
"bindingRequired": false,
"matched": true,
"binding": {
"userBound": true,
"roleId": "123456789",
"roleName": "示例角色",
"roleArea": "示例区服"
}
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
LINK 刷新:角色不匹配,继续等待绑定
POST /openapi/v1/orders/{id}/binding/refresh
HTTP 成功但 binding.matched=false;展示实际返回的角色并提示换绑,不得当作发货成功。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 4,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": "https://binding.example/game/link-demo",
"bindingUrlIssuedAt": "2026-08-24T12:00:01.000Z",
"bindingUrlExpiresAt": "2026-08-24T12:03:01.000Z",
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": "987654321",
"roleName": "其他角色",
"roleArea": "示例区服"
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"binding": {
"bindingRequired": true,
"matched": false,
"binding": {
"userBound": true,
"roleId": "987654321",
"roleName": "其他角色",
"roleArea": "示例区服"
}
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
EMAIL 查单:已发送验证码与重发冷却
GET /openapi/v1/orders/{id}
resendAvailableAt 是当前持久化发送 / 验证状态推导的最早业务重试时间,不代表请求必能通过全部 HTTP 限流。
{
"data": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "EMAIL_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "EMAIL_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": true,
"emailCodeSentAt": "2026-08-24T12:00:01.000Z",
"resendAvailableAt": "2026-08-24T12:01:01.000Z",
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
EMAIL 获取 / 恢复动作成功
POST /openapi/v1/orders/{id}/binding/action
已发送时返回已有动作;首次发送未完成时可以恢复。明确重发仍使用 email/resend。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 3,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "EMAIL_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "EMAIL_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": true,
"emailCodeSentAt": "2026-08-24T12:00:01.000Z",
"resendAvailableAt": "2026-08-24T12:01:01.000Z",
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"bindingAction": {
"bindingRequired": true,
"bindingType": "EMAIL_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": true,
"emailCodeSentAt": "2026-08-24T12:00:01.000Z",
"resendAvailableAt": "2026-08-24T12:01:01.000Z",
"allocationInProgress": false,
"bindingActionInProgress": false
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
EMAIL 验证成功且角色匹配
POST /openapi/v1/orders/{id}/email/verify
请求体 { "code": "123456" } 仅为格式示例,应填写实际收到的验证码;成功后进入 QUEUED。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 6,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "EMAIL_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "QUEUED",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": false,
"bindingType": "EMAIL_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": true,
"emailCodeSentAt": "2026-08-24T12:00:01.000Z",
"resendAvailableAt": "2026-08-24T12:01:01.000Z",
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": true,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": "123456789",
"roleName": "示例角色",
"roleArea": "示例区服"
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": "2026-08-24T12:02:00.000Z",
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "QUEUED",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"binding": {
"bindingRequired": false,
"matched": true,
"binding": {
"userBound": true,
"roleId": "123456789",
"roleName": "示例角色",
"roleArea": "示例区服"
}
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
EMAIL 重发成功
POST /openapi/v1/orders/{id}/email/resend
data.binding 返回发送操作结果;最新冷却绝对时间位于 data.order.bindingAction.resendAvailableAt。
{
"data": {
"order": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 4,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "EMAIL_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "4500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "4500",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 3,
"failureCodes": [],
"status": "WAITING_BINDING",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": true,
"bindingType": "EMAIL_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": true,
"emailCodeSentAt": "2026-08-24T12:02:00.000Z",
"resendAvailableAt": "2026-08-24T12:03:00.000Z",
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "ACTIVE",
"verified": false,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": null,
"roleName": null,
"roleArea": null
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": null,
"completedAt": null,
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 2,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "2000",
"orderedAmount": "2000",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2000",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 2,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
},
{
"sequence": 1,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
},
{
"id": "88888888-8888-4888-8888-888888888888",
"productId": "22222222-2222-4222-8222-222222222222",
"productName": "示例商品 B",
"quantity": 1,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "2500",
"grandTotal": "2500",
"orderedAmount": "2500",
"settledAmount": "0",
"releasedAmount": "0",
"heldAmount": "2500",
"feeAmount": "0",
"status": "QUEUED",
"succeededQuantity": 0,
"failedQuantity": 0,
"releasedQuantity": 0,
"pendingQuantity": 1,
"failureCodes": [],
"units": [
{
"sequence": 0,
"status": "WAITING_BINDING",
"supplierState": null,
"failureCode": null,
"failureReason": null,
"completedAt": null
}
]
}
]
},
"binding": {
"sent": true,
"inProgress": false,
"sentAt": "2026-08-24T12:02:00.000Z"
}
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
EMAIL 业务冷却尚未结束
{
"statusCode": 409,
"error": "ConflictException",
"message": "验证码发送过于频繁,请60秒后再试",
"code": "EMAIL_CODE_RATE_LIMITED",
"requestId": "44444444-4444-4444-8444-444444444444",
"timestamp": "2026-08-24T12:02:30.000Z",
"path": "/openapi/v1/orders/33333333-3333-4333-8333-333333333333/email/resend"
}
绑定窗口已过期
{
"statusCode": 409,
"error": "ConflictException",
"message": "绑定已超时",
"code": "BINDING_TIMEOUT",
"requestId": "44444444-4444-4444-8444-444444444444",
"timestamp": "2026-08-24T12:30:01.000Z",
"path": "/openapi/v1/orders/33333333-3333-4333-8333-333333333333/binding/refresh"
}
订单状态与部分成功
| 订单状态 | 含义 |
|---|---|
WAITING_BINDING |
等待 LINK 或 EMAIL 绑定完成,最长 30 分钟 |
QUEUED |
绑定已验证,等待发货 Worker 领取 |
PROCESSING |
至少一件商品正在渠道处理 |
RECONCILING |
渠道提交结果未知,正在按 2 / 4 / 8 / 16 / 32 秒进行五次对账 |
REVIEW_REQUIRED |
渠道记录无法唯一确认,需要管理员人工对账 |
SUCCEEDED |
全部单件发货成功并已结算 |
PARTIAL_SUCCESS |
部分成功;成功部分正常结算,其余已失败或释放 |
FAILED |
没有单件成功,失败或未发部分已按规则释放 |
CANCELLED |
调用方取消或绑定超时自动取消 |
以逐件结果为最终依据
订单及每个 item 都包含 quantity、succeededQuantity、failedQuantity、releasedQuantity、pendingQuantity 和去重后的 failureCodes;商品行另有 units。不要把 HTTP 下单成功理解为发货成功。
部分成功不会整单回滚
成功单件正常结算;明确失败或被停止的未发单件释放冻结金额并恢复库存。当前不支持退款。
数量互斥与守恒
四类结果数量互斥:一件商品只能计入成功、明确失败、未执行释放、尚未确定中的一类。failedQuantity 不包含 releasedQuantity;但这两类对应的资金都会计入 releasedAmount。
整单和每个商品行均满足 quantity = succeededQuantity + failedQuantity + releasedQuantity + pendingQuantity。订单各数量等于商品行对应字段之和;终态 pendingQuantity=0。failureCodes 是诊断码集合,不是件数,不能靠码的个数计算失败数量。
| 逐件状态 units[].status | 唯一归属字段 | 含义 |
|---|---|---|
WAITING_BINDING |
pendingQuantity |
尚未完成绑定 |
QUEUED |
pendingQuantity |
已排队,尚未提交渠道 |
SUBMITTING |
pendingQuantity |
已越过提交边界,渠道是否受理可能尚未明确 |
SUBMITTED |
pendingQuantity |
已取得渠道发货单号,等待结果 |
PROCESSING |
pendingQuantity |
渠道正在处理 |
RECONCILING |
pendingQuantity |
结果未知,正在查询渠道记录核实 |
REVIEW_REQUIRED |
pendingQuantity |
等待管理员人工对账;不能视为失败或补发 |
SUCCEEDED |
succeededQuantity |
已确认成功并结算 |
FAILED |
failedQuantity |
已明确失败且资金和额度已释放;也可能是在提交前预检中明确失败 |
STOPPED |
releasedQuantity |
尚未执行的单件被停止,资金和额度已释放 |
CANCELLED |
releasedQuantity |
尚未执行的单件被取消,资金和额度已释放 |
部分成功完整查单:成功 1 件、明确失败 1 件、停止释放 1 件
原价合计 3000 分,结算 1000 分,退回 2000 分;最多可考虑补发 2 件,但需先处理失败原因并重新通过角色额度校验。
{
"data": {
"id": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"orderVersion": 9,
"roleId": "123456789",
"categoryId": "55555555-5555-4555-8555-555555555555",
"deliveryType": "LINK_BINDING",
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"orderedAmount": "3000",
"settledAmount": "1000",
"releasedAmount": "2000",
"heldAmount": "0",
"feeAmount": "0",
"quantity": 3,
"succeededQuantity": 1,
"failedQuantity": 1,
"releasedQuantity": 1,
"pendingQuantity": 0,
"failureCodes": [
"SKU_STOPPED_AFTER_FAILURE",
"SUPPLIER_REJECTED"
],
"status": "PARTIAL_SUCCESS",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingAction": {
"bindingRequired": false,
"bindingType": "LINK_BINDING",
"expiresAt": "2026-08-24T12:30:00.000Z",
"bindingDeadline": "2026-08-24T12:30:00.000Z",
"bindingUrl": null,
"bindingUrlIssuedAt": null,
"bindingUrlExpiresAt": null,
"bindingUrlExpired": false,
"bindingActionEndpoint": null,
"emailCodeSent": false,
"emailCodeSentAt": null,
"resendAvailableAt": null,
"allocationInProgress": false,
"bindingActionInProgress": false
},
"binding": {
"status": "QUARANTINED",
"verified": true,
"expiresAt": "2026-08-24T12:30:00.000Z",
"roleId": "123456789",
"roleName": "示例角色",
"roleArea": "示例区服"
},
"failureCode": null,
"failureReason": null,
"review": null,
"createdAt": "2026-08-24T12:00:00.000Z",
"startedAt": "2026-08-24T12:02:00.000Z",
"completedAt": "2026-08-24T12:10:00.000Z",
"items": [
{
"id": "77777777-7777-4777-8777-777777777777",
"productId": "11111111-1111-4111-8111-111111111111",
"productName": "示例商品",
"quantity": 3,
"currency": {
"code": "CREDIT",
"name": "积分",
"scale": 0,
"cnyCentRate": 1
},
"effectiveUnitPrice": "1000",
"grandTotal": "3000",
"orderedAmount": "3000",
"settledAmount": "1000",
"releasedAmount": "2000",
"heldAmount": "0",
"feeAmount": "0",
"status": "PARTIAL_SUCCESS",
"succeededQuantity": 1,
"failedQuantity": 1,
"releasedQuantity": 1,
"pendingQuantity": 0,
"failureCodes": [
"SKU_STOPPED_AFTER_FAILURE",
"SUPPLIER_REJECTED"
],
"units": [
{
"sequence": 0,
"status": "SUCCEEDED",
"supplierState": 4,
"failureCode": null,
"failureReason": null,
"completedAt": "2026-08-24T12:09:00.000Z"
},
{
"sequence": 1,
"status": "FAILED",
"supplierState": null,
"failureCode": "SUPPLIER_REJECTED",
"failureReason": "渠道明确拒绝该件",
"completedAt": "2026-08-24T12:10:00.000Z"
},
{
"sequence": 2,
"status": "STOPPED",
"supplierState": null,
"failureCode": "SKU_STOPPED_AFTER_FAILURE",
"failureReason": "同商品前件失败,停止尚未执行的单件",
"completedAt": "2026-08-24T12:10:00.000Z"
}
]
}
]
},
"requestId": "44444444-4444-4444-8444-444444444444"
}
PARTIAL_SUCCESS 后如何补发
- 只能对原订单中已明确失败或已释放的数量补发;先根据
failureCodes处理原因,再创建一张使用**新callerOrderId**的新订单。 - 原订单已是终态,不能再整单取消,也不能在原单上追加数量或由调用方改写结果。
- 角色累计发货上限计入已成功数量和结果尚未明确的预占数量;已明确失败或释放的数量不再占用,新单仍会在事务内重新校验。
REVIEW_REQUIRED 由平台管理员处理
调用方不需要、也不能自行选择渠道记录。平台管理员登录 /open-platform 后,通过“开放订单”人工对账;运营目标为 15 分钟内响应、2 小时内完成处理,但这是人工服务目标,不是自动完成或确定时限承诺。订单详情的 review.enteredAt、review.updatedAt 和 review.expectedResolutionAt 分别表示首次进入时间、最近更新时间和目标完成时间,离开人工状态后仍保留供追溯。处理期间不会自动重发、判定成功或退款,请通过 Webhook 和查单接口继续跟踪。
开放 API 订单终态自动退回手机号
SUCCEEDED、PARTIAL_SUCCESS、FAILED 或 CANCELLED 终态会自动退回开放 API 订单占用的手机号,先进入隔离池,默认 10 分钟后复查绑定信息再进入后续冷却或可用流程。同分类最后绑定角色一致的正常退还账号,指定角色申请可提前复用;使用中、有在途订单、存在异常或正在复核的账号不可抢占,复用后仍需实时核对当前绑定。这不跳过申请配额,也不清除普通分配的冷却。RECONCILING 和 REVIEW_REQUIRED 不是终态,不会提前退回。这不改变网页端手动发货“用户手动退还或等租期到期”的原有规则。
接口清单、查询、取消与账号释放
所有路径均需签名。点击路径旁的复制按钮,可直接复制完整接口路径。
查单与回调共用 orderVersion
订单响应直接返回 orderVersion,它与该订单 Webhook 顶层版本同源。低版本结果丢弃,高版本按实际提供字段更新;相同版本的查单可以补全金额、绑定和逐件结果。Webhook 是事件摘要,字段随事件类型变化,不能用它覆盖完整订单对象或把缺失数量默认成 0。
绑定信息是该订单的持久化快照,不会随账号退回后被其他人使用而变成别人的角色。GET 只读;动作恢复需显式调用 binding/action。旧版客户端发现响应缺少 orderVersion 时不能按 0 合并,应升级契约处理并重新查询。
查询当前应用所属用户有权查看的 LINK / EMAIL 商品
GET/openapi/v1/products
请求输入
categoryId?、keyword?、page?、pageSize?
响应内容
商品分页列表、库存、角色累计发货上限、发货方式和当前用户成交单价
查询当前可见且可售的单个商品
GET/openapi/v1/products/{id}
请求输入
路径参数 id(UUID v4)
响应内容
与商品列表单行相同的完整投影,含 updatedAt / productVersion
查询应用所属用户的共享钱包余额
GET/openapi/v1/wallets
请求输入
无
响应内容
可用、冻结、合计余额及币种口径
查询当前应用订单产生的资金流水
GET/openapi/v1/wallet-transactions
请求输入
effect?、currencyCode?、startedAt?、endedAt?、cursor?、pageSize?
响应内容
HOLD / SETTLE / RELEASE 及可用、冻结余额变化
幂等创建聚合订单;完成绑定后自动开始逐件发货
POST/openapi/v1/orders
请求输入
callerOrderId、roleId、items[{ productId, quantity }]
响应内容
order、bindingAction、idempotentReplay
查询单笔订单、绑定信息、商品汇总和逐件结果
GET/openapi/v1/orders/{id}
请求输入
路径参数 id(UUID v4)
响应内容
含 orderVersion、完整数量/金额/逐件结果和只读 bindingAction 的订单快照;不会调用上游或发送验证码
按调用方订单号精确查单,用于下单超时补偿
GET/openapi/v1/orders/by-caller-order-id
请求输入
查询参数 callerOrderId(1–128 字符)
响应内容
不受 90 天列表窗口限制的单笔订单快照
查询当前应用最近最多 90 天的订单记录
GET/openapi/v1/orders
请求输入
startedAt?、endedAt?、callerOrderId?、page?、pageSize?
响应内容
订单分页列表
重新进入时显式获取或恢复原订单绑定动作
POST/openapi/v1/orders/{id}/binding/action
请求输入
无请求体;与 binding/refresh 共用绑定刷新额度
响应内容
order、bindingAction;可能调用上游生成链接或恢复邮箱动作,不创建新订单
LINK 用户绑定后刷新角色信息;匹配成功即进入发货
POST/openapi/v1/orders/{id}/binding/refresh
请求输入
无请求体
响应内容
order、binding
提交 EMAIL 绑定验证码;匹配成功即进入发货
POST/openapi/v1/orders/{id}/email/verify
请求输入
{ code: string },4 至 8 位数字
响应内容
order、binding
在允许的状态和频率内重新发送 EMAIL 绑定验证码
POST/openapi/v1/orders/{id}/email/resend
请求输入
无请求体
响应内容
order、binding
所有单件均未越过渠道提交边界时取消整单
POST/openapi/v1/orders/{id}/cancel
请求输入
无请求体
响应内容
取消后的订单快照
订单终态后幂等确认退回(终态已会自动退回)
POST/openapi/v1/orders/{id}/account/release
请求输入
无请求体
响应内容
order、lease
- 订单列表的
startedAt/endedAt使用 ISO 8601;结束时间必须晚于开始时间,单次跨度不超过 90 天。 - 列表不传时间时默认查询截至当前时间的最近 90 天;
pageSize最大 100。按callerOrderId精确查单不受该窗口限制。 - 只要任意单件越过渠道提交边界,整单取消就会返回
ORDER_NOT_CANCELLABLE。 - 开放 API 订单终态已会自动退回手机号并默认进入 10 分钟隔离复查;同分类原角色符合安全条件时可提前申请复用,普通分配仍等待复核与冷却。
account/release是幂等兜底和主动确认接口,重复调用不会重复退回。
Webhook 事件与验签
投递约定
- 回调地址必须为公网 HTTPS,系统会拒绝本地、私网、保留地址和不安全重定向。
- Webhook Secret 与 API SecretKey 是两套独立密钥,首次配置或轮换时同样只显示一次,不能混用。
- 采用至少一次投递;接收方必须以
eventId建立唯一键去重,共享一个回调入口的多应用再以applicationId路由到对应鉴权。 - 验签时要求请求头与请求体的
eventId一致,并拒绝超出本地允许时钟窗口的时间戳。 - 订单事件的
orderVersion从 1 开始、按订单单调递增,与查单data.orderVersion共用。低版本丢弃,高版本更新已提供字段,相同版本查单可补全细节;回调摘要不能整单覆盖。不同订单之间不保证顺序;WEBHOOK_TEST的版本为null。 schemaVersion表示回调请求体结构版本;当前为1.0。数量字段与整单/商品行failureCodes可用于提醒和可补发判断,最终仍以查单结果为准。- 验签成功并持久化事件后快速返回任意 2xx;非 2xx、网络错误或超时会重试。
- 单次请求体不超过 256 KiB,Content-Type 为
application/json; charset=utf-8,绝对超时为 10 秒;回调响应体请保持在 64 KiB 内。 - 最多投递 8 次(首次 + 7 次自动重试),失败后依次等待 1 分钟、5 分钟、15 分钟、1 小时、3 小时、6 小时、12 小时;耗尽后进入
DEAD,可在开放平台人工重放。 - Webhook 只负责通知,订单查询接口是断点恢复和漏通知补偿依据。
回调请求头
X-OpenAPI-Application-Id
当前事件所属开放应用,与请求体 applicationId 一致
X-OpenAPI-Event-Id
固定事件 ID,同时用于幂等去重
X-OpenAPI-Schema-Version
回调请求体结构版本,当前为 1.0
X-OpenAPI-Timestamp
本次投递的 Unix 秒级时间戳
X-OpenAPI-Signature
v1= + HMAC-SHA256 hex
timestamp + "." + eventId + "." + rawBody
签名对象是原始 UTF-8 请求体字节,不是解析后再序列化的 JSON;签名请求头固定以 v1= 开头。
LINK 显式刷新或 EMAIL 验证取得绑定快照时发送 BINDING_UPDATED;角色匹配并进入 QUEUED 时发送 BINDING_VERIFIED。当前没有后台主动轮询绑定变化,调用方完成绑定后仍需执行 refresh 或 verify。
| 事件 | 触发时机 |
|---|---|
ORDER_CREATED |
订单已受理 |
BINDING_UPDATED |
显式刷新或 EMAIL 验证后获得了新的绑定快照 |
BINDING_VERIFIED |
最新角色与订单 roleId 匹配,订单进入发货队列 |
UNIT_SUCCEEDED |
单件发货成功且整单尚未结束 |
UNIT_FAILED |
单件发货失败且整单尚未结束 |
ORDER_COMPLETED |
整单到达成功、部分成功或失败终态 |
ORDER_REVIEW_REQUIRED |
订单需要管理员人工对账 |
ORDER_RECONCILIATION_RESUMED |
管理员完成选择后恢复自动处理 |
BINDING_TIMEOUT |
30 分钟内未完成绑定,订单已自动取消 |
ORDER_CANCELLED |
订单被调用方取消 |
事件请求体
{
"schemaVersion": "1.0",
"applicationId": "00000000-0000-4000-8000-000000000301",
"eventId": "66666666-6666-4666-8666-666666666666",
"orderVersion": 7,
"type": "ORDER_COMPLETED",
"occurredAt": "2026-08-24T12:10:00.000Z",
"data": {
"orderId": "33333333-3333-4333-8333-333333333333",
"orderNo": "O...",
"callerOrderId": "merchant-order-20260824-0001",
"status": "PARTIAL_SUCCESS",
"succeededQuantity": 2,
"failedQuantity": 1,
"releasedQuantity": 0,
"pendingQuantity": 0,
"failureCodes": [
"SUPPLIER_REJECTED"
],
"changedUnitId": "99999999-9999-4999-8999-999999999999",
"items": [
{
"productId": "11111111-1111-4111-8111-111111111111",
"status": "SUCCEEDED",
"quantity": 2,
"succeededQuantity": 2,
"failedQuantity": 0,
"releasedQuantity": 0,
"failureCodes": []
},
{
"productId": "22222222-2222-4222-8222-222222222222",
"status": "FAILED",
"quantity": 1,
"succeededQuantity": 0,
"failedQuantity": 1,
"releasedQuantity": 0,
"failureCodes": [
"SUPPLIER_REJECTED"
]
}
]
}
}
Node.js 验签
import { createHmac, timingSafeEqual } from 'node:crypto'
// rawBody 必须是在 JSON 解析前保留的原始请求体字节。
function verifyWebhook({ rawBody, timestamp, eventId, applicationId, schemaVersion, signature, secret, now = Date.now() }) {
if (!/^(0|[1-9]\d{0,12})$/.test(timestamp)) return false
if (Math.abs(Number(timestamp) * 1000 - now) > 5 * 60 * 1000) return false
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(eventId)) return false
const payload = Buffer.concat([Buffer.from(timestamp + '.' + eventId + '.', 'utf8'), rawBody])
const expected = createHmac('sha256', secret).update(payload).digest('hex')
const actual = signature.startsWith('v1=') ? signature.slice(3) : ''
const verified = /^[a-fA-F0-9]{64}$/.test(actual) &&
timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(actual, 'hex'))
if (!verified) return false
// 只在验签后解析 JSON,并校验请求头和请求体身份一致。
try {
const body = JSON.parse(rawBody.toString('utf8'))
return body.eventId === eventId && body.applicationId === applicationId &&
body.schemaVersion === schemaVersion
} catch { return false }
}
// 验签成功后以 eventId 做唯一键;重复事件直接返回 2xx。
// 查单与回调共用 orderVersion:低版本丢弃,高版本合并已提供字段。
// 相同版本的查单可补全逐件/金额/绑定细节;回调摘要不能覆盖完整订单对象。
// WEBHOOK_TEST 的 orderVersion 为 null,不参与订单版本比较。
错误码与安全重试
| HTTP | code | 含义 | 处理建议 |
|---|---|---|---|
| 400 | HTTP_400 |
请求体、查询参数、UUID 或字段格式未通过校验 | 按 message 修正参数后,使用新的签名信息重试 |
| 400 | DUPLICATE_PRODUCT_LINE |
同一 SKU 在 items 中重复出现 | 修正参数后重试 |
| 400 | MIXED_CATEGORY_OR_DELIVERY_TYPE |
订单混入不同分类或不同发货方式 | 拆分订单 |
| 400 | MIXED_CURRENCY |
一张聚合订单混入了不同币种 | 按币种拆分订单 |
| 400 | INVALID_QUERY_RANGE |
订单或资金流水查询时间无效,或单次跨度超过 90 天 | 修正时间范围 |
| 400 | INVALID_CURSOR |
资金流水翻页游标无效 | 丢弃自行构造的游标,从首页重新查询 |
| 401 | OPENAPI_CREDENTIAL_INVALID |
凭据、时间戳、Nonce、签名或写请求 IP 校验失败 | 检查接入配置;服务端刻意不暴露具体鉴权原因 |
| 403 | OPENAPI_PERMISSION_REVOKED |
应用所属用户已不再具备 openapi:use | 联系管理员恢复权限 |
| 403 | PRODUCT_ACCESS_DENIED |
商品可以查看,但当前用户没有购买权限 | 只对 accessMode=PURCHASE 的商品下单 |
| 404 | OPENAPI_PRODUCT_NOT_FOUND / OPENAPI_ORDER_NOT_FOUND / HTTP_404 |
商品或当前应用下的订单不存在 | 检查资源 ID |
| 503 | OPENAPI_PRODUCT_MEDIA_URL_INVALID |
商品图片未能安全解析为绝对 HTTPS 地址 | 停止展示该商品并联系平台管理员 |
| 409 | CALLER_ORDER_ID_CONFLICT |
相同 callerOrderId 已用于不同的请求内容 | 禁止自动换号重发。先按原 callerOrderId 精确查单,核对角色、商品、数量和原业务请求;确认属于一次新的业务发货后才使用新单号 |
| 409 | ROLE_DELIVERY_LIMIT_EXCEEDED |
同一 SKU + 经验证角色 ID 的全系统累计发货额度不足 | 减少发货数量或更换真实角色;更换手机号无法规避 |
| 409 | PRODUCT_NOT_ON_SALE / PRODUCT_MEDIA_NOT_READY / CURRENCY_DISABLED |
商品或币种在下单前已变为不可用 | 重新查询 products,不要对原商品盲目重试 |
| 409 | PRODUCT_CHANNEL_MAPPING_UNAVAILABLE / PRODUCT_SUPPLY_PRICE_UNSAFE / OPENAPI_CDK_UNSUPPORTED / PRICE_OVERFLOW |
商品渠道映射或供货价不可用,或商品不在 V1 支持范围 | 停止自动重试并联系平台管理员 |
| 409 | INSUFFICIENT_STOCK / INSUFFICIENT_BALANCE |
库存或应用所属用户余额不足 | 补足后使用同一 callerOrderId 重试 |
| 409 | NO_VIRTUAL_ACCOUNT_AVAILABLE / VIRTUAL_ACCOUNT_LIMIT_REACHED |
当前没有可租用账号,或用户在该分类达到租用上限 | 本单会取消;容量恢复后使用新 callerOrderId 下单 |
| 409 | BINDING_TIMEOUT / BINDING_ROLE_MISMATCH |
绑定已超时,或最新绑定角色与订单 roleId 不一致 | 超时后新建订单;不一致时重新绑定 |
| 409 | EMAIL_CODE_RATE_LIMITED |
EMAIL 验证码发送过于频繁 | 查询原单的 bindingAction.resendAvailableAt,等冷却结束后再重发;额外 HTTP 限流仍须遵循 429 的 Retry-After |
| 409 | EMAIL_CODE_SEND_IN_PROGRESS / EMAIL_VERIFICATION_IN_PROGRESS |
同一订单正在发送验证码或提交验证,两个渠道操作会互斥 | 先查询订单状态;仍等待绑定时,等处理窗口结束后再重试,期间不要盲目 resend |
| 409 | EMAIL_BINDING_RESULT_UNKNOWN |
验证码提交结果不确定,系统未盲目重复绑定 | 查询订单;必要时调用 resend |
| 409 | BINDING_LEASE_NOT_READY / OPENAPI_ORDER_BINDING_STATE_CHANGED / OPENAPI_ORDER_NOT_WAITING_BINDING |
手机号分配未完成、已失效,或订单绑定状态已发生并发变化 | 先查询订单;仍在等待绑定时再幂等重试原操作 |
| 409 | OPENAPI_ORDER_SUPPLIER_SNAPSHOT_INCONSISTENT / OPENAPI_ORDER_SNAPSHOT_SUPPLIER_UNAVAILABLE / OPENAPI_BINDING_LEASE_SUPPLIER_MISMATCH |
订单不可变渠道快照或已分配账号与渠道不一致 | 停止自动重试,保留 requestId 并联系平台管理员 |
| 409 | OPENAPI_LEASE_RELEASE_NOT_ALLOWED |
订单尚未进入终态,不能调用幂等退回确认 | 等订单进入终态;系统会自动退回,release 仅作兜底 |
| 409 | ORDER_NOT_CANCELLABLE |
至少一件商品已经越过渠道提交边界或订单已终态 | 不可整单取消,继续查询最终结果 |
| 429 | OPENAPI_RATE_LIMITED |
超过应用级、同用户共享、全平台、单订单操作、下单件数或预认证防护阈值,或触发短时突发保护 | 优先遵循 Retry-After;未提供时指数退避并加入抖动 |
| 5xx | ORDER_FINANCIAL_STATE_INCONSISTENT / ORDER_CURRENCY_INCONSISTENT / ORDER_QUANTITY_STATE_INCONSISTENT / ORDER_BINDING_STATE_INCONSISTENT / HTTP_500 |
订单金额、币种、数量或绑定快照无法确认时返回 503,其他服务异常可能返回 500;响应包含 requestId | 保留原 callerOrderId 查询,保存 requestId 联系平台;不能将异常当作未发货、盲目补发或自动换新单号 |
错误响应结构
{
"statusCode": 409,
"error": "ConflictException",
"message": "调用方订单号已用于不同请求",
"code": "CALLER_ORDER_ID_CONFLICT",
"requestId": "44444444-4444-4444-8444-444444444444",
"timestamp": "2026-08-24T12:00:00.000Z",
"path": "/openapi/v1/orders"
}
所有 HTTP 429 响应都会携带 Retry-After(秒)。必须先按该值等待,再使用新 Timestamp / Nonce / Signature 重试;不要立即并发放大。
不要因网络超时自动生成新订单号。保留 callerOrderId,先查询订单状态;确认未受理时再使用相同参数幂等重试。
完整 Node.js 签名与请求示例
示例要求 Node.js 18+,仅应运行在可信服务端。请通过安全的环境变量或密钥管理服务注入密钥。
联调环境与覆盖范围
当前尚未提供独立隔离沙箱的地址和联调账号。固定验签向量只用于离线算法回归,开放平台的测试回调只验证连通性与签名,均不能替代免绑定、LINK / EMAIL、部分成功、超时已受理、重复 / 乱序回调和人工对账的完整联调。
独立沙箱及可控制的异常场景需另行建设,请联系平台协调联调安排;实际游戏绑定、真实邮箱验证码和渠道最终结果还需供应商测试资源验证。本文的业务响应均为结构示例,不是已完成真实联调的证明。
Node.js 18+
import { createHash, createHmac, randomBytes } from 'node:crypto'
const baseUrl = 'https://shop.cloudtentacles.com'
const accessKey = process.env.OPENAPI_ACCESS_KEY
const secretKey = process.env.OPENAPI_SECRET_KEY
if (!accessKey || !secretKey) throw new Error('缺少开放平台密钥')
const method = 'POST'
const path = '/openapi/v1/orders'
const query = new URLSearchParams()
const rawBody = JSON.stringify({ callerOrderId: 'demo-001', roleId: '123456789',
items: [{ productId: '11111111-1111-4111-8111-111111111111', quantity: 1 }] })
const timestamp = String(Math.floor(Date.now() / 1000))
const nonce = randomBytes(18).toString('base64url')
// RFC 3986 编码后再按编码后的键和值排序,重复键不能合并。
const encode = (value) => encodeURIComponent(value).replace(/[!'()*]/g,
(char) => '%' + char.charCodeAt(0).toString(16).toUpperCase())
const canonicalQuery = [...query.entries()]
.map(([key, value]) => [encode(key), encode(value)])
.sort(([ak, av], [bk, bv]) => ak < bk ? -1 : ak > bk ? 1 : av < bv ? -1 : av > bv ? 1 : 0)
.map(([key, value]) => key + '=' + value).join('&')
const bodyHash = createHash('sha256').update(Buffer.from(rawBody, 'utf8')).digest('hex')
const signingString = [method, path, canonicalQuery, bodyHash, timestamp, nonce].join('\n')
const signature = createHmac('sha256', secretKey).update(signingString).digest('hex')
const response = await fetch(baseUrl + path, {
method,
headers: {
'content-type': 'application/json; charset=utf-8',
'x-openapi-access-key': accessKey,
'x-openapi-timestamp': timestamp,
'x-openapi-nonce': nonce,
'x-openapi-signature': signature,
},
body: rawBody,
})
console.log(response.status, await response.json())