大家好,我是你们的老朋友,一个在支付系统这条路上摸爬滚打了多年的老司机。今天想跟大家聊聊一个让无数后端程序员闻风丧胆的话题——支付系统开发。
你是否也遇到过这些崩溃场景:
支付接口刚上线就出现数据错乱,用户投诉不断好不容易搞定微信支付,结果用户付款后订单状态没更新以为支付就是调个接口,结果上线后各种异常情况让人措手不及老板问你支付成功率怎么样,你却拿不出准确的数据
别急,今天我就把这些年踩过的坑、流过的泪全盘托出,让你少走3年弯路!
为什么支付系统这么容易出问题?
在开始正题之前,先聊聊为什么支付系统这么容易出问题:
资金安全要求极高:一分一厘都不能错业务逻辑复杂:涉及订单、库存、账户等多个系统并发处理困难:大促期间高并发,稍有不慎就数据错乱异常情况多样:网络超时、回调丢失、重复通知等等监管合规严格:各种金融法规必须遵守
支付新手常犯的十个错误
错误1:并发更新不加锁,数据错乱成常态
这是新手最容易犯的错误,也是最危险的错误。
// ❌ 错误示例:裸奔式更新
@Service
public class AccountService {
public void updateAccountBalance(Long accountId, BigDecimal amount) {
// 查询账户
Account account = accountRepository.findById(accountId);
// 计算新余额
BigDecimal newBalance = account.getBalance().add(amount);
// 更新余额
account.setBalance(newBalance);
accountRepository.save(account);
}
}
// 悲剧场景:
// 用户账户余额:1000元
// 并发请求1:充值500元
// 并发请求2:充值300元
// 最终结果:账户余额可能是1500元,而不是1800元!
正确姿势:一锁二判三更新
// ✅ 正确示例:一锁二判三更新
@Service
public class SafeAccountService {
@Autowired
private TransactionTemplate transactionTemplate;
public void updateAccountBalance(Long accountId, BigDecimal amount) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 一锁:获取数据库行锁
Account account = accountRepository.findByIdForUpdate(accountId);
// 二判:业务判断
if (account == null) {
throw new AccountNotFoundException("账户不存在");
}
if (account.getStatus() != AccountStatus.NORMAL) {
throw new InvalidAccountStatusException("账户状态异常");
}
// 判断余额是否充足(如果是扣款操作)
if (amount.compareTo(BigDecimal.ZERO) < 0 &&
account.getBalance().add(amount).compareTo(BigDecimal.ZERO) < 0) {
throw new InsufficientBalanceException("余额不足");
}
// 三更新:执行更新操作
BigDecimal newBalance = account.getBalance().add(amount);
account.setBalance(newBalance);
account.setUpdateTime(new Date());
accountRepository.save(account);
// 记录操作日志
accountLogService.recordBalanceChange(accountId, amount, newBalance);
} catch (Exception e) {
// 异常回滚
status.setRollbackOnly();
log.error("更新账户余额失败", e);
throw e;
}
}
});
}
}
错误2:支付回调不幂等,重复处理酿大祸
支付回调是支付系统中最容易出问题的环节之一。
// ❌ 错误示例:直接处理,可能重复
@RestController
public class PaymentCallbackController {
@PostMapping("/payment/wechat/callback")
public String handleWechatCallback(@RequestBody String xmlData) {
// 直接处理支付结果
processPaymentResult(xmlData);
return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
}
private void processPaymentResult(String xmlData) {
// 解析XML数据
Map<String, String> resultMap = parseXmlData(xmlData);
String outTradeNo = resultMap.get("out_trade_no"); // 商户订单号
String transactionId = resultMap.get("transaction_id"); // 微信交易号
// 直接更新订单状态
orderService.updateOrderStatus(outTradeNo, OrderStatus.PAID, transactionId);
}
}
// 悲剧场景:
// 微信服务器发送回调通知
// 网络不稳定,微信服务器没收到响应,重复发送回调
// 系统重复处理同一笔订单,可能导致重复发货!
正确姿势:幂等性处理
// ✅ 正确示例:幂等性处理
@RestController
public class SafePaymentCallbackController {
@PostMapping("/payment/wechat/callback")
public String handleWechatCallback(@RequestBody String xmlData) {
try {
// 验证签名
if (!verifySignature(xmlData)) {
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml>";
}
// 幂等性处理
if (processPaymentResult(xmlData)) {
return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
} else {
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>";
}
} catch (Exception e) {
log.error("处理微信支付回调异常", e);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[系统异常]]></return_msg></xml>";
}
}
private boolean processPaymentResult(String xmlData) {
// 解析XML数据
Map<String, String> resultMap = parseXmlData(xmlData);
String outTradeNo = resultMap.get("out_trade_no"); // 商户订单号
String transactionId = resultMap.get("transaction_id"); // 微信交易号
String tradeState = resultMap.get("trade_state"); // 交易状态
// 幂等性检查
if (paymentService.isPaymentProcessed(outTradeNo, transactionId)) {
log.info("支付已处理,订单号:{}", outTradeNo);
return true; // 已处理过,直接返回成功
}
// 根据交易状态处理
if ("SUCCESS".equals(tradeState)) {
return paymentService.processSuccessPayment(outTradeNo, transactionId, resultMap);
} else if ("REFUND".equals(tradeState)) {
return paymentService.processRefundPayment(outTradeNo, transactionId, resultMap);
} else if ("CLOSED".equals(tradeState)) {
return paymentService.processClosedPayment(outTradeNo, transactionId, resultMap);
} else {
log.warn("未知的交易状态:{},订单号:{}", tradeState, outTradeNo);
return false;
}
}
}
@Service
public class PaymentService {
// 幂等性检查
public boolean isPaymentProcessed(String outTradeNo, String transactionId) {
PaymentRecord record = paymentRecordRepository.findByOutTradeNo(outTradeNo);
return record != null &&
record.getTransactionId() != null &&
record.getTransactionId().equals(transactionId) &&
record.getStatus() == PaymentStatus.SUCCESS;
}
// 处理成功支付
@Transactional
public boolean processSuccessPayment(String outTradeNo, String transactionId, Map<String, String> resultMap) {
try {
// 1. 检查订单是否存在
Order order = orderRepository.findByOrderNo(outTradeNo);
if (order == null) {
log.error("订单不存在,订单号:{}", outTradeNo);
return false;
}
// 2. 检查订单状态
if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
log.warn("订单状态不正确,订单号:{},当前状态:{}", outTradeNo, order.getStatus());
return true; // 状态已更新,返回成功
}
// 3. 更新订单状态
order.setStatus(OrderStatus.PAID);
order.setTransactionId(transactionId);
order.setPaidTime(new Date());
order.setUpdateTime(new Date());
orderRepository.save(order);
// 4. 记录支付信息
PaymentRecord paymentRecord = new PaymentRecord();
paymentRecord.setOutTradeNo(outTradeNo);
paymentRecord.setTransactionId(transactionId);
paymentRecord.setAmount(new BigDecimal(resultMap.get("total_fee")));
paymentRecord.setStatus(PaymentStatus.SUCCESS);
paymentRecord.setPayTime(new Date());
paymentRecordRepository.save(paymentRecord);
// 5. 发送支付成功事件
paymentEventPublisher.publishPaymentSuccessEvent(order);
log.info("支付处理成功,订单号:{},交易号:{}", outTradeNo, transactionId);
return true;
} catch (Exception e) {
log.error("处理支付成功失败,订单号:{}", outTradeNo, e);
throw e; // 回滚事务
}
}
}
错误3:异常处理不完善,系统崩溃无从查
支付系统中的异常处理至关重要,但很多新手往往忽视。
// ❌ 错误示例:异常处理不完善
@Service
public class PaymentService {
public PaymentResult createPayment(Order order) {
try {
// 调用第三方支付接口
ThirdPartyPaymentResponse response = thirdPartyPayService.createPayment(order);
// 直接返回结果
return PaymentResult.success(response.getPaymentUrl());
} catch (Exception e) {
// 简单处理异常
return PaymentResult.failure("支付失败");
}
}
}
// 悲剧场景:
// 网络超时异常被吞掉
// 系统异常没有记录日志
// 问题排查时无从下手
正确姿势:完善的异常处理
// ✅ 正确示例:完善的异常处理
@Service
@Slf4j
public class RobustPaymentService {
@Autowired
private ThirdPartyPayService thirdPartyPayService;
@Autowired
private PaymentRecordRepository paymentRecordRepository;
public PaymentResult createPayment(Order order) {
String traceId = TraceContext.getTraceId();
try {
log.info("[{}] [PAYMENT_CREATE] 开始创建支付 | orderId={}, amount={}",
traceId, order.getOrderNo(), order.getAmount());
// 1. 参数校验
validateOrder(order);
// 2. 创建支付记录
PaymentRecord paymentRecord = createPaymentRecord(order);
// 3. 调用第三方支付接口
ThirdPartyPaymentResponse response = thirdPartyPayService.createPayment(order);
// 4. 更新支付记录
updatePaymentRecord(paymentRecord, response);
log.info("[{}] [PAYMENT_CREATE] 支付创建成功 | orderId={}, paymentId={}",
traceId, order.getOrderNo(), paymentRecord.getId());
return PaymentResult.success(response.getPaymentUrl())
.setPaymentId(paymentRecord.getId());
} catch (ValidationException e) {
log.warn("[{}] [PAYMENT_CREATE] 参数校验失败 | orderId={}, errors={}",
traceId, order.getOrderNo(), e.getErrors());
return PaymentResult.failure("PARAM_ERROR", e.getMessage());
} catch (ThirdPartyPaymentException e) {
log.error("[{}] [PAYMENT_CREATE] 第三方支付异常 | orderId={}, errorCode={}, errorMsg={}",
traceId, order.getOrderNo(), e.getErrorCode(), e.getErrorMsg());
return PaymentResult.failure(e.getErrorCode(), e.getErrorMsg());
} catch (DatabaseException e) {
log.error("[{}] [PAYMENT_CREATE] 数据库异常 | orderId={}",
traceId, order.getOrderNo(), e);
return PaymentResult.failure("DB_ERROR", "系统繁忙,请稍后重试");
} catch (Exception e) {
log.error("[{}] [PAYMENT_CREATE] 系统异常 | orderId={}",
traceId, order.getOrderNo(), e);
return PaymentResult.failure("SYSTEM_ERROR", "系统异常,请联系客服");
}
}
private void validateOrder(Order order) throws ValidationException {
List<String> errors = new ArrayList<>();
if (order == null) {
errors.add("订单不能为空");
} else {
if (StringUtils.isBlank(order.getOrderNo())) {
errors.add("订单号不能为空");
}
if (order.getAmount() == null || order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
errors.add("订单金额必须大于0");
}
if (order.getAmount().compareTo(new BigDecimal("100000")) > 0) {
errors.add("订单金额不能超过10万元");
}
}
if (!errors.isEmpty()) {
throw new ValidationException(errors);
}
}
private PaymentRecord createPaymentRecord(Order order) throws DatabaseException {
try {
PaymentRecord paymentRecord = new PaymentRecord();
paymentRecord.setOrderNo(order.getOrderNo());
paymentRecord.setAmount(order.getAmount());
paymentRecord.setStatus(PaymentStatus.PENDING);
paymentRecord.setCreateTime(new Date());
paymentRecord.setUpdateTime(new Date());
return paymentRecordRepository.save(paymentRecord);
} catch (Exception e) {
throw new DatabaseException("创建支付记录失败", e);
}
}
private void updatePaymentRecord(PaymentRecord paymentRecord, ThirdPartyPaymentResponse response)
throws DatabaseException {
try {
paymentRecord.setThirdPartyPaymentId(response.getPaymentId());
paymentRecord.setPaymentUrl(response.getPaymentUrl());
paymentRecord.setUpdateTime(new Date());
paymentRecordRepository.save(paymentRecord);
} catch (Exception e) {
throw new DatabaseException("更新支付记录失败", e);
}
}
}
错误4:安全措施不到位,用户信息被泄露
支付系统涉及用户敏感信息,安全措施必须到位。
// ❌ 错误示例:安全措施不到位
@RestController
public class UnsafePaymentController {
@PostMapping("/payment")
public PaymentResult createPayment(@RequestBody PaymentRequest request) {
// 直接记录用户银行卡信息
log.info("用户支付请求:{}", request);
// 调用支付接口
PaymentResult result = paymentService.createPayment(request);
return result;
}
}
// 悲剧场景:
// 日志中记录了用户银行卡号
// 敏感信息在网络中明文传输
// 违反PCI DSS合规要求
正确姿势:严格的安全措施
// ✅ 正确示例:严格的安全措施
@RestController
@Slf4j
public class SecurePaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/payment")
public PaymentResult createPayment(@RequestBody @Valid PaymentRequest request) {
// 1. 敏感信息脱敏
String maskedCardNo = maskCardNo(request.getCardNo());
String maskedMobile = maskMobile(request.getMobile());
log.info("用户支付请求 | userId={}, cardNo={}, mobile={}",
request.getUserId(), maskedCardNo, maskedMobile);
// 2. 参数校验
validatePaymentRequest(request);
// 3. 调用支付接口
PaymentResult result = paymentService.createPayment(request);
// 4. 响应数据脱敏
return maskResponseData(result);
}
private String maskCardNo(String cardNo) {
if (StringUtils.isBlank(cardNo) || cardNo.length() < 8) {
return cardNo;
}
return cardNo.substring(0, 4) + "****" + cardNo.substring(cardNo.length() - 4);
}
private String maskMobile(String mobile) {
if (StringUtils.isBlank(mobile) || mobile.length() != 11) {
return mobile;
}
return mobile.substring(0, 3) + "****" + mobile.substring(7);
}
private void validatePaymentRequest(PaymentRequest request) {
// 使用Bean Validation进行参数校验
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<PaymentRequest>> violations = validator.validate(request);
if (!violations.isEmpty()) {
List<String> errors = violations.stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.collect(Collectors.toList());
throw new ValidationException("参数校验失败: " + String.join(", ", errors));
}
}
private PaymentResult maskResponseData(PaymentResult result) {
// 对响应数据进行脱敏处理
if (result != null && result.getData() != null) {
// 根据业务需要对敏感数据进行处理
result.getData().remove("sensitive_info");
}
return result;
}
}
// 支付请求参数校验
@Data
public class PaymentRequest {
@NotBlank(message = "用户ID不能为空")
private String userId;
@NotBlank(message = "银行卡号不能为空")
@Pattern(regexp = "^d{16,19}$", message = "银行卡号格式不正确")
private String cardNo;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]d{9}$", message = "手机号格式不正确")
private String mobile;
@NotNull(message = "支付金额不能为空")
@DecimalMin(value = "0.01", message = "支付金额必须大于0")
@DecimalMax(value = "100000", message = "支付金额不能超过10万元")
private BigDecimal amount;
}
错误5:事务管理不规范,数据一致性无法保证
支付系统对数据一致性要求极高,事务管理必须规范。
// ❌ 错误示例:事务管理不规范
@Service
public class PaymentService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountRepository accountRepository;
public void processPayment(String orderNo, BigDecimal amount) {
// 更新订单状态
Order order = orderRepository.findByOrderNo(orderNo);
order.setStatus(OrderStatus.PAID);
orderRepository.save(order);
// 扣减用户账户余额
Account account = accountRepository.findByUserId(order.getUserId());
account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);
// 如果中间出现异常,数据就不一致了!
}
}
正确姿势:规范的事务管理
// ✅ 正确示例:规范的事务管理
@Service
@Slf4j
public class TransactionalPaymentService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransactionTemplate transactionTemplate;
public PaymentResult processPayment(String orderNo, BigDecimal amount) {
return transactionTemplate.execute(new TransactionCallback<PaymentResult>() {
@Override
public PaymentResult doInTransaction(TransactionStatus status) {
try {
// 1. 查询并锁定订单
Order order = orderRepository.findByOrderNoForUpdate(orderNo);
if (order == null) {
throw new OrderNotFoundException("订单不存在");
}
if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
throw new IllegalOrderStateException("订单状态不正确");
}
// 2. 查询并锁定账户
Account account = accountRepository.findByUserIdForUpdate(order.getUserId());
if (account == null) {
throw new AccountNotFoundException("账户不存在");
}
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("账户余额不足");
}
// 3. 更新订单状态
order.setStatus(OrderStatus.PAID);
order.setPaidTime(new Date());
order.setUpdateTime(new Date());
orderRepository.save(order);
// 4. 扣减账户余额
account.setBalance(account.getBalance().subtract(amount));
account.setUpdateTime(new Date());
accountRepository.save(account);
// 5. 记录支付日志
paymentLogService.recordPayment(orderNo, amount, account.getUserId());
log.info("支付处理成功 | orderNo={}, amount={}, userId={}",
orderNo, amount, account.getUserId());
return PaymentResult.success();
} catch (Exception e) {
// 标记事务回滚
status.setRollbackOnly();
log.error("支付处理失败 | orderNo={}", orderNo, e);
throw new PaymentProcessingException("支付处理失败", e);
}
}
});
}
}
错误6:监控告警不完善,问题发现太滞后
支付系统必须有完善的监控告警机制。
// ❌ 错误示例:监控告警不完善
@Service
public class PaymentService {
public PaymentResult processPayment(PaymentRequest request) {
// 处理支付逻辑
PaymentResult result = doProcessPayment(request);
// 没有监控和告警
return result;
}
}
正确姿势:完善的监控告警
// ✅ 正确示例:完善的监控告警
@Service
@Slf4j
public class MonitoredPaymentService {
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private AlertService alertService;
public PaymentResult processPayment(PaymentRequest request) {
String traceId = TraceContext.getTraceId();
long startTime = System.currentTimeMillis();
try {
log.info("[{}] [PAYMENT_PROCESS] 开始处理支付 | userId={}, amount={}",
traceId, request.getUserId(), request.getAmount());
// 处理支付逻辑
PaymentResult result = doProcessPayment(request);
long cost = System.currentTimeMillis() - startTime;
// 记录成功指标
recordSuccessMetrics(request, cost);
log.info("[{}] [PAYMENT_PROCESS] 支付处理成功 | userId={}, amount={}, cost={}ms",
traceId, request.getUserId(), request.getAmount(), cost);
return result;
} catch (Exception e) {
long cost = System.currentTimeMillis() - startTime;
// 记录失败指标
recordFailureMetrics(request, e, cost);
log.error("[{}] [PAYMENT_PROCESS] 支付处理失败 | userId={}, amount={}, cost={}ms",
traceId, request.getUserId(), request.getAmount(), cost, e);
// 发送告警
sendAlert(request, e);
throw e;
}
}
private void recordSuccessMetrics(PaymentRequest request, long cost) {
// 记录支付成功次数
Counter.builder("payment.success")
.description("支付成功次数")
.tag("payment_method", request.getPaymentMethod())
.tag("currency", request.getCurrency())
.register(meterRegistry)
.increment();
// 记录支付响应时间
Timer.builder("payment.response.time")
.description("支付响应时间")
.tag("payment_method", request.getPaymentMethod())
.register(meterRegistry)
.record(cost, TimeUnit.MILLISECONDS);
// 记录支付金额分布
DistributionSummary.builder("payment.amount")
.description("支付金额分布")
.tag("payment_method", request.getPaymentMethod())
.register(meterRegistry)
.record(request.getAmount().doubleValue());
}
private void recordFailureMetrics(PaymentRequest request, Exception e, long cost) {
// 记录支付失败次数
Counter.builder("payment.failure")
.description("支付失败次数")
.tag("payment_method", request.getPaymentMethod())
.tag("error_type", e.getClass().getSimpleName())
.register(meterRegistry)
.increment();
// 记录失败响应时间
Timer.builder("payment.failure.time")
.description("支付失败响应时间")
.tag("payment_method", request.getPaymentMethod())
.tag("error_type", e.getClass().getSimpleName())
.register(meterRegistry)
.record(cost, TimeUnit.MILLISECONDS);
}
private void sendAlert(PaymentRequest request, Exception e) {
// 支付失败率告警
if (shouldSendAlert()) {
Alert alert = Alert.builder()
.level(AlertLevel.HIGH)
.title("支付系统异常")
.content(String.format("支付失败,用户ID:%s,金额:%s,错误:%s",
request.getUserId(), request.getAmount(), e.getMessage()))
.build();
alertService.sendAlert(alert);
}
}
private boolean shouldSendAlert() {
// 根据失败率等指标判断是否需要发送告警
// 这里简化处理
return true;
}
}
错误7:测试不充分,上线后问题频发
支付系统的测试必须充分,包括各种异常场景。
// ❌ 错误示例:测试不充分
@Test
public class PaymentServiceTest {
@Test
public void testCreatePayment() {
// 只测试正常情况
PaymentResult result = paymentService.createPayment(order);
assertTrue(result.isSuccess());
}
}
正确姿势:充分的测试
// ✅ 正确示例:充分的测试
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"spring.jpa.hibernate.ddl-auto=create-drop"
})
class ComprehensivePaymentServiceTest {
@Autowired
private PaymentService paymentService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountRepository accountRepository;
@BeforeEach
void setUp() {
// 初始化测试数据
createTestOrder();
createTestAccount();
}
@Test
@DisplayName("正常支付流程测试")
void testNormalPaymentProcess() {
PaymentRequest request = createNormalPaymentRequest();
PaymentResult result = paymentService.processPayment(request);
assertTrue(result.isSuccess());
assertEquals("支付成功", result.getMessage());
// 验证订单状态
Order order = orderRepository.findByOrderNo(request.getOrderNo());
assertEquals(OrderStatus.PAID, order.getStatus());
// 验证账户余额
Account account = accountRepository.findByUserId(request.getUserId());
assertEquals(new BigDecimal("900.00"), account.getBalance());
}
@Test
@DisplayName("余额不足支付失败测试")
void testInsufficientBalancePayment() {
PaymentRequest request = createInsufficientBalancePaymentRequest();
PaymentProcessingException exception = assertThrows(
PaymentProcessingException.class,
() -> paymentService.processPayment(request));
assertEquals("账户余额不足", exception.getMessage());
// 验证订单状态未改变
Order order = orderRepository.findByOrderNo(request.getOrderNo());
assertEquals(OrderStatus.PENDING_PAYMENT, order.getStatus());
// 验证账户余额未改变
Account account = accountRepository.findByUserId(request.getUserId());
assertEquals(new BigDecimal("1000.00"), account.getBalance());
}
@Test
@DisplayName("并发支付测试")
void testConcurrentPayment() throws InterruptedException {
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
List<PaymentResult> results = Collections.synchronizedList(new ArrayList<>());
// 同时发起多个支付请求
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
PaymentRequest request = createNormalPaymentRequest();
PaymentResult result = paymentService.processPayment(request);
results.add(result);
} catch (Exception e) {
// 记录异常
results.add(PaymentResult.failure(e.getMessage()));
} finally {
latch.countDown();
}
}).start();
}
// 等待所有线程完成
latch.await(30, TimeUnit.SECONDS);
// 验证只有一个支付成功
long successCount = results.stream().filter(PaymentResult::isSuccess).count();
assertEquals(1, successCount, "应该只有一个支付成功");
// 验证只有一个订单状态更新
Order order = orderRepository.findByOrderNo("TEST_ORDER_001");
if (successCount == 1) {
assertEquals(OrderStatus.PAID, order.getStatus());
} else {
assertEquals(OrderStatus.PENDING_PAYMENT, order.getStatus());
}
}
@Test
@DisplayName("支付回调重复处理测试")
void testDuplicatePaymentCallback() {
String outTradeNo = "TEST_ORDER_001";
String transactionId = "WX_000001";
// 第一次处理
boolean firstResult = paymentService.processPaymentCallback(outTradeNo, transactionId);
assertTrue(firstResult, "第一次处理应该成功");
// 第二次处理(重复)
boolean secondResult = paymentService.processPaymentCallback(outTradeNo, transactionId);
assertTrue(secondResult, "重复处理应该也成功");
// 验证订单状态
Order order = orderRepository.findByOrderNo(outTradeNo);
assertEquals(OrderStatus.PAID, order.getStatus());
// 验证只有一条支付记录
List<PaymentRecord> records = paymentRecordRepository.findByOutTradeNo(outTradeNo);
assertEquals(1, records.size());
}
private PaymentRequest createNormalPaymentRequest() {
PaymentRequest request = new PaymentRequest();
request.setUserId("USER_001");
request.setOrderNo("TEST_ORDER_001");
request.setAmount(new BigDecimal("100.00"));
request.setPaymentMethod("BALANCE");
return request;
}
private PaymentRequest createInsufficientBalancePaymentRequest() {
PaymentRequest request = new PaymentRequest();
request.setUserId("USER_001");
request.setOrderNo("TEST_ORDER_001");
request.setAmount(new BigDecimal("2000.00"); // 超过账户余额
request.setPaymentMethod("BALANCE");
return request;
}
private void createTestOrder() {
Order order = new Order();
order.setOrderNo("TEST_ORDER_001");
order.setUserId("USER_001");
order.setAmount(new BigDecimal("100.00"));
order.setStatus(OrderStatus.PENDING_PAYMENT);
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
orderRepository.save(order);
}
private void createTestAccount() {
Account account = new Account();
account.setUserId("USER_001");
account.setBalance(new BigDecimal("1000.00"));
account.setStatus(AccountStatus.NORMAL);
account.setCreateTime(new Date());
account.setUpdateTime(new Date());
accountRepository.save(account);
}
}
错误8:日志记录不规范,问题排查困难
支付系统的日志记录必须规范,便于问题排查。
// ❌ 错误示例:日志记录不规范
@Service
public class PaymentService {
public void processPayment(PaymentRequest request) {
System.out.println("开始处理支付");
// 处理逻辑
System.out.println("支付处理完成");
}
}
正确姿势:规范的日志记录
// ✅ 正确示例:规范的日志记录
@Service
@Slf4j
public class WellLoggedPaymentService {
public PaymentResult processPayment(PaymentRequest request) {
String traceId = TraceContext.getTraceId();
// 入口日志
log.info("[{}] [PAYMENT_PROCESS] 开始处理支付 | userId={}, orderNo={}, amount={}, paymentMethod={}",
traceId, request.getUserId(), request.getOrderNo(),
request.getAmount(), request.getPaymentMethod());
long startTime = System.currentTimeMillis();
try {
// 参数校验日志
log.debug("[{}] [PARAM_VALIDATE] 开始参数校验", traceId);
validateRequest(request);
log.debug("[{}] [PARAM_VALIDATE] 参数校验通过", traceId);
// 业务处理日志
log.debug("[{}] [BUSINESS_PROCESS] 开始业务处理", traceId);
PaymentResult result = doBusinessProcess(request);
log.debug("[{}] [BUSINESS_PROCESS] 业务处理完成", traceId);
long cost = System.currentTimeMillis() - startTime;
// 成功日志
log.info("[{}] [PAYMENT_PROCESS] 支付处理成功 | userId={}, orderNo={}, amount={}, cost={}ms",
traceId, request.getUserId(), request.getOrderNo(),
request.getAmount(), cost);
return result;
} catch (ValidationException e) {
long cost = System.currentTimeMillis() - startTime;
log.warn("[{}] [PAYMENT_PROCESS] 参数校验失败 | userId={}, errors={}, cost={}ms",
traceId, request.getUserId(), e.getErrors(), cost);
return PaymentResult.failure("PARAM_ERROR", e.getMessage());
} catch (BusinessException e) {
long cost = System.currentTimeMillis() - startTime;
log.warn("[{}] [PAYMENT_PROCESS] 业务异常 | userId={}, code={}, message={}, cost={}ms",
traceId, request.getUserId(), e.getCode(), e.getMessage(), cost);
return PaymentResult.failure(e.getCode(), e.getMessage());
} catch (Exception e) {
long cost = System.currentTimeMillis() - startTime;
log.error("[{}] [PAYMENT_PROCESS] 系统异常 | userId={}, orderNo={}, error={}, cost={}ms",
traceId, request.getUserId(), request.getOrderNo(), e.getMessage(), cost, e);
return PaymentResult.failure("SYSTEM_ERROR", "系统繁忙");
}
}
private void validateRequest(PaymentRequest request) throws ValidationException {
List<String> errors = new ArrayList<>();
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
errors.add("支付金额必须大于0");
}
if (errors.isEmpty()) {
return;
}
throw new ValidationException(errors);
}
private PaymentResult doBusinessProcess(PaymentRequest request) throws BusinessException {
// 模拟业务处理
if (request.getAmount().compareTo(new BigDecimal("10000")) > 0) {
throw new BusinessException("AMOUNT_TOO_LARGE", "支付金额过大");
}
return PaymentResult.success();
}
}
错误9:配置管理混乱,生产环境出问题
支付系统的配置管理必须规范,避免生产环境出问题。
// ❌ 错误示例:配置管理混乱
@Service
public class PaymentService {
private String paymentGatewayUrl = "http://localhost:8080/payment"; // 开发环境URL
public void processPayment(PaymentRequest request) {
// 使用硬编码的URL,生产环境会出问题
callPaymentGateway(paymentGatewayUrl, request);
}
}
正确姿势:规范的配置管理
// ✅ 正确示例:规范的配置管理
@Service
public class ConfigurablePaymentService {
@Value("${payment.gateway.url}")
private String paymentGatewayUrl;
@Value("${payment.gateway.timeout:5000}")
private int paymentGatewayTimeout;
@Value("${payment.gateway.retry.times:3}")
private int retryTimes;
@Autowired
private RestTemplate restTemplate;
public PaymentResult processPayment(PaymentRequest request) {
log.info("支付网关配置 | url={}, timeout={}, retryTimes={}",
paymentGatewayUrl, paymentGatewayTimeout, retryTimes);
return callPaymentGatewayWithRetry(request);
}
private PaymentResult callPaymentGatewayWithRetry(PaymentRequest request) {
for (int i = 0; i < retryTimes; i++) {
try {
return callPaymentGateway(request);
} catch (Exception e) {
log.warn("调用支付网关失败,第{}次重试", i + 1, e);
if (i == retryTimes - 1) {
// 最后一次重试失败,抛出异常
throw new PaymentGatewayException("调用支付网关失败", e);
}
// 等待后重试
try {
Thread.sleep(1000 * (i + 1)); // 递增等待时间
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new PaymentGatewayException("重试被中断", ie);
}
}
}
throw new PaymentGatewayException("调用支付网关失败");
}
private PaymentResult callPaymentGateway(PaymentRequest request) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-API-Key", getApiKey());
HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<PaymentResult> response = restTemplate.exchange(
paymentGatewayUrl,
HttpMethod.POST,
entity,
PaymentResult.class);
return response.getBody();
} catch (Exception e) {
log.error("调用支付网关异常 | url={}, request={}", paymentGatewayUrl, request, e);
throw new PaymentGatewayException("调用支付网关异常", e);
}
}
private String getApiKey() {
// 从安全存储中获取API密钥
return secureConfigService.getPaymentApiKey();
}
}
// 配置文件
/*
# application.yml
payment:
gateway:
url: ${PAYMENT_GATEWAY_URL:http://localhost:8080/payment}
timeout: ${PAYMENT_GATEWAY_TIMEOUT:5000}
retry:
times: ${PAYMENT_GATEWAY_RETRY_TIMES:3}
*/
// 配置类
@Configuration
@ConfigurationProperties(prefix = "payment.gateway")
@Data
public class PaymentGatewayConfig {
private String url;
private int timeout = 5000;
private Retry retry = new Retry();
@Data
public static class Retry {
private int times = 3;
}
}
错误10:缺乏容错机制,系统脆弱易崩溃
支付系统必须有完善的容错机制,保证系统稳定性。
// ❌ 错误示例:缺乏容错机制
@Service
public class PaymentService {
public PaymentResult processPayment(PaymentRequest request) {
// 直接调用第三方服务,没有容错机制
return thirdPartyPaymentService.process(request);
}
}
正确姿势:完善的容错机制
// ✅ 正确示例:完善的容错机制
@Service
@Slf4j
public class FaultTolerantPaymentService {
@Autowired
private ThirdPartyPaymentService thirdPartyPaymentService;
@Autowired
private CircuitBreaker circuitBreaker;
@Autowired
private FallbackPaymentService fallbackPaymentService;
public PaymentResult processPayment(PaymentRequest request) {
String traceId = TraceContext.getTraceId();
try {
// 使用熔断器调用第三方支付服务
return circuitBreaker.executeSupplier(() -> {
log.debug("[{}] [THIRD_PARTY_CALL] 调用第三方支付服务", traceId);
PaymentResult result = thirdPartyPaymentService.process(request);
log.debug("[{}] [THIRD_PARTY_CALL] 第三方支付服务调用成功", traceId);
return result;
});
} catch (CircuitBreakerOpenException e) {
// 熔断器打开,使用降级方案
log.warn("[{}] [CIRCUIT_BREAKER] 熔断器打开,使用降级方案", traceId);
return handleFallback(request);
} catch (Exception e) {
// 其他异常,记录日志并使用降级方案
log.error("[{}] [THIRD_PARTY_CALL] 调用第三方支付服务异常", traceId, e);
return handleFallback(request);
}
}
private PaymentResult handleFallback(PaymentRequest request) {
try {
// 尝试使用降级支付服务
log.info("[{}] [FALLBACK] 使用降级支付服务", TraceContext.getTraceId());
return fallbackPaymentService.process(request);
} catch (Exception fallbackException) {
// 降级服务也失败,记录错误并返回失败结果
log.error("[{}] [FALLBACK] 降级支付服务也失败", TraceContext.getTraceId(), fallbackException);
return PaymentResult.failure("ALL_PAYMENT_SERVICES_DOWN", "所有支付服务都不可用");
}
}
}
// 熔断器配置
@Configuration
public class CircuitBreakerConfig {
@Bean
public CircuitBreaker circuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率阈值50%
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断器打开时长30秒
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
.slidingWindowSize(10) // 滑动窗口大小10个请求
.minimumNumberOfCalls(5) // 最小请求数5个
.build();
return CircuitBreaker.of("payment-service", config);
}
}
// 降级支付服务
@Service
@Slf4j
public class FallbackPaymentService {
public PaymentResult process(PaymentRequest request) {
// 实现降级逻辑,比如:
// 1. 使用备用支付渠道
// 2. 记录支付请求,后续人工处理
// 3. 返回特殊状态,让用户稍后重试
log.info("使用降级支付服务处理支付请求 | userId={}, amount={}",
request.getUserId(), request.getAmount());
// 这里简化处理,实际应该根据业务需求实现
return PaymentResult.failure("SERVICE_TEMPORARILY_UNAVAILABLE", "服务暂时不可用,请稍后重试");
}
}
结语
支付系统开发是一个复杂而严谨的过程,每一个细节都关系到用户的资金安全和公司的声誉。今天分享的这10个常见错误,每一个都是血泪教训,希望能让大家少走弯路。
记住这几点:
并发安全是底线:一锁二判三更新,数据一致性不容妥协幂等性是关键:支付回调必须做幂等处理,避免重复处理异常处理要完善:各种异常场景都要考虑,不能让异常破坏数据一致性安全措施要到位:敏感信息必须脱敏,符合合规要求事务管理要规范:跨表操作必须使用事务,保证数据一致性监控告警要完善:及时发现问题,快速响应处理测试要充分:各种场景都要测试,特别是并发和异常场景日志要规范:便于问题排查和审计配置要规范:避免生产环境配置错误容错机制要完善:保证系统稳定性和可用性
支付系统无小事,每一行代码都关系到用户的资金安全。在追求功能快速上线的同时,一定要把稳定性和安全性放在首位。
如果你觉得这篇文章对你有帮助,欢迎点赞、在看、转发三连,你的支持是我们持续创作的最大动力!
服务端技术精选 | 专注分享实用的后端技术干货和实战经验














暂无评论内容