异步回调
概述
当订单状态发生变更时(如支付成功、支付失败、关闭、退款成功),DaxPay 系统会主动向商户在支付请求中传入的 notifyUrl 发送异步通知。
两套通知协议
平台根据订单的支付来源选择通知协议,请求方式、报文格式、验签方式均不同:
| 协议 | protocol 值 | 适用场景 | 请求方式 | 报文格式 | 验签方式 |
|---|---|---|---|---|---|
| 系统协议 | system | 标准支付 API(/unipay/*)发起的订单 | POST | JSON body | RSA(SHA256withRSA) |
| 易支付协议 | easy_pay | 易支付插件(/epay/*)发起的订单 | GET | URL query 参数 | MD5 / RSA(取决于 V1/V2) |
easy_pay 协议是 GET 请求
易支付兼容协议(protocol=easy_pay)的回调走 GET + URL query 参数,不是 POST JSON。对接易支付时需用 request.getParameter() 接收,而非 request.getReader()。
通知流程(system 协议)
- 商户在发起支付请求时传入
notifyUrl参数。 - 系统处理完业务后,向
notifyUrl发送 POST 请求(JSON 格式)。 - 商户系统接收通知,先验签,再处理业务逻辑。
- 商户系统处理后,返回
SUCCESS字符串(大小写不敏感,前后空格会被 trim)。 - 如果商户系统返回非
SUCCESS或 HTTP 状态非 2xx 或超时未响应,系统按策略重试。
重试策略
固定 16 次重试,间隔递增(实现于 NoticeRetryPolicy,通过 Artemis 延时队列调度):
| 重试次数 | 间隔 | 累计耗时 |
|---|---|---|
| 1 | 15s | 15s |
| 2 | 15s | 30s |
| 3 | 30s | 1min |
| 4 | 3min | 4min |
| 5 | 10min | 14min |
| 6 | 20min | 34min |
| 7~9 | 30min × 3 | ~2h4min |
| 10 | 60min | ~3h4min |
| 11~13 | 3h × 3 | ~12h4min |
| 14~16 | 6h × 3 | ~30h4min |
- 最大重试次数:16 次(超过后停止,标记为发送失败)
- 仅自动发送路径会重试:商户在管理端「手动重发通知」失败后不会自动重试,需再次手动触发
- ACK 判定:HTTP 状态 2xx 且 响应体 trim 后忽略大小写等于
SUCCESS,两者缺一即视为失败并重试
回调消息格式
请求方式:POST Content-Type:application/json
报文结构(DaxNoticeResult)
回调报文继承 DaxResult,增加事件与商户字段:
| 参数 | 类型 | 描述 |
|---|---|---|
| event | string | 通知事件码(见下方事件类型) |
| protocol | string | 通知协议(system / easy_pay) |
| mchNo | string | 商户号 |
| appId | string | 应用 ID |
| code | int | 状态码(0 = 成功) |
| msg | string | 提示信息 |
| data | object | 业务数据(订单/退款单快照) |
| sign | string | 平台 RSA 签名(Base64) |
| resTime | string | 通知时间(UTC,ISO 8601) |
| reqId | string | 请求 ID |
事件类型(NoticeEventEnum)
| event | 说明 |
|---|---|
| pay.success | 支付成功 |
| pay.fail | 支付失败 |
| pay.close | 支付关闭 |
| refund.success | 退款成功 |
| refund.close | 退款关闭 |
回调示例:支付成功
json
{
"event": "pay.success",
"protocol": "system",
"mchNo": "M200000001",
"appId": "APP001",
"code": 0,
"msg": "success",
"data": {
"orderNo": "P2024120112345700001",
"bizOrderNo": "ORDER20241201001",
"tradeNo": "T2024120112345700001",
"amount": 100,
"realAmount": 100,
"status": "success",
"method": "wechat_qr",
"payTime": "2024-12-01 12:00:00",
"attach": "{\"orderId\": 123}"
},
"sign": "Base64签名值",
"resTime": "2024-12-01T04:00:00Z",
"reqId": "NTF20241201001"
}回调示例:退款成功
json
{
"event": "refund.success",
"protocol": "system",
"mchNo": "M200000001",
"appId": "APP001",
"code": 0,
"msg": "success",
"data": {
"refundNo": "R2024120112345700001",
"bizRefundNo": "REFUND20241201001",
"bizOrderNo": "ORDER20241201001",
"amount": 100,
"status": "success",
"finishTime": "2024-12-01 14:00:00"
},
"sign": "Base64签名值",
"resTime": "2024-12-01T06:00:00Z",
"reqId": "NTF20241201002"
}签名验证
商户接收回调后,必须验证 sign 字段,确保消息由 DaxPay 平台发出且未被篡改。
验签步骤
- 获取回调 JSON 中的所有字段。
- 移除
sign字段。 - 排除值为空的字段。
- 将剩余字段按 key 的 ASCII 码升序排列。
- 拼接为
key1=value1&key2=value2格式。 - 使用平台公钥以
SHA256withRSA验签。
先验签再处理业务
务必先验签通过后再处理业务逻辑,避免伪造通知导致数据不一致。
验签代码示例参见 签名机制。
商户响应
验签通过且业务处理成功后,商户系统应返回:
- HTTP 状态码:2xx(200-299)
- 响应体:
SUCCESS(大小写不敏感,前后空格会被自动 trim)
text
SUCCESS返回非 SUCCESS 或 HTTP 非 2xx 或超时(15 秒),系统将按上述 重试策略 重新发送通知。