在这个支付幂等性的实现中,通过“一锁、二判、三更新”严格控制了支付链接生成接口的幂等性,确保同一业务单号在同一时间只会生成一个有效的支付链接,避免重复支付或其他意外操作。
@Facade
@DistributeLock(keyExpression = "#payCreateRequest.bizNo", scene = "GENERATE_PAY_URL")
public PayCreateResponse generatePayUrl(PayCreateRequest payCreateRequest) {
PayCreateResponse response = new PayCreateResponse();
PayOrder payOrder = payOrderService.create(payCreateRequest);
if (payOrder.getOrderState() != PayOrderState.TO_PAY) {
response.setPayOrderId(payOrder.getPayOrderId());
response.setPayUrl(payOrder.getPayUrl());
response.setSuccess(true);
return response;
}
PayChannelResponse payChannelResponse = doPay(payCreateRequest, payOrder);
if (payChannelResponse.getSuccess()) {
boolean updateResult = payOrderService.paying(payOrder.getPayOrderId(), payChannelResponse.getPayUrl());
Assert.isTrue(updateResult, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
response.setSuccess(true);
response.setPayOrderId(payOrder.getPayOrderId());
response.setPayUrl(payChannelResponse.getPayUrl());
} else {
response.setSuccess(false);
response.setResponseCode(payChannelResponse.getResponseCode());
response.setResponseMessage(payChannelResponse.getResponseMessage());
}
return response;
}
@Service
public class PayOrderService extends ServiceImpl<PayOrderMapper, PayOrder> {
private static final Logger logger = LoggerFactory.getLogger(PayOrderService.class);
@Autowired
private PayOrderMapper payOrderMapper;
public PayOrder create(PayCreateRequest payCreateRequest) {
PayOrder existPayOrder = payOrderMapper.selectByBizNoAndPayer(
payCreateRequest.getPayerId(),
payCreateRequest.getBizNo(),
payCreateRequest.getBizType().name(),
payCreateRequest.getPayChannel().name()
);
if (existPayOrder != null) {
if (existPayOrder.getOrderState() != PayOrderState.EXPIRED) {
return existPayOrder;
}
}
PayOrder payOrder = PayOrder.create(payCreateRequest);
boolean saveResult = save(payOrder);
Assert.isTrue(saveResult, () -> new BizException(RepoErrorCode.INSERT_FAILED));
return payOrder;
}
}
1. 一锁
在方法入口处使用了自定义注解 @DistributeLock
,并将锁的内容设置为业务单号 bizNo
。该锁是一个互斥锁,可以是分布式锁(如Redis锁)或数据库悲观锁。其作用是确保在同一时刻,同一个业务单号的请求只会有一个线程可以进行操作,其他请求会被阻塞,直到锁释放。这一步确保了多线程环境下的操作安全,防止多个请求同时生成支付链接。
2. 二判
在 PayOrderService.create
方法中实现了幂等性判断。首先,通过业务单号、付款方、支付类型和支付渠道查询数据库,检查是否已有相同条件的支付单存在。如果存在,且状态不为过期(如已支付、处理中等),则直接返回已有的支付单信息,包括支付链接等内容,不会重复生成新的支付单。这一判断基于数据库查询,可以通过唯一性索引、流水表或状态机来提高查询效率,防止重复操作。
3. 三更新
如果第二步判断不存在在途支付单,则创建一个新的支付单,并调用支付渠道生成支付链接。生成的支付链接会更新到支付单中,并将该支付单持久化到数据库。这一更新操作包括将生成的支付链接更新到数据库,同时返回给前端。这一步确保了新的支付单被正确保存,链接准确更新,从而完成一次新的支付操作。
总结
该方法通过“一锁、二判、三更新”三步流程有效实现了支付接口的幂等性控制:
- 加锁确保了同一业务单号在并发情况下的唯一性操作。
- 幂等性判断避免了重复生成支付单。
- 数据更新确保了新生成的支付链接能正确返回并持久化。
这种设计有效地防止了重复支付操作,并确保了系统的可靠性和数据的一致性。
支付幂等性是指在支付系统中,无论同一个支付请求被重复调用多少次,结果都应该是相同的,并且不会导致重复支付或数据不一致的问题。即在网络请求可能被多次触发的情况下,确保同一笔支付请求仅会成功一次,不会因为系统的重试或用户的重复点击而多次扣款或生成多个支付订单。
为什么支付幂等性很重要?
在支付场景中,由于网络波动、请求超时等原因,客户端或服务端可能会多次发送同一个支付请求。如果支付系统没有做幂等性处理,可能会出现以下问题:
- 多次扣款:用户因同一个请求被多次扣款,导致资金损失。
- 数据不一致:生成多个支付订单,导致订单状态、库存等数据混乱。
- 用户体验差:用户可能因重复支付或状态异常而产生疑惑,甚至失去信任。
如何实现支付幂等性?
实现支付幂等性通常包含以下几种方法:
-
唯一性标识:为每个支付请求生成一个唯一的业务单号(如订单号),并在每次支付请求中携带该标识。服务器可以根据该标识检查该支付是否已经处理过,避免重复处理。
-
幂等性锁:在支付操作前对订单加锁(如分布式锁或悲观锁),确保同一时间只有一个请求能成功执行,防止并发请求导致重复扣款。
-
状态检查:在执行支付操作前检查订单状态,如果订单已经支付成功或处于支付处理中,则直接返回支付结果,避免重复支付。
-
数据库唯一索引:通过数据库的唯一索引约束(如订单号)来防止重复插入支付订单。
总结
支付幂等性是支付系统中一个非常重要的特性,它通过一系列控制措施,确保同一支付请求即使被重复调用,也不会产生多次扣款、重复订单等问题,保障用户资金安全和数据一致性。