跳到主要内容

支付系统设计

问题

如何设计一个可靠的支付系统?

答案

支付流程

核心表设计

支付单表
CREATE TABLE payment_order (
id BIGINT PRIMARY KEY,
payment_no VARCHAR(64) UNIQUE, -- 支付单号
order_no VARCHAR(64), -- 关联订单号
channel VARCHAR(32), -- 支付渠道(WECHAT/ALIPAY)
amount DECIMAL(10,2), -- 支付金额
status TINYINT, -- 0待支付 1支付中 2已支付 3已退款
third_party_no VARCHAR(128), -- 第三方流水号
callback_time DATETIME,
created_at DATETIME,
INDEX idx_order (order_no)
);

幂等处理

支付回调幂等
@Transactional
public void handleCallback(PaymentCallback callback) {
PaymentOrder payment = paymentMapper.findByPaymentNo(callback.getPaymentNo());

// 幂等:已处理过的直接返回成功
if (payment.getStatus() == PaymentStatus.PAID) return;

// CAS 更新状态:待支付 → 已支付
int rows = paymentMapper.updateStatus(
payment.getId(), PaymentStatus.UNPAID, PaymentStatus.PAID
);
if (rows == 0) return; // 并发场景下已被其他线程处理

// 通知订单服务
orderService.onPaymentSuccess(payment.getOrderNo());
}

对账系统

每日对账流程
@Scheduled(cron = "0 0 1 * * ?")  // 每天凌晨 1 点
public void dailyReconciliation() {
LocalDate yesterday = LocalDate.now().minusDays(1);

// 1. 拉取第三方支付账单
List<ThirdPartyBill> thirdBills = paymentChannel.downloadBill(yesterday);

// 2. 查询本地支付记录
List<PaymentOrder> localOrders = paymentMapper.findByDate(yesterday);

// 3. 对比差异
Map<String, ThirdPartyBill> thirdMap = thirdBills.stream()
.collect(Collectors.toMap(ThirdPartyBill::getPaymentNo, Function.identity()));

for (PaymentOrder local : localOrders) {
ThirdPartyBill third = thirdMap.remove(local.getPaymentNo());
if (third == null) {
// 本地有但第三方没有 → 可能支付失败未回调
handleLocalExtra(local);
} else if (!local.getAmount().equals(third.getAmount())) {
// 金额不一致 → 告警人工处理
alertService.sendAlert("金额不一致: " + local.getPaymentNo());
}
}
// thirdMap 中剩余的:第三方有但本地没有 → 可能漏单
thirdMap.values().forEach(this::handleThirdExtra);
}

常见面试问题

Q1: 如何保证支付不重复扣款?

答案

  • 唯一支付单号:同一订单只能创建一个支付单
  • 幂等回调:用支付单号做幂等键,CAS 更新状态
  • 对账兜底:每日对账发现异常自动或人工处理

Q2: 退款怎么设计?

答案

  • 创建退款单关联原支付单
  • 调用第三方退款接口
  • 退款也是异步回调,需要幂等处理
  • 部分退款记录已退金额,总退款 ≤ 原支付金额

Q3: 支付掉单怎么处理?

答案

  • 主动查询:定时任务扫描超时未回调的支付单(如 5 分钟),主动调第三方查询接口
  • 补偿回调:查到已支付则补充执行后续逻辑
  • 对账兜底:T+1 对账修复遗漏

相关链接