为什么会出现重复支付问题?
用户提交订单时,如果设计不当可能导致用户可以重复提交两次订单,站在用户的角度,他并不知道系统的具体设计,极有可能按部就班的将两个订单都支付成功;或者说由于网络问题,第一次支付的订单没有及时返回成功支付的提示,导致用户认为第一笔订单失败了,又支付的一次。这两种情况最根本的问题是没有保证提交支付操作的幂等性。
如何解决?
解决幂等性的方案有很多种,我使用的是redis+token,并设置订单的超时时间。
实现流程?
-
创建订单时,创建一个唯一的token作为key存储在redis中,并设置key的超时时间做为订单的超时时间,最后将token返回给前端。
-
在订单提交支付的时候,后端需要判断前端传过来的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. 模拟重复提交订单操作
第一次:
后续请求:
要防止业务系统功能的重复操作,就必须要保证操作的幂等性:无论一个操作被重复执行多少次,对系统的影响都只能有一次,不能重复!