通知商户(异步回调 notify_url)
1.1 说明
- 调用方向:通知中台 → 商户在签约时登记的
notifyUrl(HTTPS POST)。 - 报文分层:加签输入 全部在 HTTP 请求头;业务数据 仅在 请求体(
Content-Type: application/json)。不要 使用单行Authorization: RSA mch_id=...形式承载通知参数。 - 时间语义:业务 Body 不含
notifyTime;时间与 HeaderBaofu-Timestamp(秒级)及验签待签串一致。
1.2 请求头
键名与仓库 MerchantNotifyHeaderConstants 一致(subscription-pay 经 SDK 传入同名 inputParams,由中台映射为出站 Header):
| Header | 说明 |
|---|---|
Baofu-Timestamp |
秒级时间戳(与中台组签一致) |
Baofu-Nonce |
随机串 |
Baofu-Serial |
证书序列号(通常为商户 API 签证书序列号) |
Request-ID |
链路日志 ID(与开放网关 Request-ID 语义一致) |
Baofu-Signature-Type |
签名算法类型,如 RSA、SM2(与签约 Baofu-Sign-Type / sign_type 对齐) |
Baofu-Signature |
签名值 |
示例
POST /your/notify/path HTTP/1.1
Host: merchant.example.com
Content-Type: application/json
Accept: application/json
Baofu-Timestamp: 1766038862
Baofu-Nonce: 5FC2FC21E871BD5B8C7A3E0B0339BC8F
Baofu-Serial: 139248429
Request-ID: 7c9e2b1a-4d3f-4e1a-9c2b-0a1b2c3d4e5f
Baofu-Signature-Type: RSA
Baofu-Signature: <signature>
{"notifyType":"PAYMENT",...}
1.3 商户验签(待签串)
使用 com.baofoo.subscription.common.util.MerchantNotifySignUtil#buildResponseSignString(与开放网关应答侧约定一致):
- 取 Header 中秒级时间戳、随机串;数字信封若本次通知无则传空串(拼接时仍保留单独一行)。
- 取 HTTP Body 原始字节 对应的 JSON 字符串(与接收到的完全一致,勿重新格式化)。
- 按 「时间戳 +
\n+ 随机串 +\n+ 数字信封 +\n+ Body +\n」 拼接(每行以换行结束,包括最后一行)。 - 按
Baofu-Signature-Type使用约定证书验签Baofu-Signature。
1.4 请求体(业务 JSON,驼峰)
公共约定:
- 不含 字段
merchantNo;主体见unifiedMemberNo等。 - 不含
notifyTime。 - 支付/扣款类通知 Body 的
notifyType固定为PAYMENT;成功、失败等终态以status、failCode、failMessage区分(失败终态时后两者有业务含义,成功时可为空串)。 - 所有字段值均为 字符串类型(数值、日期等以字符串形式传输)。
1.4.1 扣款/支付类通知(notifyType=PAYMENT)
| 字段 | 类型 | 说明 |
|---|---|---|
notifyType |
string | 固定值 PAYMENT |
deductionNo |
string | 扣款单号 |
subscriptionNo |
string | 订阅号 |
unifiedMemberNo |
string | 统一会员编号 |
outTradeNo |
string | 商户签约订单号(与订阅同源) |
status |
string | 扣款单状态(SUCCESS / FAILED),见 §3.2 |
periodNumber |
string | 当前账单期数 |
scheduledPeriodDate |
string | 本期计划扣款日(自然日,如 2026-06-19) |
originalAmount |
string | 原始应扣金额(分) |
actualAmount |
string | 实际扣款金额(分) |
subscriptionStatus |
string | 当前订阅状态,见 §3.1 |
channelOrderNo |
string | 渠道订单号(可为空串) |
failCode |
string | 失败错误码(成功时为空串) |
failMessage |
string | 失败原因(成功时为空串) |
扣款成功示例(status=SUCCESS)
{
"notifyType": "PAYMENT",
"deductionNo": "DD01010120260319120000000001",
"subscriptionNo": "SP01010120260319120000000001",
"unifiedMemberNo": "UM_NEW_10001",
"outTradeNo": "MCH202603190001",
"status": "SUCCESS",
"periodNumber": "3",
"scheduledPeriodDate": "2026-06-19",
"originalAmount": "1990",
"actualAmount": "1592",
"subscriptionStatus": "ACTIVE",
"channelOrderNo": "CH2026031912000001",
"failCode": "",
"failMessage": ""
}
扣款失败示例(status=FAILED)
{
"notifyType": "PAYMENT",
"deductionNo": "DD01010120260319120000000002",
"subscriptionNo": "SP01010120260319120000000001",
"unifiedMemberNo": "UM_NEW_10001",
"outTradeNo": "MCH202603190001",
"status": "FAILED",
"periodNumber": "3",
"scheduledPeriodDate": "2026-06-19",
"originalAmount": "1990",
"actualAmount": "",
"subscriptionStatus": "SUSPENDED",
"channelOrderNo": "",
"failCode": "INSUFFICIENT_BALANCE",
"failMessage": "余额不足"
}
1.4.2 订阅类通知(notifyType=SUBSCRIPTION)
| 字段 | 类型 | 说明 |
|---|---|---|
notifyType |
string | 固定值 SUBSCRIPTION |
subscriptionNo |
string | 订阅号 |
unifiedMemberNo |
string | 统一会员编号 |
outTradeNo |
string | 商户签约订单号 |
productCode |
string | 内部产品编码 |
userId |
string | 用户标识 |
status |
string | 订阅状态(ACTIVE / CANCELLED / FAILED 等),见 §3.1 |
currentPeriod |
string | 当前期数 |
totalPeriod |
string | 总期数(永续时为空串) |
baseAmount |
string | 每期扣款基准金额(分) |
appliedPricingType |
string | 应用的定价类型,见 §3.6 |
nextDeductionTime |
string | 下次计划扣款时间 |
signTime |
string | 签约时间 |
activateTime |
string | 激活时间(未激活时为空串) |
cancelTime |
string | 取消时间(未取消时为空串) |
订阅签约成功示例(status=ACTIVE)
{
"notifyType": "SUBSCRIPTION",
"subscriptionNo": "SP01010120260319120000000001",
"unifiedMemberNo": "UM_NEW_10001",
"outTradeNo": "MCH202603190001",
"productCode": "MONTHLY_VIP",
"userId": "U10001",
"status": "ACTIVE",
"currentPeriod": "1",
"totalPeriod": "12",
"baseAmount": "1990",
"appliedPricingType": "FIRST_PERIOD_DISCOUNT",
"nextDeductionTime": "2026-07-19 10:00:00",
"signTime": "2026-06-19 10:00:00",
"activateTime": "2026-06-19 10:00:01",
"cancelTime": ""
}
订阅取消示例(status=CANCELLED)
{
"notifyType": "SUBSCRIPTION",
"subscriptionNo": "SP01010120260319120000000001",
"unifiedMemberNo": "UM_NEW_10001",
"outTradeNo": "MCH202603190001",
"productCode": "MONTHLY_VIP",
"userId": "U10001",
"status": "CANCELLED",
"currentPeriod": "3",
"totalPeriod": "12",
"baseAmount": "1990",
"appliedPricingType": "FIRST_PERIOD_DISCOUNT",
"nextDeductionTime": "",
"signTime": "2026-06-19 10:00:00",
"activateTime": "2026-06-19 10:00:01",
"cancelTime": "2026-08-15 14:30:00"
}
订阅失败示例(status=FAILED,签约首期支付失败)
{
"notifyType": "SUBSCRIPTION",
"subscriptionNo": "SP01010120260319120000000002",
"unifiedMemberNo": "UM_NEW_10001",
"outTradeNo": "MCH202603190003",
"productCode": "MONTHLY_VIP",
"userId": "U10001",
"status": "FAILED",
"currentPeriod": "0",
"totalPeriod": "12",
"baseAmount": "1990",
"appliedPricingType": "FIRST_PERIOD_DISCOUNT",
"nextDeductionTime": "",
"signTime": "2026-06-19 10:00:00",
"activateTime": "",
"cancelTime": ""
}
- 验签通过后处理业务,建议返回 HTTP 2xx。
- 幂等:建议以
deductionNo/subscriptionNo+ 业务终态关键字段(如status、periodNumber)组合去重;大类notifyType仅SUBSCRIPTION/PAYMENT/REFUND(预留)时须结合 Body 区分具体场景。 - 是否需要固定 JSON 体(如
{ "code": "SUCCESS" })以 通知中台回调规范 为准。