✅一个订单,在11:00超时关闭,但在11:00也支付成功了,怎么办?

典型回答

假如,有一笔订单,在10:00下单成功,超时时间是1小时,那么在11点的时候,支付成功了,这时候该如何处理?

明确终态

这是一个比较常见的一个并发处理的问题,而且也是业务中比较常见的问题,一个简单的支付单的状态机如下:

7dd5915a623f11535c1e906f716f900f.svg

在"支付中"的一笔支付单 ,是有可能推进到支付成功的状态,并且也可能推进到已取消的状态的。

一般来说,正常的支付业务中,支付成功和已取消,都应该是终态,也就是状态机中的最终状态,终态是不能再变化的。 如果一个模型没有明确的终态,或者已经终态的终态数据状态还能随便变化,那么他的设计一定是不合理的。

状态流转控制

那么,如果刚好在同一时刻,同一笔支付单同时来了一个支付成功的消息,和一个超时关闭的请求,该如何处理呢?

首先,我们要做的就是状态机的校验,在我们的支付成功处理和超时关闭的处理过程中,需要做状态的判断,只有支付中的状态才能执行这两个动作。并且数据库的update语句也需要做控制,即:

update pay_order set status = "PAY_SUCCESS",lock_version = lock_version + 1 where pay_order_no = #{parOrderNo} and status = "PAYING" and lock_version  = #{lock_version}


update pay_order set status = "PAY_EXPIRED",lock_version = lock_version + 1 where pay_order_no = #{parOrderNo} and status = "PAYING" and lock_version  = #{lock_version}

这时候在发生并发时,就可以确保一个payorder,要么被推进到PAYSUCCESS状态,要么被推进到PAY_EXPIRED状态,会且只会发生其中的一种情况。

那有一个成功了,就有一个会失败。这是必然的,这时候就有两种情况了:

1、支付成功处理成功,支付超时处理失败

2、支付超时处理成功,支付成功处理失败

这两种情况如何处理呢?

逆向流程

先说简单的情况,假如支付成功处理成功,支付超时处理失败,这种其实没啥问题,因为已经支付成功了,超时的请求直接拒绝掉就行了。这是业务上正常的逻辑。

第二种情况就不好处理了,因为对于支付超时处理成功了,但是支付成功处理失败这种,我们就需要考虑,钱怎么办?

用户把钱付完了,但是支付却没成功,这肯定是业务上接受不了的。那这时候怎么办呢?

办法就是:原路退回

当出现这种情况的时候,我们是可以识别出来的,也就是说在支付成功的处理过程中,如果发现支付单被关闭了,那么就触发原路退回的流程,把钱再给用户退回去。

为啥非要退款?而不是让订单推进到成功,或者再补一个支付单。

一方面,状态机中已取消一定是一个终态,终态再流转到其他状态不合理。

另一方面,在订单超时的业务逻辑中,可能直接把库存退回去了,营销券也释放了,那么这时候补一个支付单是不现实的。

并且这个过程需要考虑,如果退回失败了怎么办?

资金恒等式

为了保证方案的完整性,就需要在支付单上多记录一些金额,比如支付金额、冲退金额。当支付单的状态为PAY_SUCCESS时,需要满足以下恒等式

支付金额 > 0 ;冲退金额=0

当支付单的状态为PAY_EXPIRED时,需要满足以下恒等式:

支付金额-冲退金额 = 0

只有这样,我们才能确保,这笔单子,要么用户付了钱,支付成功。要么支付失败,钱没付或者钱付了之后退回去了。

那我们就需要引入对账机制,不断的做资金恒等式的校验,发现不一致的时候,需要进行对齐操作,即退款重试。直到成功为止。

分布式锁

上面的方案大致差不多了,如果再做得好一点的话,可以在进入支付成功/支付超时的业务逻辑操作之前,先抢一下这笔支付单的锁,在前面就把并发控制好。没抢到锁的就失败,下次重试。

这样可以大大的降低并发冲突,避免出现异常,及很多线上的报错。

原文: https://www.yuque.com/hollis666/xkm7k3/vwwc639702l84g4g