如果不让你用看门狗,你会用什么方案来解决重复支付问题?

👌如果不让你用看门狗,你会用什么方案来解决重复支付问题?

1. 幂等性令牌(Idempotency Token)

原理:在每次支付请求中,客户端生成一个唯一的幂等性令牌,并将其发送给服务器。服务器在处理支付请求时,会检查这个令牌是否已经被使用过。如果令牌已经被使用,服务器会拒绝重复的支付请求。

实现步骤

  1. 客户端生成一个唯一的幂等性令牌(如 UUID)。
  2. 客户端将令牌和支付请求一起发送给服务器。
  3. 服务器检查令牌是否已经存在于数据库中。
  4. 如果令牌不存在,服务器处理支付请求并将令牌存储在数据库中。
  5. 如果令牌已经存在,服务器拒绝支付请求或者返回之前的处理结果。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PaymentService {

private PaymentRepository paymentRepository;

public PaymentService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}

public void processPayment(PaymentRequest request) throws DuplicatePaymentException {
String antiReplayToken = request.getAntiReplayToken();

if (paymentRepository.existsByAntiReplayToken(antiReplayToken)) {
throw new DuplicatePaymentException("Duplicate payment detected for anti-replay token: " + antiReplayToken);
}

// 处理支付逻辑
Payment payment = new Payment();
payment.setAntiReplayToken(antiReplayToken);
payment.setAmount(request.getAmount());
payment.setStatus("processing");

paymentRepository.save(payment);

// 完成支付后更新状态
payment.setStatus("completed");
paymentRepository.save(payment);
}
}

2. 数据库唯一约束(不推荐)

原理:在数据库中为每个支付请求生成一个唯一的交易 ID,并在数据库中设置唯一约束(Unique Constraint)来防止重复记录。

实现步骤

  1. 在支付请求中生成一个唯一的交易 ID。
  2. 在数据库中保存支付记录时,使用交易 ID 作为唯一键。
  3. 如果尝试插入重复的交易 ID,数据库会抛出唯一约束异常,从而防止重复支付。

示例代码(伪代码):

1
2
3
4
5
6
7
8
9
CREATE TABLE payments (
id SERIAL PRIMARY KEY,
transaction_id VARCHAR(255) UNIQUE NOT NULL,
amount DECIMAL(10, 2),
status VARCHAR(50)
);

-- 插入支付记录时
INSERT INTO payments (transaction_id, amount, status) VALUES ('unique_transaction_id', 100.00, 'pending');

3. 分布式锁

原理:使用分布式锁来确保同一时间只有一个支付请求能够被处理。可以使用 Redis、ZooKeeper 等工具实现分布式锁。

实现步骤

  1. 在处理支付请求前,尝试获取分布式锁。
  2. 如果获取锁成功,处理支付请求。
  3. 如果获取锁失败,拒绝支付请求或等待重试。
  4. 支付处理完成后,释放分布式锁。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

@Autowired
private RedissonClient redissonClient;

@Autowired
private PaymentRepository paymentRepository;

public void processPayment(PaymentRequest request) throws DuplicatePaymentException {
String transactionId = request.getTransactionId();
RLock lock = redissonClient.getLock("payment_lock_" + transactionId);

try {
if (lock.tryLock(10, 10, TimeUnit.SECONDS)) {
if (paymentRepository.existsByTransactionId(transactionId)) {
throw new DuplicatePaymentException("Duplicate payment detected for transaction ID: " + transactionId);
}

// 处理支付逻辑
Payment payment = new Payment();
payment.setTransactionId(transactionId);
payment.setAmount(request.getAmount());
payment.setStatus("processing");

paymentRepository.save(payment);

// 完成支付后更新状态
payment.setStatus("completed");
paymentRepository.save(payment);
} else {
throw new RuntimeException("Could not acquire lock for transaction ID: " + transactionId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Lock acquisition interrupted", e);
} finally {
lock.unlock();
}
}
}

4. 事务和锁机制

原理:使用数据库事务和锁机制来确保支付操作的原子性和一致性。

实现步骤

  1. 在数据库中为支付操作创建一个事务。
  2. 在事务中获取支付记录的锁,防止其他事务同时修改相同的记录。
  3. 执行支付操作并提交事务。
  4. 如果在支付操作中发生冲突,回滚事务并重试。

5. 状态机

原理:使用状态机来管理支付请求的状态,确保每个支付请求只能从一个状态转移到另一个状态,避免重复处理。

实现步骤

  1. 定义支付请求的状态(如pendingprocessingcompletedfailed)。
  2. 在处理支付请求时,根据当前状态进行状态转移。
  3. 使用数据库事务确保状态转移的原子性。

总结

以上方法都可以有效地防止重复支付,具体选择哪种方案取决于系统的架构和需求。幂等性令牌和数据库唯一约束是比较常见且易于实现的方法,而分布式锁和状态机适用于更复杂的分布式系统。无论选择哪种方案,都需要确保支付操作的幂等性和一致性。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/cf41s0446a6gokgr

 wechat
天生我才必有用