一笔订单,在取消的那一刻用户刚好付款了,怎么办?

Sherwin.Wei Lv7

一笔订单,在取消的那一刻用户刚好付款了,怎么办?

回答重点

这种情况在正常的业务场景中是有可能出现的,因为订单都会有定时取消的逻辑,比如 10 分钟或者 15分钟,而用户刚好卡在这个时间点进行付款,此时就会出现两种情况:

1)用户支付成功,支付回调的那一刻支付单刚好还没取消,而等回调结束,取消支付单的事务提交,支付单取消。此时用户扣款了,但是对应的权益或资产没了。

image.png

2)用户支付成功,支付回调的那一刻支付单已经被取消。但此时用户已经扣款,东西却没了。

image.png

可以看到,不论是哪种情况,其实都需要做一定的处理,不然用户肯定会来投诉!

这种场景无非就是支付单支付成功和取消两种状态的“争夺”,正常情况下,订单或者支付单都会有状态机的存在,在当前场景简单来说有以下两条路径:

1)待支付->支付中->支付成功

2)待支付->支付中->已取消

针对情况 1 ,如果是支付回调取胜,此时的状态应该已从 支付中->支付成功
针对情况 2 ,如果是取消支付单取胜,此时的状态应该已从 支付中->已取消

所以我们在修改支付单状态的时候,基于原始状态的判断,就可以做正常的处理,来看下 SQL 应该就很清晰了:

1
2
3
4
5
6
# 支付成功
update pay_info set status = 'paySuccess' where orderNo = '1' and status = 'paying';

# 取消
update pay_info set status = 'cancel' where orderNo = '1' and status = 'paying';

重点就是我们加了 status = 'paying' 这个条件,这就能保证个情况只有一个能成功,另一个一定失败。

1)假设情况 1 成功了,此时用户已经成功付款,那么 status 已经变为 paySuccess,取消的 SQL 必定执行失败,此时就让它失败,不需要做任何别的处理。

image.png

2)假设情况 2 成功了,此时订单已被取消,status 已经变为 cancel,支付成功的 SQL 必定执行失败,这种情况下我们就需要做逆向处理,即给用户退款 。订单被取消,用户的钱也被原路退回,这种处理也没任何问题。

image.png

业务小优化

针对订单超时业务,这里在业务上可以做一个小优化,你想想,用户付款前可能有点挣扎,然后在最后一刻终于下定决心进行付款,这时候却告知被退款了,用户很可能就不会再下单了。因此我们在页面上可以限时订单取消倒计时为 10 分钟,但实际后端是延迟 11 分钟取消订单,这样就能避免这种情况的发生啦。

Redis 分布式锁实现

最后除了利用数据库处理,还可以使用分布式锁,对一笔订单加锁也能保证这笔订单正常的业务流转。

每次进行取消订单付款操作时,首先尝试获取订单的分布式锁,确保只有一个操作能修改订单状态。

在分布式系统中,订单在取消的同时用户付款的竞态问题可以通过分布式锁来解决。以下是一个具体的、落地的方案,确保订单状态的可靠性,避免因并发导致状态冲突:

订单取消流程

1)超时触发取消订单

2)取消订单方法中先获取该订单的分布式锁。如果锁被其他操作持有(如付款),等待或抛出异常。

若成功获取锁,检查订单状态是否已付款:

  • 若订单未付款,将订单状态更新为“已取消”。
  • 若订单已付款,直接跳过这笔订单的处理。
  • 释放分布式锁,完成取消流程。

订单付款流程

1)三方支付成功回调。

2)后端系统接收回调后,先获取该订单的分布式锁,如果锁被其他操作持有(如取消),等待或抛出异常(没有给三方响应成功,三方会重新发起回调)。

若成功获取锁,检查订单状态是否为“待支付”:

  • 若订单状态为“待支付”,继续执行扣款,并将订单状态更新为“已付款”。
  • 若订单状态为“已取消”,则发起退款,并提示用户订单已取消,无法支付。
  • 释放分布式锁,完成流程。

扩展知识

Comments