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\"}"
}

字段说明

event
string required
事件名,见上表。
id
integer required
支付订单 id。
applicationId
integer required
所属应用 id。
appOrderNo
string required
商户业务单号。
status
string required
推送时的订单状态(PAID / PARTIAL_PAID / EXPIRED / CANCELLED / LATE_PAID …)。
exceptionReason
string? optional
异常原因,如 OVERPAID / UNDERPAID_TIMEOUT / LATE_PAYMENT。
handlingStatus
string? optional
异常处理状态。
fiatCurrency
string? optional
法币币种。
fiatAmount
string? optional
法币金额,字符串(不带科学计数法)。
payCurrency
string? optional
收款币种。
chainId
integer? optional
公链内部 id。
chainCode
string? optional
公链 code。新增字段,随后端新版本上线。
chainTokenId
integer? optional
代币 id。
tokenSymbol
string? optional
代币符号。新增字段,随后端新版本上线。
payAmount
string? optional
应付金额,字符串。
rateSnapshotId
integer? optional
汇率快照 id;直接按币数下单时为 null。
paidAmount
string required
累计到账金额,字符串。
overpaidAmount
string required
多付金额,字符串。
payAddress / memo
string? optional
收款地址 / Memo(Tag)。
txHash
string? optional
最近一次匹配交易哈希。
expireTimeMillis
integer? optional
订单过期时间(毫秒)。新增字段,随后端新版本上线。
matchedTxs
array required
已匹配到本订单的全部到账交易(按匹配顺序)。
rechargeRecordIdinteger

对应的充值记录 id。

amountstring

该笔金额。

fromAddressstring

转出地址。

txHashstring

交易哈希。

exceptionReason / handlingStatusstring?

该笔的异常原因 / 处理状态。

riskLevelstring?

该笔 KYT 风险等级。

riskLevel / riskReason / riskSnapshot
string? optional
KYT 风险信息(命中高风险时回填)。
extras
string? 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,本地状态机要允许「已过期 → 已支付」。