Skip to content

异步回调 ​

更新时间:2026/9/22 11:20:47

概述 ​

当订单状态发生变更时(如支付成功、支付失败、关闭、退款成功),DaxPay 系统会主动向商户发送异步通知。通知的「怎么投递」与「报文长什么样」是两个正交维度:传输通道(HTTP 回调 / MQ 推送)× 报文格式(系统协议 / 易支付协议)。

传输通道与报文格式 ​

传输通道(transport) —— 决定通知如何投递:

transport投递方式ACK 语义重试
httpHTTP 请求(POST JSON 或 GET query,由报文格式决定)HTTP 2xx 且响应体 trim 后为 SUCCESS16 次递增重试
mq发布到 Artemis Topic daxpay.notice.<appId>publish 成功即视为投递成功3 次短间隔重试

报文格式(format) —— 决定报文如何组装与签名:

format适用场景请求方式报文格式验签方式
system标准支付 API(/unipay/*)发起的订单POSTJSON bodyRSA(SHA256withRSA)
easy_pay易支付插件(/epay/*)发起的订单GETURL query 参数MD5 / RSA(取决于 V1/V2)

easy_pay 格式是 GET 请求

易支付兼容格式(format=easy_pay)的回调走 GET + URL query 参数,不是 POST JSON。对接易支付时需用 request.getParameter() 接收,而非 request.getReader()。

通知流程(system 协议) ​

  1. 商户在发起支付请求时传入 notifyUrl 参数。
  2. 系统处理完业务后,向 notifyUrl 发送 POST 请求(JSON 格式)。
  3. 商户系统接收通知,先验签,再处理业务逻辑。
  4. 商户系统处理后,返回 SUCCESS 字符串(大小写不敏感,前后空格会被 trim)。
  5. 如果商户系统返回非 SUCCESS 或 HTTP 状态非 2xx 或超时未响应,系统按策略重试。

应用级订阅(并行通知) ​

除订单级 notifyUrl(每笔订单传入)外,商户还可在「异步通知配置」中为应用配置统一通知(HTTP 回调地址或 MQ 推送)+ 订阅事件。同一业务事件会并行生成订单级与应用级两条独立通知任务:

  • 订单级(source=order):走支付请求传入的 notifyUrl,恒 http + system
  • 应用级(source=app):走应用配置,按 notifyWay(http/mq)决定传输通道

可能收到两份通知

若商户既在订单传了 notifyUrl,又配置了应用级订阅,同一事件会分别发到订单级 URL 和应用级地址(两份独立通知,各自重试)。这是预期行为(双轨并行),便于商户同时推送到对账系统与业务系统。仅订单级 URL 为空 / 应用级未配置时才单发一路。

应用级配置见管理端「应用管理 → 异步通知配置」。

重试策略 ​

固定 16 次重试,间隔递增(实现于 NoticeRetryPolicy,通过 Artemis 延时队列调度):

重试次数间隔累计耗时
115s15s
215s30s
330s1min
43min4min
510min14min
620min34min
7~930min × 3~2h4min
1060min~3h4min
11~133h × 3~12h4min
14~166h × 3~30h4min
  • 最大重试次数:16 次(超过后停止,标记为发送失败)
  • 仅自动发送路径会重试:商户在管理端「手动重发通知」失败后不会自动重试,需再次手动触发
  • ACK 判定:HTTP 状态 2xx 且 响应体 trim 后忽略大小写等于 SUCCESS,两者缺一即视为失败并重试

MQ 推送方式的重试 ​

transport=mq 时仅当 publish 到 Topic 失败(broker 故障/网络异常)才重试 3 次,间隔 10s / 30s / 60s;消费侧消费失败由商户/MQ 自身机制负责(建议使用 JMS 持久订阅,离线不丢消息)。

MQ 推送方式 ​

当应用「异步通知配置」选择 notifyWay=mq 时,平台将通知消息(system 格式的 DaxNoticeResult JSON,含 RSA 签名)发布到按应用隔离的 Topic,商户自行订阅消费。

  • Topic 命名:daxpay.notice.<appId>,每个应用独立隔离
  • 消息体:与 HTTP 回调完全相同的 DaxNoticeResult JSON,验签逻辑一致
  • ACK 语义:publish 成功即视为投递完成(对齐 Stripe EventBridge 模型,推到事件总线即完成)
  • broker 要求:Artemis address 需配为 multicast 路由类型,商户侧用持久订阅(Durable Subscriber)

Java JMS 消费示例:

java
@JmsListener(
    destination = "daxpay.notice.APP001",       // 对应 appId
    containerFactory = "topicListenerFactory",  // pubSubDomain=true 的 Topic 监听工厂
    subscription = "daxpay-notify-app001"       // 持久订阅名(同一 appId 多实例去重)
)
public void onNotice(String body) {
    // body 为 DaxNoticeResult JSON, 验签逻辑与 HTTP 回调完全一致
    // 参见下方「签名验证」
}

回调消息格式 ​

请求方式:POST Content-Type:application/json

报文结构(DaxNoticeResult) ​

回调报文继承 DaxResult,增加事件与商户字段:

参数类型描述
eventstring通知事件码(见下方事件类型)
eventIdstring事件 ID(同一通知的多次重试推送恒为同值,商户端可据此做幂等去重)
protocolstring通知协议(system / easy_pay)
mchNostring商户号
appIdstring应用 ID
codeint状态码(0 = 成功)
msgstring提示信息
dataobject业务数据(订单/退款单快照)
signstring平台 RSA 签名(Base64)
resTimestring通知时间(北京时间,yyyy-MM-dd HH:mm:ss)
reqIdstring请求 ID

事件类型(NoticeEventEnum) ​

event说明
pay.success支付成功
pay.fail支付失败
pay.close支付关闭(主动关单)
pay.timeout支付超时关闭(业务单超时未付自动关闭)
pay.cancel支付撤销(资金态 CANCEL)
refund.success退款成功
refund.fail退款失败
refund.close退款关闭
transfer.success转账成功
transfer.fail转账失败
transfer.close转账关闭
alloc.success分账成功
alloc.fail分账失败

转账、分账通知分别取对应业务单携带的通知地址(分账请求时传入的 notifyUrl),并同样支持应用级订阅。分账通知快照含明细列表。

回调示例:支付成功 ​

json
{
  "event": "pay.success",
  "eventId": "1024",
  "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-01 12:00:00",
  "reqId": "NTF20241201001"
}

回调示例:退款成功 ​

json
{
  "event": "refund.success",
  "eventId": "1025",
  "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-01 14:00:00",
  "reqId": "NTF20241201002"
}

签名验证 ​

商户接收回调后,必须验证 sign 字段,确保消息由 DaxPay 平台发出且未被篡改。

验签步骤 ​

  1. 获取回调 JSON 中的所有字段。
  2. 移除 sign 字段。
  3. 排除值为空的字段。
  4. 将剩余字段按 key 的 ASCII 码升序排列。
  5. 拼接为 key1=value1&key2=value2 格式。
  6. 使用平台公钥以 SHA256withRSA 验签。

先验签再处理业务

务必先验签通过后再处理业务逻辑,避免伪造通知导致数据不一致。

验签代码示例参见 签名机制。

商户响应 ​

验签通过且业务处理成功后,商户系统应返回:

  • HTTP 状态码:2xx(200-299)
  • 响应体:SUCCESS(大小写不敏感,前后空格会被自动 trim)
text
SUCCESS

返回非 SUCCESS 或 HTTP 非 2xx 或超时(15 秒),系统将按上述 重试策略 重新发送通知。