防止错付和多付是支付系统的关键,以下是Java调用第三方支付接口的防护措施:
一、核心防护策略
1. 幂等性设计
@Service
public class PaymentService {
// 使用数据库唯一索引或分布式锁
@Transactional
public PaymentResult pay(PaymentRequest request) {
// 1. 生成唯一幂等键
String idempotentKey = generateIdempotentKey(request);
// 2. 检查是否已处理
PaymentRecord record = paymentRecordDao.selectByIdempotentKey(idempotentKey);
if (record != null && record.getStatus() == PaymentStatus.SUCCESS) {
return PaymentResult.duplicate(record);
}
// 3. 插入幂等记录(数据库唯一约束防止重复)
try {
paymentRecordDao.insertIdempotentRecord(idempotentKey, PaymentStatus.PROCESSING);
} catch (DuplicateKeyException e) {
throw new BusinessException("重复支付请求");
}
// 4. 执行业务逻辑
return doPay(request, idempotentKey);
}
private String generateIdempotentKey(PaymentRequest request) {
return String.format("PAY:%s:%s:%s",
request.getMerchantId(),
request.getOrderNo(),
request.getAmount()
);
}
}
2. 金额校验机制
@Component
public class AmountValidator {
public void validatePayment(PaymentRequest request) {
// 1. 前置校验
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new ValidationException("金额必须大于0");
}
// 2. 金额范围限制
if (request.getAmount().compareTo(new BigDecimal("1000000")) > 0) {
throw new ValidationException("单笔支付金额超过上限");
}
// 3. 与订单金额对比
Order order = orderService.getOrder(request.getOrderNo());
if (order.getAmount().compareTo(request.getAmount()) != 0) {
log.warn("支付金额与订单金额不一致: order={}, pay={}",
order.getAmount(), request.getAmount());
throw new ValidationException("支付金额不匹配");
}
// 4. 小数位数校验
if (request.getAmount().scale() > 2) {
throw new ValidationException("金额小数位数最多2位");
}
}
}
二、并发控制
3. 分布式锁控制
@Service
public class ConcurrentPaymentService {
@Autowired
private RedissonClient redissonClient;
public PaymentResult safePay(PaymentRequest request) {
String lockKey = "PAY_LOCK:" + request.getOrderNo();
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待5秒,持有30秒
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
return processPayment(request);
} else {
throw new BusinessException("支付处理中,请稍后");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("支付处理被中断");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
三、支付流程控制
4. 状态机管理
@Component
public class PaymentStateMachine {
private StateMachine<PaymentState, PaymentEvent> stateMachine;
@Transactional
public PaymentResult processPayment(String orderNo, PaymentEvent event) {
// 1. 获取当前状态
PaymentState currentState = getCurrentState(orderNo);
// 2. 状态转移校验
if (!canTransition(currentState, event)) {
throw new IllegalStateException("状态转移非法");
}
// 3. 发送状态事件
boolean accepted = stateMachine.sendEvent(
MessageBuilder.withPayload(event)
.setHeader("orderNo", orderNo)
.build()
);
if (!accepted) {
throw new BusinessException("支付状态更新失败");
}
}
private boolean canTransition(PaymentState from, PaymentEvent event) {
// 定义允许的状态转移
Map<PaymentState, Set<PaymentEvent>> transitions = Map.of(
PaymentState.INIT, Set.of(PaymentEvent.PAY_REQUEST),
PaymentState.PROCESSING, Set.of(PaymentEvent.PAY_SUCCESS, PaymentEvent.PAY_FAIL),
PaymentState.SUCCESS, Set.of(PaymentEvent.REFUND_REQUEST) // 成功后只能退款
);
return transitions.getOrDefault(from, Collections.emptySet()).contains(event);
}
}
四、对账与监控
5. 定时对账任务
@Component
@Slf4j
public class ReconciliationJob {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void dailyReconciliation() {
// 1. 查询当天所有支付记录
List<PaymentRecord> localRecords = paymentDao.getTodayRecords();
// 2. 从第三方获取对账单
List<ThirdPartyRecord> thirdPartyRecords = thirdPartyService.getDailyBill();
// 3. 比对差异
ReconciliationResult result = reconcile(localRecords, thirdPartyRecords);
if (!result.isBalanced()) {
// 4. 告警并处理差异
alertService.sendAlert(result.getDiscrepancies());
handleDiscrepancies(result.getDiscrepancies());
}
// 5. 记录对账结果
reconciliationDao.saveResult(result);
}
private ReconciliationResult reconcile(List<PaymentRecord> local,
List<ThirdPartyRecord> thirdParty) {
ReconciliationResult result = new ReconciliationResult();
// 金额汇总比对
BigDecimal localTotal = local.stream()
.map(PaymentRecord::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal thirdPartyTotal = thirdParty.stream()
.map(ThirdPartyRecord::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
result.setLocalTotal(localTotal);
result.setThirdPartyTotal(thirdPartyTotal);
// 找出差异记录
findDiscrepancies(local, thirdParty, result);
return result;
}
}
6. 支付监控
@Component
@Slf4j
public class PaymentMonitor {
@EventListener
public void monitorPayment(PaymentEvent event) {
// 实时监控支付异常
Metrics.counter("payment.total", "status", event.getStatus().name()).increment();
if (event.getStatus() == PaymentStatus.FAILED) {
log.error("支付失败: orderNo={}, reason={}",
event.getOrderNo(), event.getErrorMessage());
// 异常支付告警
alertService.sendPaymentFailureAlert(event);
// 记录到异常表用于人工核查
exceptionDao.save(PaymentException.fromEvent(event));
}
}
}
五、完整支付流程示例
@Service
@Transactional
@Slf4j
public class SafePaymentService {
public PaymentResult securePay(PaymentRequest request) {
// 1. 参数校验
validateRequest(request);
// 2. 订单校验
Order order = validateOrder(request.getOrderNo());
// 3. 金额校验
validateAmount(order, request.getAmount());
// 4. 幂等性检查
String idempotentKey = generateIdempotentKey(request);
if (isDuplicateRequest(idempotentKey)) {
return getExistingResult(idempotentKey);
}
// 5. 获取分布式锁
try (LockGuard guard = distributedLock.lock(request.getOrderNo())) {
// 6. 状态检查
if (order.getStatus() == OrderStatus.PAID) {
throw new BusinessException("订单已支付");
}
// 7. 创建支付记录
PaymentRecord record = createPaymentRecord(request, idempotentKey);
try {
// 8. 调用第三方支付
ThirdPartyResponse response = thirdPartyClient.pay(request);
// 9. 更新支付状态
updatePaymentStatus(record, response);
// 10. 更新订单状态
updateOrderStatus(order, OrderStatus.PAID);
// 11. 记录成功日志
logPaymentSuccess(record, response);
return PaymentResult.success(record);
} catch (Exception e) {
// 12. 失败处理
handlePaymentFailure(record, e);
throw new PaymentException("支付失败", e);
}
}
}
private boolean isDuplicateRequest(String idempotentKey) {
// 检查Redis或数据库
return redisTemplate.hasKey("payment:idempotent:" + idempotentKey);
}
}
六、配置建议
# application.yml
payment:
security:
# 金额限制
max-amount: 1000000
min-amount: 0.01
# 频率限制
rate-limit: 10 # 每分钟最大请求数
# 超时设置
timeout: 30s
# 对账配置
reconciliation:
enabled: true
cron: "0 0 2 * * ?"
alert-threshold: 0.01 # 差异超过0.01元告警
七、最佳实践总结
- 事前预防
- 前端金额二次确认
- 支付密码/短信验证码
- 支付限额控制
- 事中控制
- 幂等性设计
- 分布式锁
- 状态机校验
- 实时监控
- 事后核对
- 定时对账
- 差异自动处理
- 人工审核机制
- 完整操作日志
- 监控告警
- 大额支付实时告警
- 异常频次监控
- 对账差异告警
- 系统健康度监控
通过这些措施,可以大幅降低错付和多付的风险,确保支付系统的安全可靠。