如果一笔订单,用户在微信和支付宝同时支付,会怎么样?
- 标签:后端、场景题
🏛 业务架构师
用户在微信和支付宝同时支付同一笔订单(常见于收银台双开、页面卡顿重复发起),问题的本质是:渠道异步回调的并发竞态 + 资金不能收双份。我分三层回答。
第一层:状态机原子更新(只能成功一笔)
订单状态流转用条件更新串行化,回调处理前先原子抢占:
sql
UPDATE orders SET status='PAID', channel='WECHAT'
WHERE order_id=? AND status='WAIT_PAY';- 微信回调先到:抢占成功,订单已支付
- 支付宝回调后到:affected rows = 0,说明已有渠道成单——这笔不能确认成功,必须自动原路退款,绝不能因为"用户自己发起的"就吞下第二笔
第二层:防重与幂等
- 每个渠道的回调用支付流水号唯一索引保幂等,同一渠道重复回调直接返回成功(渠道要求返回成功否则无限重试)
- 两个渠道是不同流水号,唯一索引挡不住跨渠道,所以主防线必须是订单状态机
第三层:对账兜底(真实世界的主防线)
异步网络环境下,回调丢失、延迟、乱序都真实存在。每日与两个渠道的对账文件核对:
- 渠道成功 + 本地未支付 → 补单还是退款,按业务规则自动处理
- 双渠道都成功 → 自动退款第二笔,生成资损预警工单
业务视角收尾:这个场景的业务目标不是"技术上杜绝并发"(做不到,回调时机不受我们控制),而是无论竞态怎么发生,用户资金结果正确且尽量无感:快的那个成交,慢的那个秒级退款并短信告知"该订单已通过微信支付,支付宝款项已退回"。处理得好是无感细节,处理不好就是客诉和监管投诉。
🏗 系统架构师
这是典型的并发支付幂等问题。如果没做防护,最坏结果是:一笔订单被两个渠道各扣一次款,订单状态被覆盖,对账时两边流水对不上,资损+客诉。
根因:支付回调到达时先改订单状态再通知渠道,两个渠道的回调没有互斥,也不校验订单当前状态。
我的方案核心是"一个订单同一时刻只允许一笔支付有效",分层防:
收单侧防并发:支付成功回调处理前,先执行原子抢占——
UPDATE orders SET pay_status=paid, pay_channel=? WHERE order_no=? AND pay_status=unpaid,利用行锁 + 状态机条件更新,只有一方能更新成功, affected rows = 0 的那笔直接走原路退款。这是最后一道防线,必须有。发起侧防重复:用户在微信发起支付时,把订单与渠道绑定(Redis
SETNX order_no:channel+ 短 TTL),另一渠道发起时要么拦截提示"已有支付进行中",要么先关闭上一笔再放行。微信、支付宝都有关单接口,切渠道前先调关单。回调处理幂等:回调可能重复推送,处理逻辑以第三方流水号做幂等键,落一张支付流水表加唯一索引,重复回调直接返回成功。
兜底对账:每日与微信/支付宝侧对账文件核对,发现单边账自动触发退款或补单,并有运营介入入口。
架构上我强调一点:防线不能只建在一层,收单抢占是数据库层的硬保障,即使 Redis 挂了、前置校验被绕过也不会真正双扣款。
💻 全栈开发专家
结论:第二个渠道的回调会被状态机挡下,然后触发自动退款。前提是你把幂等和状态机做对了。
实现
- 订单状态条件更新(核心互斥):
sql
UPDATE orders SET status = 'PAID', pay_channel = 'WECHAT', trade_no = ?
WHERE order_no = ? AND status = 'PENDING';微信先回调成功,状态变 PAID;支付宝回调到来时 AND status='PENDING' 不满足,影响行数 0。
- 回调处理分支:
java
int updated = orderMapper.markPaid(...); // 条件更新
if (updated == 1) {
// 正常推进:发支付成功消息、扣库存
} else {
Order o = orderMapper.findByNo(orderNo);
if (o.isPaid() && !"ALIPAY".equals(o.getPayChannel())) {
refundService.autoRefund(o, alipayTradeNo); // 渠道不一致 -> 原路退回
}
// 已是本渠道已支付(重复回调)-> 直接返回成功,别让渠道重推
}要点
- 回调幂等:渠道流水号(transaction_id)建唯一索引,重复推送直接吸收。
- 退款是异步的,要记退款流水 + 定时对账,退款失败转人工。
- 极端情况"用户同时扫了两个码都在输密码",本质就是并发的两个回调,上面的行锁 + 条件更新天然串行化,不需要额外分布式锁。
踩过的坑 双渠道支付的根因常是前端重复拉起支付,产品层也该限制:已有待支付单时复用同一收银台订单,从源头减少。