返回博客列表
后端工程2026年8月20日2 分钟阅读

支付与订单系统中的幂等、回调和最终一致性

从预下单、支付通知、主动查单到退款,梳理一条不能依赖前端成功页的交易状态链路。

#Go#MySQL#Redis#支付系统

支付链路的困难不在于发起一次 HTTP 请求,而在于任何一步都可能重复、延迟或乱序。用户重复点击、网关超时、回调重试和服务重启,都不能制造第二笔订单或让已支付订单回到待支付。

幂等键放在哪里

客户端为一次业务意图生成 Idempotency-Key,服务端以租户、接口和幂等键建立唯一约束。相同请求返回原结果,参数不同但幂等键相同则明确报冲突。

UNIQUE KEY uk_tenant_idempotency (tenant_id, endpoint, idempotency_key)

状态机只能单向推进

  • CREATED → PAYING:生成支付参数

  • PAYING → PAID:验签通知或主动查单确认

  • PAID → REFUNDING:创建退款请求

  • REFUNDING → REFUNDED:退款通知或查询确认

  • 任何终态都不接受旧事件回退

回调处理顺序

  1. 读取原始请求体并验证签名

  2. 校验商户、订单号、金额和币种

  3. 在事务内锁定订单并判断当前状态

  4. 写入支付记录和订单新状态

  5. 记录待发送领域事件

  6. 事务提交后快速返回成功

为什么还要主动查单

回调可能因为网络、网关或配置问题无法到达。订单长时间处于 PAYING 时,后台任务可以主动查询渠道状态。客户端轮询也只查询自己的服务端,不能直接把渠道响应当作本地订单状态。

退款不是反向支付

退款需要独立记录申请金额、渠道退款号、原因和状态。一笔支付可能发生部分退款,多次退款总额不能超过实付金额,所有状态变化必须可审计。

测试重点

  • 相同幂等键并发请求

  • 重复支付通知

  • 旧通知晚于新通知到达

  • 事务提交成功但事件发送失败

  • 退款金额累计越界

  • 渠道 401、429 与超时