状态机在支付中的应用

一、什么是状态机(有限状态机 FSM)
简单一句话:一个支付单,在任意时刻只有唯一状态,只能被指定事件驱动,流向预先定义好的下一个状态。
有限状态机 = 状态 + 事件(用户支付) + 动作(支付中改完成) + 流转规则

二、支付业务为什么必须用状态机?
支付的痛点:

  • 异步回调(微信异步通知,晚几秒回来)
  • 重复请求、重复回调
  • 并发请求(同时退款+支付回调)

如果用简单 update set status=xx 裸更新容易出现:脏数据、状态被覆盖
状态机的价值:

  1. 强约束流转,非法状态变更直接拒绝
  2. 幂等防重:同一个事件多次进来,只处理一次
  3. 把复杂的分支if-else收拢成统一流转规则

三、支付订单标准状态流转(最经典的一套)

  1. 支付单基础状态枚举

  2. INIT 初始化/待支付

  3. PAYING 支付处理中(已调起三方收银台)

  4. PAY_SUCCESS 支付成功(收到三方成功回调)

  5. PAY_FAIL 支付失败(余额不足、银行卡拒绝)

  6. REFUNDING 退款中

  7. REFUNDED 已全额退款

  8. CLOSED 超时关闭 / 主动关闭订单

  9. 合法流转(简化版)

  10. INIT

  • 触发【发起支付】→ PAYING
  • 触发【超时关闭】→ CLOSED
  1. PAYING
  • 三方回调【支付成功】→ PAY_SUCCESS
  • 三方回调【支付失败】→ PAY_FAIL
  • 超时未支付 → CLOSED
  1. PAY_SUCCESS(终态之一,但支持退款分支)
  • 触发【申请退款】→ REFUNDING
  1. REFUNDING
  • 退款回调成功 → REFUNDED
  • 退款失败 → 退回 PAY_SUCCESS(可重试退款)

重点:
不允许:已关闭的订单再被改成支付成功;不允许支付失败单直接退款

四、工程落地的三种实现方式(后端开发常用)
方式1:数据库状态机(最常用,分布式支付系统首选)
数据表字段:
pay_order(id, order_no, status, version, ...)
更新SQL必须带前置状态条件(乐观锁+状态前置判断)
伪代码:
// 正确:只能从 PAYING 状态,被支付成功事件改成 PAY_SUCCESS
int rows = update pay_order set status = PAY_SUCCESS, pay_time = now()
where order_no = ? and status = PAYING and version = ?;
if(rows == 0) {
// 状态已经变了(被别的请求处理过了),拒绝执行,防止重复回调覆盖
return 幂等跳过
}
这就是数据库层面的有限状态机。

方式2:框架状态机(Spring StateMachine / 自研状态机引擎)

把配置写在配置类/配置文件里:
state(INIT)
.on(Event.PAY_SUBMIT).target(PAYING)
state(PAYING)
.on(Event.PAY_CALLBACK_SUCCESS).target(PAY_SUCCESS)
优点:流转规则集中配置,可读性强,新增状态只改配置
缺点:分布式场景下,还是要依赖DB做持久化。

方式3:状态机 + 本地消息表 / 定时补偿任务
支付是异步的:

  • 收到回调 → 驱动状态机流转
  • 定时任务扫描:卡在 PAYING 很久的单子,主动查询三方接口对账,驱动状态机修正状态
    解决回调丢失问题。

五、支付里几个典型的状态机应用场景
场景1:支付回调幂等防重
三方支付网关可能重复推送回调:

  • 第一次回调:PAYING → PAY_SUCCESS(更新成功,执行业务发券、记账)
  • 第二次重复回调:当前状态已经是 PAY_SUCCESS,状态机判定:不允许再次流转,直接返回success,不重复执行业务。

场景2:部分退款 / 多次退款状态控制
订单支付成功后:

  • 第一次部分退款:状态进入退款中,记录已退金额
  • 第二次退款:状态机校验:剩余可退金额>0,才允许发起二次退款
    防止超退、超额退款资损。

场景3:关单超时自动关闭
未支付订单30分钟超时:
定时任务触发【超时事件】
状态机判断:当前状态是INIT/PAYING → 流转到CLOSED
如果已经是支付成功,拒绝关单。

场景4:对账与差错处理
日终对账:
本地库状态 vs 微信/支付宝对账文件不一致
人工/补偿任务触发【差错修复事件】,在管理员权限的状态机规则下修正状态。

六、设计要点

  1. 状态设计要粗粒度,不要太细碎
    不要拆出几十个细碎中间态,增加维护成本。
  2. 区分:终态 / 中间态
    终态:已支付、已全额退款、已关闭(一般不再主动流转)
    中间态:支付中、退款中(等待外部回调)
  3. 状态机 + 乐观锁/分布式锁 一起使用
    防止并发下两个请求同时驱动状态流转。
  4. 状态变更必须留流水日志(状态变更日志表)
    order_state_log(订单号,旧状态,新状态,事件,操作时间,来源(回调/定时/人工))
    支付审计、排查资损必备。

七、精简总结

  1. 状态机由状态、事件、动作组成,约束对象只能按照预设规则流转状态。
  2. 支付系统中,解决异步回调、重复请求、并发更新导致的状态混乱与资损风险。
  3. 落地一般是:数据库状态前置判断 + 乐观锁 + 状态变更日志 + 定时补偿。
  4. 支付核心流转:初始化→支付中→支付成功(失败/超时)→退款中→已退款→关闭
  5. 核心价值:状态强约束、幂等、可追溯、减少大量if-else分支。
posted @ 2026-07-05 16:15  wds200  阅读(5)  评论(0)    收藏  举报