关于订单重复支付问题(幂等问题)的解决方案

109 阅读2分钟

为什么会出现重复支付问题?

用户提交订单时,如果设计不当可能导致用户可以重复提交两次订单,站在用户的角度,他并不知道系统的具体设计,极有可能按部就班的将两个订单都支付成功;或者说由于网络问题,第一次支付的订单没有及时返回成功支付的提示,导致用户认为第一笔订单失败了,又支付的一次。这两种情况最根本的问题是没有保证提交支付操作的幂等性。

如何解决?

解决幂等性的方案有很多种,我使用的是redis+token,并设置订单的超时时间。

实现流程?

  1. 创建订单时,创建一个唯一的token作为key存储在redis中,并设置key的超时时间做为订单的超时时间,最后将token返回给前端。

  2. 在订单提交支付的时候,后端需要判断前端传过来的key是否存在,如果存在则将其删除并执行后续代码;如果不存在表示订单超时或已被其他线程处理,直接返回错误信息。

流程图

相关代码

controller

/**
* 获取支付token
*/
@GetMapping("/paytoken")
public AjaxResult payToken(){
    return AjaxResult.success("操作成功",paymentFacade.payToken());

}

/**
* 支付
* @param paymentRequest
* @return
*/

@PostMapping("/pay")
public AjaxResult pay(@RequestHeader("token") String token, @RequestBody PaymentRequest paymentRequest) {
    try {
        PaymentResult result = paymentFacade.processPayment(token,paymentRequest);
        return AjaxResult.success(result);
    } catch (Exception e) {
        return AjaxResult.error(e.getMessage());
    }
}

业务代码Facade

/**
* 支付token,防止重复提交
* @return
*/
public String payToken(){
    String token = IdUtils.fastUUID();
    redisCache.setCacheObject(token,1,5, TimeUnit.MINUTES);
    return token;
}

/**
* 支付处理
* @param request
* @return
*/
@Transactional
public PaymentResult processPayment(String token, PaymentRequest request){
    Integer value = redisCache.getCacheObject(token);
    if(value == null){
        throw new ServiceException("请勿重复提交");
    }else{
        //删除token
        redisCache.deleteObject(token);
    }
    //省略后续代码
    ......
}

其中setCacheObject第三个参数表示key的过期时间

使用Postman工具测试

1. 获取订单token

2. 模拟重复提交订单操作

第一次:

后续请求:

要防止业务系统功能的重复操作,就必须要保证操作的幂等性:无论一个操作被重复执行多少次,对系统的影响都只能有一次,不能重复!