WEBHOOKS / PAYMENT ORDER
支付订单回调事件
支付订单状态变化时,派付以 HTTPS POST 推送 payment_order.* 事件。投递、签名、ACK 与重试机制与充值 / 提现回调完全相同。
回调地址
- 下单时传了
callbackUrl→ 推到该地址; - 否则用应用后台为
PAYMENT_ORDER业务配置的回调地址; - 两者都没有 → 不推送(只能靠查询)。
事件
event
payment_order.paid已支付 optional | 累计到账 ≥ 应付金额(含多付)时推送;开启「过期到账自动完成」时迟到款补足也会推送;后台人工「标记已支付」时补推。每笔订单最多推送一次。 |
payment_order.partial_paid部分支付 optional | 首次部分到账时推送,默认关闭,需在应用支付设置里开启「部分支付回调」。每笔订单最多一次(之后再到账不再推 partial_paid)。 |
payment_order.expired已过期 optional | 待支付 / 待选币订单到期未付,或部分支付补款超时。由每分钟一次的过期扫描或读订单时触发。 |
payment_order.cancelled已取消 optional | 商户在后台取消订单。 |
payment_order.late_paid过期后到账 optional | 订单过期后收到款项、且应用未开启「过期到账自动完成」时推送(订单状态 LATE_PAID,待人工处理)。新增事件,随后端新版本上线;此前版本该场景不推送。 |
EXCEPTION(KYT 高风险)、错链 / 错币 / 错 Memo 等异常不推送回调,需在后台处理。
Payload
{
"event": "payment_order.paid",
"id": 5001,
"applicationId": 1024,
"appOrderNo": "ORDER_20261023_0001",
"status": "PAID",
"exceptionReason": null,
"handlingStatus": "NONE",
"fiatCurrency": "USD",
"fiatAmount": "100.00",
"payCurrency": "USDT",
"chainId": 3,
"chainCode": "TRON",
"chainTokenId": 50,
"tokenSymbol": "USDT",
"payAmount": "100.010001",
"rateSnapshotId": 7001,
"paidAmount": "100.010001",
"overpaidAmount": "0",
"payAddress": "TXyz...abc",
"memo": null,
"txHash": "a1b2c3...",
"expireTimeMillis": 1792732800000,
"matchedTxs": [
{
"rechargeRecordId": 9001,
"amount": "100.010001",
"fromAddress": "TPayer...",
"txHash": "a1b2c3...",
"exceptionReason": null,
"handlingStatus": "NONE",
"riskLevel": null
}
],
"riskLevel": null,
"riskReason": null,
"riskSnapshot": null,
"extras": "{\"userId\":\"u_1024\"}"
}字段说明
eventstring required | 事件名,见上表。 |
idinteger required | 支付订单 id。 |
applicationIdinteger required | 所属应用 id。 |
appOrderNostring required | 商户业务单号。 |
statusstring required | 推送时的订单状态(PAID / PARTIAL_PAID / EXPIRED / CANCELLED / LATE_PAID …)。 |
exceptionReasonstring? optional | 异常原因,如 OVERPAID / UNDERPAID_TIMEOUT / LATE_PAYMENT。 |
handlingStatusstring? optional | 异常处理状态。 |
fiatCurrencystring? optional | 法币币种。 |
fiatAmountstring? optional | 法币金额,字符串(不带科学计数法)。 |
payCurrencystring? optional | 收款币种。 |
chainIdinteger? optional | 公链内部 id。 |
chainCodestring? optional | 公链 code。新增字段,随后端新版本上线。 |
chainTokenIdinteger? optional | 代币 id。 |
tokenSymbolstring? optional | 代币符号。新增字段,随后端新版本上线。 |
payAmountstring? optional | 应付金额,字符串。 |
rateSnapshotIdinteger? optional | 汇率快照 id;直接按币数下单时为 null。 |
paidAmountstring required | 累计到账金额,字符串。 |
overpaidAmountstring required | 多付金额,字符串。 |
payAddress / memostring? optional | 收款地址 / Memo(Tag)。 |
txHashstring? optional | 最近一次匹配交易哈希。 |
expireTimeMillisinteger? optional | 订单过期时间(毫秒)。新增字段,随后端新版本上线。 |
matchedTxsarray required | 已匹配到本订单的全部到账交易(按匹配顺序)。 rechargeRecordIdinteger对应的充值记录 id。 amountstring该笔金额。 fromAddressstring转出地址。 txHashstring交易哈希。 exceptionReason / handlingStatusstring?该笔的异常原因 / 处理状态。 riskLevelstring?该笔 KYT 风险等级。 |
riskLevel / riskReason / riskSnapshotstring? optional | KYT 风险信息(命中高风险时回填)。 |
extrasstring? optional | 下单时传入的扩展字段。 |
金额是字符串,没有 network 字段
金额字段一律为字符串,请用十进制库处理,不要转浮点。支付订单回调不带充值回调里的network 字段。 验签、ACK 与重试
与其它回调共用同一套机制,完整说明与多语言验签代码见 Payload 与验签: 4 个 header(X-Api-Key / X-Timestamp / X-Nonce / X-Signature), stringToSign = "POST\n" + path + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + SHA256_hex(rawBody), X-Signature = HMAC-SHA256(apiSecret, stringToSign) 小写 hex。
回调签名的 path 不含 query
回调签名里的 path 只取回调 URL 的 path 部分,不含?query。 这一点与你调用派付 API 时的请求签名不同(请求签名自 2026-10-01 起 PATH 含 query)。 建议回调地址不要依赖 query 参数区分业务,需要区分时用不同的 path。 - ACK:HTTP 2xx 且响应体表示成功(如
{"code":0}、{"success":true}或纯文本success/ok)。 - 重试:失败后按指数退避重试,最多 10 次;任务状态与重发见回调任务查询(
bizType=PAYMENT_ORDER、bizId=订单 id)与重发回调(按 callbackTaskId)。
验签自测向量
以下数值由派付后端签名实现实际计算得出,可用于核对你的验签代码:
apiSecret = sk_test_demo_secret
method = POST
path = /pqpa/callback # 回调 URL 的 path,不含 query
X-Timestamp= 1792732800000
X-Nonce = 4f1c2b7e9a0d4e3f8b6a5c1d2e3f4a5b
rawBody = {"event":"payment_order.paid","id":5001,"appOrderNo":"ORDER_20261023_0001","status":"PAID"}
SHA256_hex(rawBody) = c1dcdb37d50265575f78e3bde0e3c241bb458645e8c2deea779a81694eb471b0
X-Signature = 2ab084bf6a2e1679678c753478d697bbfca53895332bc70c9ac7ded68a8e755d幂等处理建议
- 以
(appOrderNo, event)去重:同一事件可能因重试或人工重发多次到达。 - 事件可能乱序到达(例如重试中的
expired晚于人工补推的paid):本地订单已是「已支付」时,忽略后到的关闭类事件。 - 发货 / 入账只在验签通过的
payment_order.paid上做;拿不准时用按单号查询复核status。
// 伪代码:按 (appOrderNo, event) 去重,并以订单终态为准
const p = JSON.parse(rawBody);
if (await seen(p.appOrderNo, p.event)) return ack(); // 重复推送,直接 ACK
switch (p.event) {
case 'payment_order.paid':
await markOrderPaid(p.appOrderNo, p.paidAmount); // 发货 / 入账
break;
case 'payment_order.expired':
case 'payment_order.cancelled':
await closeOrder(p.appOrderNo); // 若本地已是已支付,忽略
break;
case 'payment_order.late_paid':
await flagForManualReview(p.appOrderNo); // 不要自动发货
break;
}
return ack(); // HTTP 200 + {"code":0}迟到付款(LATE_PAID)
订单过期后仍可能收到付款人的转账(钱包广播慢、付款人没看倒计时)。处理取决于应用支付设置「过期到账自动完成」:
- 关闭(默认):订单转
LATE_PAID(exceptionReason=LATE_PAYMENT,handlingStatus=PENDING),推送payment_order.late_paid(新版本)。商户在后台核实后可「标记已支付」,此时补推payment_order.paid。 - 开启:迟到款按正常付款累计,足额即转
PAID并推送payment_order.paid。
注意:此前已推送过 payment_order.expired 的订单,之后仍可能收到 late_paid / paid,本地状态机要允许「已过期 → 已支付」。