Appearance
支付回调幂等到底该怎么落地
先说结论
- 支付回调的第一目标不是“接收到通知”,而是“多次通知也只把业务改对一次”
- 幂等判断要落在本地数据库,不能只靠内存锁或单机标记
- 回调链路里只做必要的验签、落库、状态推进和事件投递,其他动作尽量异步化
一、支付回调为什么一定要按“重复到来”设计
第三方支付平台的回调天然就可能重复:
- 网络抖动后平台会重试
- 你回包慢了平台会重试
- 你处理成功了但响应没送达,平台也会重试
所以一条更接近真实生产的假设是:
- 同一笔支付通知会来多次
- 到达时间可能乱序
- 应用重启或故障恢复后还会再次处理
二、一个更稳的回调处理顺序
更推荐按这个顺序处理:
- 验签,确认通知确实来自支付平台。
- 校验商户号、订单号、金额和支付状态。
- 本地事务里更新支付流水和订单状态。
- 记录幂等键或状态版本。
- 事务提交后再投递“支付成功”事件。
这一步里最重要的是第 3 和第 4 步:业务状态推进和幂等判断必须一起落在本地事务里。
三、幂等键怎么选
常见做法有两种:
1. 支付平台交易号
适合一个平台交易号只对应一笔订单的场景。
2. 订单号 + 支付状态版本
适合你需要更明确控制订单状态推进,比如只允许:
WAIT_PAY -> PAID- 不允许
PAID -> PAID再重复发事件
不管选哪种,最终都应该让数据库帮你兜底,而不是靠代码里先查再改。
四、最容易踩的坑
1. 回调里做太多事
比如:
- 发券
- 加积分
- 通知仓储
- 刷新营销权益
- 调多个下游服务
这样一旦某个下游慢了,整个回调链路会越来越脆弱。更推荐把这些动作都改成异步事件消费。
2. 只做“先查有没有处理过”
并发下两个回调几乎同时到来时,单纯先查再改很容易穿透。要用唯一键、条件更新或状态机来兜底。
3. 只校验订单号,不校验金额和商户信息
这会留下明显的安全和数据一致性风险。
4. 已经更新订单了,却没可靠投递后续事件
这样会出现订单是已支付,但库存、发货、积分链路完全没收到通知的情况。
五、回调成功后最好还能补什么
- 支付回调成功率
- 重复回调次数
- 幂等命中次数
- 状态推进失败次数
- 事件投递失败次数
这些指标比“接口有没有 200”更能反映支付链路是不是真的稳。
总结
支付回调幂等的关键,不是挡住“第二次请求”,而是保证“无论来几次,都只把业务推进到正确状态一次”。把验签、状态推进、幂等落库和事件投递边界拆清后,支付链路会稳定很多。