支付的完整交易链路如何实现?
未标记
考查重点:交易状态、履约与对账。服务端核价建单,发起渠道支付,验签回调按幂等状态更新后履约;查询对账兜回调缺失与不确定结果
本题目录
参考答案
核心回答
支付链路要把“业务订单、支付渠道的扣款结果、后续履约”连接起来,并处理各环节之间的失败与重试。以电商订单为参考:服务端核价和创建订单,发起渠道支付,接收并验证渠道通知,通过幂等的状态变更确认收款,再触发库存确认和发货;未支付订单走查单与关单,已付款异常走补偿、退款和对账。
最关键的三个点是:支付结果以经过验证的渠道通知或服务端查单为依据;重复通知和并发请求只能产生一次业务效果;外部扣款与本地数据库无法靠一个本地事务包住,需要状态机和补偿把链路收敛。
从创建订单到支付成功
1. 服务端创建订单和支付记录
服务端重新计算商品、优惠、运费和应付金额,检查用户、商品与库存条件,保存订单明细及价格快照。金额使用约定的最小货币单位或准确的十进制表示,不直接信任客户端提交的总价。
业务订单号标识一次购买,支付单号标识一次支付尝试,渠道交易号标识渠道侧的交易。这几个标识各有用途,需要保存映射。重复点击支付时,先查已有的有效支付尝试,不能每次点击都无条件创建一笔新支付。
库存策略需要说清楚。例如下单先预占库存,付款后确认扣减,关闭订单后释放预占;采用这种策略时,支付成功不能再把同一份库存重复扣一次。也可以选择其他策略,但扣减、释放与超卖处理要相互一致。
2. 后端向渠道下单,客户端完成支付
后端使用渠道要求的身份与签名机制调用下单接口,保存商户支付单号、金额、渠道和请求结果,再把调起支付所需的信息返回客户端。例如微信 Native 支付由后端取得 code_url,前端展示二维码。浏览器跳转或客户端显示“支付完成”,只用于引导查询,不能直接把业务订单改成已付款。
调用渠道超时表示结果未知,可能渠道已受理但响应丢失。应根据渠道的幂等约定,使用原支付单号查单或重试,不能把超时直接当失败并另起支付。网络调用也不宜一直占着数据库行锁,等待外部系统返回。
3. 可靠接收通知,确认真实支付结果
回调入口先按渠道规范验签,并把已通过验证的通知可靠保存,再按约定及时应答,后续处理可异步完成。例如微信支付 API v3 回调需要验签,业务资源还需要解密;它的通知确认与订单业务处理可以分开。若持久化接收失败,就不能把尚未可靠接收的通知当作处理成功。
处理通知时核对商户身份、应用、商户支付单号、渠道交易号、交易状态、订单金额与币种。用户实付金额可能受渠道优惠影响,不能随便拿某个金额字段替代订单总金额。未收到回调、状态长时间未知时,主动查单;查单和回调进入同一套支付确认逻辑。
4. 本地确认收款,再可靠触发履约
在一个本地数据库事务内,利用支付单或业务操作的唯一约束、订单状态的条件更新,记录收款流水、把符合条件的订单推进为已付款,并写入待发送的履约事件。只有首次有效推进才产生相应业务效果;重复通知返回已处理结果,不能重复记账、发货或核销优惠券。
如果下一步需要消息队列,可以采用事务发件箱:订单更新与待发事件在同一事务中提交,再由后台投递。消息可能重复,下游仍按订单和业务动作去重。这样处理的是本地更新与消息发布之间的缺口,不会让支付渠道、数据库和所有下游自动拥有一个全局事务。
三类容易遗漏的异常
扣款成功,但本地没有收到结果。 订单保持待确认状态,依靠查单任务和账单对账补回结果。即使用户关闭页面,后台仍能完成确认和履约。不能因为一次回调丢失就认为支付失败。
订单超时关闭与用户付款并发。 定时任务不能仅根据本地倒计时就释放库存。需要协调渠道查单、关单结果和本地状态变更;若付款已经成功,转入支付确认。若本地已取消或库存已释放后才发现成功收款,需要按业务规则恢复履约,或进入异常退款,不能简单忽略成功通知。迟到的未支付或关闭结果也不能覆盖已确认的收款事实。
同一业务订单出现两笔真实成功支付。 本地幂等可以避免发两次货,却不能撤销渠道已经发生的第二笔扣款。需要记录两笔资金事实,识别多收款并退款或转人工处理。允许多次支付尝试时,还要限制同时有效的尝试,降低这种情况出现的机会。
退款、对账和故障定位如何闭环
退款有自己的商户退款单号、金额和状态。申请被受理不等于退款已经完成,要通过通知或查询确认结果,并校验累计退款金额与可退额度。业务订单、支付单、退款单可以分别建状态,避免用一个“成功/失败”字段承载所有含义。
对账比较业务订单、收款与退款流水、渠道账单,找出渠道已成功但本地未确认、重复收款、金额不符和退款未收敛等差异,再按差异类型补查或处理。修复过程仍走幂等的业务入口。
定位时能够用业务订单号串起支付单、渠道交易号、通知处理记录、查单与关单结果、履约事件。验证重点包括重复通知、回调与查单并发、响应丢失、接收后进程退出、消息重复,以及关单与付款竞争;接口正常返回并不足以证明这些场景下资金和履约仍一致。
常见追问
为什么不直接在回调里完成所有事情?
回调需要及时应答,发货、库存服务或其他下游可能慢或失败。可靠接收通知后异步推进,可减少渠道重试造成的压力;业务任务需要自己的重试和故障恢复,不能依赖已经确认成功的回调再次送达。
分布式锁能否保证支付幂等?
锁可以减少并发竞争,但锁过期、进程退出、重试和消息重放仍可能发生。最终业务效果应由持久化的唯一约束、状态条件与事务保证,不能只靠拿到一把锁。
支付成功后库存处理失败怎么办?
保留已收款事实,重试尚未完成的库存确认或履约步骤。若最终无法履约,按业务规则取消、退款并通知用户,而不是把已付款状态直接改回未付款。
参考资料
- 国内:JavaGuide:幂等性设计:借鉴唯一约束、业务状态与事务的说明,解释正文中重复支付通知只产生一次业务效果的实现边界,以及锁和一次性令牌的局限。
- 国内:微信支付:Native 支付开发指引:给出了后端下单、前端展示二维码、查单、关单、对账与退款的连接关系,用来说明参考支付链路各阶段的职责。
- 国内:微信支付:支付成功回调通知:参考普通支付通知的验签、解密、应答、重入与订单金额字段约定,解释正文中回调验证、可靠接收及主动查单的职责。
- 国外:Stripe:Webhooks:借鉴重复事件、乱序和及时应答的说明,解释正文中通知接收、异步幂等处理与后续履约的分工;事件字段仍以各渠道契约为准。
- 国外:AWS:Transactional outbox pattern:解释数据库提交与消息发布之间的双写缺口,以及事务发件箱的处理方式,用于支付确认后可靠触发履约;消费者仍需要处理重复消息。
