Skip to content

异步回调

更新时间:2026/9/11 20:21:59

概述

当订单状态发生变更时(如支付成功、支付失败、关闭、退款成功),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通知事件码(见下方事件类型)
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分账失败
risk.hit风控命中(黑名单/海外 IP 等规则触发)

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

回调示例:支付成功

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-01 12:00:00",
  "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-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 秒),系统将按上述 重试策略 重新发送通知。

官方网站 · 基于 GNU LGPL v3.0 协议开源