统一交易通知
统一交易通知 default_notify_url
通知结果参数列表 通知URL 是默认配置的全局通知URL,交易完成时平台会把相关交易和表单信息发送到该URL,需要接收处理信息。
通知优先级:notify_url(订单级)> default_notify_url(全局级)
- 如果订单请求中传递了
notify_url,则仅通知该URL,不会通知default_notify_url - 如果订单请求中未传递
notify_url,则通知default_notify_url
对后台通知交互时,如果平台收到服务商的应答不是纯字符串success或超过5秒后返回时,平台认为通知失败,平台会通过一定的策略(通知频率为0/15/15/30/180/1800/1800/1800/1800/3600,单位:秒)间歇性重新发起通知,尽可能提高通知的成功率,但不保证通知最终能成功。 由于存在重新发送后台通知的情况, 因此同样的通知可能会多次发送给服务商系统。服务商系统必须能够正确处理重复的通知。 推荐的做法是, 当收到通知进行处理时, 首先检查对应业务数据的状态, 判断该通知是否已经处理过, 如果没有处理过再进行处理, 如果处理过直接返回结果成功。 在对业务数据进行状态检查和处理之前, 要采用数据锁进行并发控制, 以避免函数重入造成的数据混乱。 特别注意:服务商后台接收到通知参数后,要对接收到通知参数里的订单号pay_trade_no和订单金额total_amount和自身业务系统的订单和金额做校验,校验一致后才更新数据库订单状态 后台通知通过请求中的notify_url进行, post方式给服务商系统(通知参数内容为json格式的字符串)
通知参数
| 参数 | 必填 | 类型 | 说明 | 示例值 |
|---|---|---|---|---|
| code | 是 | String(32) | 交易结果 000000 表示成功 | 000000 |
| pay_trade_no | 是 | String(32) | 支付凭证中的商户单号 | 202401010001 |
| transaction_id | 是 | String(32) | 支付凭证中交易单号 | 420000202401010001 |
| merchant_no | 是 | String(32) | 商户号 | 1234567890 |
| payment_method | 是 | String(32) | 支付方式 WECHAT-微信 ALIPAY-支付宝 UNIONPAY-云闪付 | |
| openid | 否 | String(32) | 微信用户openid 或者支付宝buyer_id | oABC1234567890 |
| trade_state | 是 | String(32) | 交易状态 SUCCESS-支付成功 REFUND-转入退款 NOTPAY-未支付 CLOSED-已关闭 | SUCCESS |
| total_amount | 是 | String(32) | 订单总金额 单位为分 | 100 |
| pay_time | 否 | String(32) | 支付时间 yyyyMMddHHmmss | 20260502012256 |
| remark | 否 | String(256) | 备注信息 | 交易备注 |
| form_info | 否 | Object | 表单信息 {"name":"姓名", "mobile":"手机号"} | {"name":"张三", "mobile":"13800138000"} |
| nonce_str | 是 | String(32) | 随机字符串 | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS |
| timestamp | 是 | String(32) | 时间戳 Unix时间戳(秒) | 1704067200 |
| sign_type | 否 | String(32) | 签名类型 | RSA2 |
| sign | 否 | String(512) | 签名 | 使用平台提供的公钥进行签名验证 |
通知示例
支付成功通知(未启用自定义表单)
{
"code": "000000",
"pay_trade_no": "202401010001",
"transaction_id": "420000202401010001",
"merchant_no": "1234567890",
"payment_method": "WECHAT",
"openid": "oABC1234567890",
"trade_state": "SUCCESS",
"total_amount": "100",
"pay_time": "20260502012256",
"remark": "交易备注",
"nonce_str": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"timestamp": "1704067200",
"sign_type": "RSA2",
"sign": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
}
支付成功通知(启用自定义表单)
{
"code": "000000",
"pay_trade_no": "202401010002",
"transaction_id": "420000202401010002",
"merchant_no": "1234567890",
"payment_method": "ALIPAY",
"openid": "20881021234567890",
"trade_state": "SUCCESS",
"total_amount": "200",
"pay_time": "20260502013045",
"form_info": {"name":"张三", "mobile":"13800138000", "project":"项目A"},
"remark": "扫码支付",
"nonce_str": "7N9375JLMEID27DR3613TJ9ZOMUN78WT",
"timestamp": "1704067800",
"sign_type": "RSA2",
"sign": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8B..."
}
说明:
form_info字段仅在启用自定义表单时返回,包含用户扫码时填写的信息form_info为 JSON 对象,字段由商户在申请二维码时自定义
通知响应
服务商接收到通知后,需要返回纯字符串 success 表示接收成功,否则平台会认为通知失败并重新发送。
成功响应
success
失败响应
fail
安全注意事项
1. 签名验证(必须)
所有通知都必须进行签名验证,以确保数据来自平台且未被篡改:
- 从通知参数中获取
sign_type和sign字段 - 将除
sign外的所有参数按字母顺序排序 - 使用平台提供的公钥验证签名
- 签名验证失败的通知必须拒绝处理
2. 金额校验(必须)
必须校验通知金额与业务订单金额是否一致:
- 提取通知中的
pay_trade_no和total_amount - 从业务数据库查询对应订单的原始金额
- 金额不一致时拒绝处理并记录异常
- 金额单位为分,注意单位转换
3. 重复通知处理
平台可能会多次发送同一通知,必须实现幂等处理:
# 伪代码示例
def handle_notify(notify_data):
# 1. 验证签名
if not verify_sign(notify_data):
return "fail"
# 2. 查询订单状态
order = get_order(notify_data['pay_trade_no'])
# 3. 检查是否已处理
if order.status == 'PAID':
return "success" # 已处理,直接返回成功
# 4. 校验金额
if order.amount != notify_data['total_amount']:
log_error("金额不一致")
return "fail"
# 5. 使用数据库事务处理(防并发)
with transaction:
order = get_order_for_update(notify_data['pay_trade_no'])
if order.status == 'PAID':
return "success" # 双重检查
# 6. 更新订单状态
order.status = 'PAID'
order.transaction_id = notify_data['transaction_id']
order.pay_time = notify_data['pay_time']
order.save()
return "success"
4. 并发控制
必须使用数据库锁或分布式锁防止并发处理同一通知:
- 推荐使用数据库行锁(
SELECT ... FOR UPDATE) - 或使用 Redis 分布式锁(锁 key 为
pay_trade_no) - 锁超时时间建议设置为 10-30 秒
5. 响应要求
- 收到通知后必须尽快响应(建议 2 秒内)
- 响应必须是纯字符串
success或fail - 不要返回 JSON 或 HTML,否则平台会认为通知失败
- 业务处理可以异步进行,但响应必须同步返回
最佳实践
1. 日志记录
记录所有通知的完整信息,便于问题排查:
log.info(f"收到通知: {notify_data}")
log.info(f"订单号: {pay_trade_no}, 金额: {total_amount}")
log.info(f"签名验证: {'成功' if sign_valid else '失败'}")
log.info(f"处理结果: {result}")
2. 异常处理
- 通知处理失败时返回
fail,平台会重新发送 - 记录详细的错误信息到日志
- 设置告警机制,监控通知处理失败率
3. 自定义表单数据处理
如果启用了自定义表单(enable_custom_form=true):
form_info中的字段由商户自定义,可能是用户信息、项目名称等- 根据业务需要存储
form_info数据 - 建议将
form_info存储为 JSON 格式,便于后续查询
4. 测试建议
- 上线前使用沙箱环境充分测试
- 测试各种场景:支付成功、失败、重复通知、金额不一致等
- 验证签名验证逻辑的正确性
- 测试并发场景下的幂等性
5. 常见问题
Q: 为什么收到重复通知?
A: 如果响应超时或未返回 success,平台会重试。检查响应逻辑是否正确。
Q: 签名验证失败怎么办?
A: 确认使用了正确的公钥,参数排序是否正确,不要遗漏任何参数。
Q: 如何处理异步业务逻辑?
A: 先快速返回 success,然后将业务处理放入消息队列异步执行。