← 返回生产场景

💰 支付系统

支付 / 退款 / 对账 / 幂等 / 回调处理 / 资金安全

🔁问题1:防重复扣款(幂等)最严重
用户点支付→网络超时→用户再点→扣了两次钱。这是资金事故。
// 方案:唯一支付流水号 + 状态机 public PayResult pay(PayRequest req) { // 1. 幂等检查:用支付单号查是否已处理 PayOrder exist = payMapper.findByPayNo(req.getPayNo()); if (exist != null) { if (exist.getStatus() == SUCCESS) return exist.getResult(); // 已成功,直接返回 if (exist.getStatus() == PAYING) throw "支付处理中,请稍候"; } // 2. 创建支付单(唯一索引防重) try { payMapper.insert(payOrder); } catch (DuplicateKeyException e) { throw "请勿重复支付"; } // 3. 调第三方支付 ThirdPayResult r = thirdPayClient.pay(req); payMapper.updateStatus(payNo, SUCCESS); return r; }
核心:① 支付单号唯一索引 ② 状态机(PAYING→SUCCESS/FAILED,不可逆)③ 回调也用支付单号做幂等
📞问题2:支付回调怎么处理高频
第三方支付(微信/支付宝)异步回调通知支付结果,要保证可靠处理。
// 回调接口:第三方支付成功后调这个 @PostMapping("/pay/callback") public String handleCallback(@RequestBody String body, HttpServletRequest req) { // 1. 验签(防伪造!不验签=谁都能伪造支付成功) if (!signUtil.verify(body, req.getHeader("X-Sign"))) { return "FAIL"; } // 2. 幂等(回调可能重复发) PayOrder order = payMapper.findByPayNo(payNo); if (order.getStatus() == SUCCESS) return "SUCCESS"; // 已处理 // 3. 更新订单状态 orderMapper.updateStatus(order.getOrderId(), PAID); payMapper.updateStatus(payNo, SUCCESS); // 4. 返回成功(告诉第三方不用再回调了) return "SUCCESS"; }
注意事项:① 必须验签(不验签=黑客能伪造支付成功)② 必须幂等(第三方可能重复发回调3~5次)③ 返回 SUCCESS 后第三方才停止重试 ④ 如果处理失败返回 FAIL,第三方会隔一段时间重试
🧾问题3:对账(防漏单/错单)
每天和第三方对账,检查有没有"用户付了钱但系统没记录"或"系统记录了但第三方没有"。
// 定时对账任务(每天凌晨跑) @Scheduled(cron = "0 0 3 * * ?") public void dailyReconcile() { // 1. 下载第三方账单 List<ThirdPayBill> thirdBills = thirdPayClient.downloadBill(yesterday); // 2. 和本地支付记录比对 for (ThirdPayBill bill : thirdBills) { PayOrder local = payMapper.findByPayNo(bill.getPayNo()); if (local == null) { // 第三方有,本地没有 alert("异常:第三方有支付记录但本地没有!" + bill.getPayNo()); } else if (local.getAmount() != bill.getAmount()) { alert("金额不一致!"); } } // 3. 检查本地有但第三方没有的(可能掉单) }
资金安全三道防线:① 实时:支付时幂等+状态机 ② 异步:回调验签+幂等 ③ 事后:每天对账兜底
生产场景 · 支付系统 · 全栈资料库