支付回调处理,咱得整得 “幂等可靠” 不翻车

支付回调处理,咱得整得 "幂等可靠" 不翻车

作为一个写了 8 年 Java 的老兵,说实话,支付回调这玩意,属实不能马虎。一不小心就 "翻车"。本文就从实战角度聊聊,如何用 幂等可靠的姿势,优雅处理支付回调。


💥 先说问题:支付回调为啥这么敏感?

支付回调,简单说,就是用户付款后,第三方支付平台(比如支付宝、微信)会把"付款成功"的消息回调给我们系统。

BUT! 回调可能:

  • 被多次触发(支付平台重试机制)
  • 被恶意伪造(安全问题)
  • 到达顺序不一致(并发问题)

如果我们系统里对同一订单处理多次,就可能出现:

  • 重复发货
  • 多次发放优惠券
  • 错误业务状态

这就尴尬了,业务也翻车,客户也不满,老板也发火。


🎯 核心目标:幂等 + 安全 + 高可用

我们要的不是"处理一次",而是:

  • 无论多少次回调,业务只处理一次
  • 防止伪造
  • 保证高并发下不出错

🧠 思路拆解

✅ 1. 校验签名,验证来源

确保是支付平台真的发的请求,不是别人伪造的。

✅ 2. 校验业务参数

比如校验订单是否存在、支付金额是否正确等。

✅ 3. 幂等处理核心:状态机 + 乐观锁 / 分布式锁

订单状态从 "待支付" → "已支付",只允许状态从前往后流转,任何重复请求都不再处理

✅ 4. 异常友好,日志记录

任何异常、非法请求都要记录日志,方便排查。


🧪 实战代码

下面直接上关键代码,基于 Spring Boot + MyBatis Plus 实现。

🚪 控制器入口

kotlin 复制代码
@PostMapping("/api/pay/callback")
public String handlePayCallback(@RequestBody PayCallbackRequest request) {
    log.info("收到支付回调:{}", request);

    try {
        // 1. 验签
        if (!payService.verifySignature(request)) {
            log.warn("验签失败!");
            return "FAIL";
        }

        // 2. 幂等处理
        payService.handleCallback(request);

        return "SUCCESS";
    } catch (Exception e) {
        log.error("处理支付回调异常", e);
        return "FAIL";
    }
}

🧰 核心处理逻辑

scss 复制代码
@Transactional
public void handleCallback(PayCallbackRequest request) {
    String orderNo = request.getOrderNo();

    // 1. 查询订单
    Order order = orderMapper.selectByOrderNo(orderNo);
    if (order == null) {
        throw new BizException("订单不存在");
    }

    // 2. 校验金额
    if (!request.getAmount().equals(order.getAmount())) {
        throw new BizException("金额不一致");
    }

    // 3. 幂等判断 - 已处理直接返回
    if (OrderStatus.PAID.equals(order.getStatus())) {
        log.info("订单已处理,无需重复处理: {}", orderNo);
        return;
    }

    // 4. 更新订单状态(乐观锁)
    int rows = orderMapper.updateStatus(
        orderNo,
        OrderStatus.WAIT_PAY,
        OrderStatus.PAID
    );

    if (rows == 0) {
        log.warn("订单状态更新失败,可能是并发导致:{}", orderNo);
        return; // 并发请求中,其他线程已更新
    }

    // 5. 处理业务逻辑(比如发货、发放优惠券等)
    bizService.doAfterPayment(order);
}

✅ 乐观锁实现(MyBatis Plus)

数据库表字段 version 开启乐观锁支持:

java 复制代码
@Version
private Integer version;

更新 SQL 使用 WHERE status = ? AND version = ? 方式,确保并发下只有一次能成功。


🔐 验签逻辑(伪代码)

ini 复制代码
public boolean verifySignature(PayCallbackRequest request) {
    String data = request.toSignString(); // 转换为按顺序拼接的字符串
    String signature = request.getSign();

    String expected = SignUtil.sign(data, PAYMENT_SECRET_KEY);
    return expected.equals(signature);
}

🧱 数据库设计建议

订单表字段:

  • order_no:订单号(唯一索引)
  • status:订单状态(枚举)
  • amount:订单金额
  • version:乐观锁字段

🧠 最佳实践 Tips:

  • 使用本地事务统一管理状态修改和业务处理
  • 并发处理用乐观锁优于分布式锁
  • 重要日志打全,后期排查容易
  • 支付回调要记录原始报文,方便追溯

✅ 总结

支付回调这件事,最忌讳的就是 "想当然"。你以为只会来一次,但现实是 ------ 可能来十次,还不一定是平台发的。

所以:

  • 幂等性 是底线
  • 安全性 是前提
  • 高可用 是保障

咱写代码的风格就一句话:不翻车、不掉坑、能复用、能扩展。


🚀 如果你也在做支付系统

欢迎留言交流,或者直接在评论区贴出你们的回调处理姿势,让我们一起把"幂等"整明白!

相关推荐
踏浪无痕2 小时前
高并发写入 API 设计:借鉴 NSQ 的内存队列与背压机制
后端·面试·go
⑩-2 小时前
Spring 事务失效
java·后端·spring
BingoGo2 小时前
告别 Shell 脚本:用 Laravel Envoy 实现干净可复用的部署
后端
爱因斯坦乐2 小时前
【若依】前后端分离添加导入
java·前端·javascript
Cache技术分享2 小时前
267. Java 集合 - Java 开发必看:ArrayList 与 LinkedList 的全方位对比及选择建议
前端·后端
用户8307196840822 小时前
Spring Boot JWT登录授权使用指南(无感刷新)
java·spring boot
uup2 小时前
Redis 缓存击穿
java
2501_921649492 小时前
亚太股票数据API:日股、韩股、新加坡股票、印尼股票市场实时行情,实时数据API-python
开发语言·后端·python·websocket·金融
怀旧,2 小时前
【Linux系统编程】11. 基础IO(上)
java·linux·服务器