背景
负责了一个支付相关的功能,因为我们的支付渠道有很多,有的时候用户在选择某一个支付渠道支付(如微信)之后一直处理中,然后用户又换了一个其他的支付渠道(如支付宝)来支付。但是后面出现了两个支付都成功了的问题。但是我们的系统上没有针对这种情况做特殊处理,就导致用户多付了钱。
技术选型
关于支付的并发问题,其实有几种方案,最简单的就是用户在某个支付渠道支付中的时候,不让他用其他的渠道再次支付,直到上一个渠道支付结果明确的成功或者失败。
这时候就需要给订单加锁,最开始我们就是这么做的。实现方式就是在每一个订单上,我们加了一个payStatus,payChannel和payStreamNo三个字段,当用户用某个渠道开始支付的时候:
payStatus = PAYING
payChannel = WECHAT
payStreanNo = NULL
这时候,如果微信支付的结果还没有返回的话,那么用户想要用支付宝再次针对这笔订单支付的时候会失败,因为payStatus = PAYING并且payChannel != ALIPAY ,所以会提示支付中。
一直到微信返回结果之后,我们再根据支付结果尝试继续处理:
支付成功:
payStatus = PAID
payChannel = WECHAT
payStreanNo = xxxxxxxxxxxxxx
支付失败
payStatus = FAILED
payChannel = NULL
payStreanNo = NULL
但是,上面这个整体逻辑还存在一个问题,那就是当我们有并发请求,可能有两笔支付请求在检查payStatus的时候,发现都是INIT或者FAILED,那么这两个请求就都可以唤起支付渠道。
这个情况,在支付的时候比较少,因为支付都是用户自己操作的,问题不大,但是有一些场景可能会存在,比如说有那种花呗还款的场景,这时候就可能会有用户的主动还款,和系统的自动扣款。
那么这时候就可能会发生上述的并发情况,那么就需要想办法解决这个问题。
你做了什么
这个地方其实主要就是并发情况下的一个幂等问题,所以为了解决并发的问题,我这里引入了一个分布式锁,在开始进行支付操作时,尝试添加分布式锁,加锁成功,再去判断payStatus。如果加锁失败,说明当前有并发请求,那么就失败掉。
伪代码如下:
// 一锁:先加一个分布式锁
@DistributeLock(scene = "PAY", keyExpression = "#request.orderId", expire = 3000)
public PayResponse apply(PayRequest request) {
PayResponse response = new PayResponse();
// 二判:判断请求是否执行成功过
OrderDTO orderDTO = orderService.queryOrder(request.getProduct(), request.getIdentifier());
if (orderDTO != null && orderDTO.getPayStatus() == PayStatus.PAYING) {
response.setSuccess(false);
response.setResponseCode("PAYING");
return response;
}
// 三更新:执行更新的业务逻辑
return orderService.applyPay(request);
}