创建订单
在你的服务端调用,为一次业务创建一张平台支付单。
http
POST {API_BASE}/api/pay/open/createOrder
X-Pay-Api-Key: <你的 API KEY>
Content-Type: application/json这个接口的调用方是持 API KEY 的商家系统,不是浏览器。商户身份只来自 KEY。
请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantOrderNo | string | 是 | 你的订单号。幂等键,重试必须用同一个号。长度上限 64 |
amount | string | 是 | 代币单位的小数字符串,如 "100" / "100.5"。见下方"金额约定" |
chainId | number | 是 | 用哪条链收款(来自你的可用链清单) |
tokenId | number | 是 | 这条链上的哪个代币 |
subject | string | 否 | 标题,展示在收银页与订单列表里 |
notifyUrl | string | 否 | 本单专属回调地址,覆盖商户档案上的默认值 |
returnUrl | string | 否 | 支付完成后把买家浏览器跳回你的哪个地址 |
关于 notifyUrl 与 returnUrl 的区别:前者是服务器到服务器(你的后端据此发货), 后者是买家浏览器里的那一跳(你的"支付完成"页)。很多商家两个都要。
bash
curl -X POST "$API_BASE/api/pay/open/createOrder" \
-H "X-Pay-Api-Key: $PAY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"merchantOrderNo": "SHOP-2026-0001",
"chainId": 2,
"tokenId": 1,
"amount": "100.5",
"subject": "订单 SHOP-2026-0001",
"notifyUrl": "https://your-shop.com/pay/notify",
"returnUrl": "https://your-shop.com/pay/done"
}'金额约定
这是整套接口里最容易出错的地方,请务必看清。
| 接口 | amount 的形态 | 示例 |
|---|---|---|
| 开放接口下单(本页) | 代币单位的小数字符串 | "100" / "100.5" |
收银页线下下单 createCashierOrder | 最小单位整数字符串 | 6 位小数的币,100 就是 "100000000" |
两者刻意相反:商家的服务端本来就知道人可读的金额,而收银页前端是 parseUnits 之后的结果。 传错形态会被拒绝或被误解 —— 金额差 10 的 decimals 次方,而对方很可能不会立刻发现。
decimals 由代币决定(USDT 常见为 6),可以在链与代币清单里读到。
幂等:merchantOrderNo 是幂等键
同一个 merchantOrderNo 重复调用不会新建订单,而是返回同一张支付单,响应里的 idempotentReplay 为 true。
- 网络抖动、超时重试,用同一个
merchantOrderNo再发一次是安全的。 - 想确实再开一单,就必须换一个新的
merchantOrderNo。 - 为什么不这么做会出事:没有幂等键,重试会生成两张单、可能收到两笔钱, 而平台没有私钥,多收的那一笔退不回去。
响应
json
{
"code": 0,
"data": {
"order": {
"orderNo": "PO20261011XXXXXXXXXXXX",
"merchantOrderNo": "SHOP-2026-0001",
"merchantNo": "M00000XXXX",
"status": 1,
"statusText": "待支付",
"orderType": 1,
"amountMinimal": "100500000",
"payAmountMinimal": "100500123",
"payAmountDisplay": "100.500123",
"receivedAmountMinimal": "0",
"chainId": 2,
"chainKey": "tron",
"chainType": 2,
"tokenId": 1,
"tokenSymbol": "USDT",
"tokenContract": "TXxx...合约地址",
"tokenDecimals": 6,
"payAddress": "TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"subject": "订单 SHOP-2026-0001",
"txHash": "",
"confirmations": 0,
"requiredConfirmations": 19,
"expiresAt": "2026-10-11T10:30:00Z",
"identify": {
"mode": 1,
"orderNo": "PO20261011XXXXXXXXXXXX",
"calldataSuffix": "0x...订单识别码..."
},
"notifyUrl": "https://your-shop.com/pay/notify"
},
"idempotentReplay": false
},
"msg": "下单成功"
}关键字段
| 字段 | 说明 |
|---|---|
orderNo | 平台订单号。查询接口与收银页都用它定位这一单 |
idempotentReplay | true = 复用了已有支付单,不是新建。重试时必须读它,否则你会以为又开了一张新单 |
amountMinimal | 你下单时给的金额(最小单位) |
payAmountMinimal | 实际应该转的金额(最小单位)。识别方式为"金额尾数"时会比 amountMinimal 多一段尾数 —— 以它为准 |
payAmountDisplay | 上面那个给人看的形式,省得你按 decimals 自己除一遍 |
receivedAmountMinimal | 链上实际收到的金额。对账用,可能大于应付(买家多转),也可能是 0(还没扫到) |
payAddress | 这一单该转给谁(下单时锁定的快照) |
tokenContract | 代币合约地址。拼接交易要用它(交易的 to 是合约,不是收款地址,写反了代币一个都不会动) |
identify | 订单识别信息,必须原样透传,改了大小写或加前缀,扫块时就认不出这一单 |
notifyUrl | 这一单实际生效的回调地址。回给你,是为了让你当场发现"我传的地址没生效" |
expiresAt | 订单的付款有效期 |
用 payAmountMinimal,不要用 amountMinimal
买家实际该转的金额是 payAmountMinimal。当你看到它和 amountMinimal 不一样时, 那是识别用的尾数,不是错误。
常见拒绝原因
下单被拒时,错误信息是给人看的,会原样转达(例如"金额低于最小限额")。 这些不消耗幂等额度,可以修正后重试。完整的排查表见错误码。