Skip to content

背景

负责了一个支付相关的功能,因为我们的支付渠道有很多,有的时候用户在选择某一个支付渠道支付(如微信)之后一直处理中,然后用户又换了一个其他的支付渠道(如支付宝)来支付。但是后面出现了两个支付都成功了的问题。但是我们的系统上没有针对这种情况做特殊处理,就导致用户多付了钱。

技术选型

关于支付的并发问题,其实有几种方案,最简单的就是用户在某个支付渠道支付中的时候,不让他用其他的渠道再次支付,直到上一个渠道支付结果明确的成功或者失败。

这时候就需要给订单加锁,最开始我们就是这么做的。实现方式就是在每一个订单上,我们加了一个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。如果加锁失败,说明当前有并发请求,那么就失败掉。

伪代码如下:

java
// 一锁:先加一个分布式锁
@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);
}