⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在当今的支付业务领域,其涉及的环节繁多,涵盖了创建订单、发起支付、等待第三方回调、退款以及关闭等多个关键步骤。

由于环节复杂且异常场景层出不穷,如果仅仅依赖一个简单的 status 字段来随意更新订单状态,极易引发逻辑混乱、漏单现象,同时也会给后续的审计工作带来极大的困难。

为了解决这些问题,众多支付系统纷纷引入了状态机(State Machine)这一强大工具,用于管理支付订单在不同阶段的状态转移。与此同时,配合使用状态变更记录表(也称为历史表)进行详细记录,为后续的审计和问题排查提供有力支持。

一、为何支付系统需要状态机

1. 业务复杂度的应对

支付流程犹如一幅错综复杂的画卷,从订单创建伊始,到支付成功或失败,再到可能的退款或关闭操作,其间存在着各种各样的中间状态和异常情况。

例如,用户可能因为各种原因迟迟不进行付款操作;第三方回调信息可能由于网络等因素丢失;部分退款的情况也时有发生。

这些复杂的业务场景使得简单的状态管理方式难以胜任,而状态机的引入则为解决这些问题提供了有效的途径。

2. 提升可维护性与可审计性

  • 可维护性:状态机能够清晰地列出状态与事件之间的对应关系,就像一张精确的地图,为团队成员指引方向。这避免了团队成员随意修改订单状态,减少了沟通成本和排查问题的时间。关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册!团队成员可以根据状态机的规则,快速了解系统的运行逻辑,提高开发和维护的效率。
  • 可审计性:支付业务涉及资金流动,受到严格的监管和合规要求。因此,对每一步状态变更进行详细的留痕记录是必不可少的。状态变更记录表可以记录下状态变更的时间、原因、操作者等重要信息,为后续的审计工作提供了完整的证据链。

3. 有序处理异常场景

在支付过程中,异常场景时有发生,如第三方回调可能延迟或失败;用户可能在支付中途取消订单;退款过程需要多次与第三方进行确认等。

状态机的引入使得这些异常场景的处理更加有序,能够确保系统内状态的一致性。通过状态机的规则,可以对不同的异常情况进行分类处理,避免系统出现混乱。

综上所述,对于中大型支付系统而言,状态机几乎是不可或缺的设计工具,它能够帮助系统更好地应对复杂的业务场景,提高系统的稳定性和可维护性。

二、常见支付状态的解析

在实际的支付业务中,支付状态的定义并非一成不变,而是需要根据具体的业务需求进行灵活调整。下面为大家呈现一个常见且相对完整的支付状态集合,你可以根据自身业务的特点进行增减或合并。

1. CREATED(已创建)

当订单在支付中心成功生成,但尚未正式发起支付时,订单处于此状态。这是订单生命周期的起始阶段,标志着订单的初步创建。

2. PENDING(待支付)

一旦向第三方发起支付请求,或者生成了支付链接/二维码供用户进行支付操作,但尚未收到最终的支付结果时,订单进入待支付状态。此时,系统需要等待用户完成支付或第三方返回支付结果。

3. PROCESSING(支付中 / 处理中)

部分支付渠道会返回「处理中」的状态信息,这意味着第三方需要一定的时间来完成扣款确认操作。在某些业务场景中,可能会将「待支付」和「支付中」这两个状态合并为一个状态,以简化业务逻辑。

4. SUCCESS(支付成功)

当收到第三方支付成功的回调信息,或者主动查询到支付成功的结果时,订单正式完成支付,进入支付成功状态。这是支付流程的一个重要里程碑,标志着交易的顺利完成。

5. FAIL(支付失败)

若第三方支付明确表示失败,或者用户超时未付款,订单将进入支付失败状态。一旦进入此状态,通常情况下订单将无法再进行支付操作。

6. REFUNDING(退款中)

对于已经支付成功的订单,当用户或系统发起退款请求后,订单进入退款中状态,此时需要等待第三方的退款结果。在这个阶段,系统需要与第三方进行沟通,确保退款操作的顺利进行。

7. REFUNDED(退款成功)

当第三方确认退款成功后,订单进入退款成功状态。如果是部分退款的情况,需要在订单或退款表中详细记录已退金额和剩余可退金额,以便进行后续的管理和查询。

8. CLOSED(已关闭 / 已取消)

若订单在支付成功之前被取消,例如用户主动取消订单或系统因超时自动关闭订单,订单将进入已关闭状态。一旦订单处于此状态,将不可再进行支付或退款操作。

这些状态基本涵盖了常见的支付生命周期。你可以根据实际业务场景的需求,对状态进行简化或细化。例如,如果业务场景不需要「PROCESSING」或「REFUNDING」状态,可以将其去除;若需要更加精细的退款流程,也可以进一步细化「部分退款」「多次退款」等状态。

三、典型状态流转示例详解

状态机的核心机制在于通过当前状态和触发事件来确定下一个状态。下面为大家展示一个简化的示例,清晰地呈现了常见的事件触发和状态变化情况。

以上示例仅为通用情况,实际业务中可能会根据部分退款、多次退款、多渠道回调等复杂情况进行更加精细的设计。

四、状态变更记录表的设计要点

4.1 为何需要单独的记录表

  • 审计与追溯:资金相关的业务对留痕要求极高,通过状态变更记录表,可以在事后详细查看每一次状态变化发生的时间、原因以及操作者等重要信息。关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册!这为审计工作提供了有力的支持,确保业务操作的合规性。
  • 问题排查:当用户提出投诉或系统出现故障时,状态变更记录表可以帮助我们快速还原订单的完整生命周期,定位问题的根源。通过查看状态变更的历史记录,我们可以了解订单在各个阶段的状态变化情况,找出可能存在的问题。
  • 统计分析:基于状态变更表,我们可以进行丰富的统计分析工作。例如,统计订单在各状态停留的时间分布,了解业务流程的效率;分析失败率和退款率等指标,为业务优化提供数据支持。

4.2 表结构示例

常见的状态变更记录表(也可称为 payment_status_history 或 payment_order_history 等)可以按照以下结构进行设计:

需要注意的是,一个订单从创建到完成,可能会多次变更状态,每次状态变更都需要插入一条新的记录,而不是只保留一条记录。各字段的含义如下:

  • order_id:用于区分该记录属于哪个订单,确保记录与订单之间的关联关系。
  • from_status / to_status:记录本次状态变更的起点和终点,清晰展示状态的变化过程。
  • event:具体的事件名称,如 PaymentSuccess 或 CloseOrder,明确触发状态变更的原因。
  • operator:记录是由谁或哪个系统触发了本次变更,方便后续的责任追溯。
  • remark:可以写入失败原因、第三方返回码等辅助信息,为问题排查和分析提供更多的线索。
  • create_time:记录状态变更的发生时间戳,便于进行时间维度的统计和分析。

五、项目中状态机的落地方式

5.1 手写状态机

在大多数项目中,手动编写状态机映射或状态流转表是一种常见的做法。以下是具体的实现步骤:

首先,定义支付状态和支付事件的枚举类:

然后,在代码中使用映射或 if-else / switch 逻辑来控制当前状态和事件到下一个状态的转换规则。每次更新订单状态时,需要按照以下步骤进行操作:

  1. 查询订单的当前状态。
  2. 判断是否允许触发对应的事件。
  3. 如果允许,则将订单状态更新为目标状态。
  4. 插入一条状态变更记录到 payment_status_history 表中。

以下是一个示例代码:

5.2 使用 Spring StateMachine

Spring 提供了 Spring Statemachine 库,该库可以更加系统化地管理复杂的状态、事件和转移。

它支持分层状态机、并行状态机等高级功能,还可以配置监听器在状态变更时自动将相关信息写入数据库。该库适用于状态过多、流程极其复杂或需要可视化管理的场景。

然而,如果团队对该框架不太熟悉,且业务需求不算特别复杂,手写状态机往往已经能够满足需求。

六、关键关注点解析

1. 幂等性的保障

支付回调可能会多次触发,为了确保系统的稳定性和数据的准确性,需要保证重复回调不会导致重复更新或错误更新。可以在数据库层面进行幂等校验,例如,如果订单状态已经是 SUCCESS,再次收到成功回调时可以直接忽略该请求。

2. 异常场景的处理

  • 第三方回调丢失:当第三方回调信息丢失时,订单可能会一直停留在 PENDING 状态。为了解决这个问题,需要定期主动查询第三方支付结果,确保订单状态能够及时更新。
  • 超时关闭:如果用户长时间未支付,订单可以自动从 CREATED 或 PENDING 状态转为 CLOSED 状态,以释放系统资源,避免无效订单的占用。
  • 退款失败:若第三方退款失败,订单需要回到 SUCCESS 状态,并且可以再次发起退款请求,确保用户的退款需求能够得到妥善处理。

3. 部分退款的处理

如果业务允许部分退款,需要额外记录已退金额和剩余可退金额等信息。同时,状态机也需要支持部分退款成功、多次退款等更复杂的场景,以满足业务的多样化需求。通过合理设计状态机和数据库表结构,可以确保部分退款业务的顺利进行。

4.数据一致性

通常使用数据库事务保证订单表与状态变更表的同步更新;

大规模系统可采用消息队列或分布式事务方案。

5.对账与统计

完整的支付系统还需要对账逻辑(对比第三方交易流水),并将状态变更表的数据用于审计与统计分析。

七、总结

1.状态机设计:

列出核心状态(CREATED、PENDING、SUCCESS、FAIL、REFUNDING、REFUNDED、CLOSED 等)和对应事件;

明确当前状态 + 事件 -> 下一个状态的规则,保证每次状态变更都有明确触发。

2.状态变更记录表:

建议使用多条记录的方式保存状态流转历史,每次变更都插入一条;

表中至少包含订单标识、原状态、新状态、触发事件、操作人、时间、备注等核心字段;

方便后续审计、问题排查与统计分析。

3.关键落地点:

确保幂等与异常场景处理;

考虑部分退款、多次退款等业务需求;

根据团队熟悉程度,选择手写状态机或Spring StateMachine;

在大规模或合规要求高的场景中,要特别重视审计和数据一致性。

通过以上设计,一个支付系统就能够在单体或微服务架构中实现对订单全生命周期的有效管理,保持状态清晰、有序,满足合规与审计需求,并在异常场景下依旧具备较好的可维护性和可追溯性。

文章目录
  1. 1. 一、为何支付系统需要状态机
    1. 1.1. 1. 业务复杂度的应对
    2. 1.2. 2. 提升可维护性与可审计性
    3. 1.3. 3. 有序处理异常场景
  2. 2. 二、常见支付状态的解析
    1. 2.1. 1. CREATED(已创建)
    2. 2.2. 2. PENDING(待支付)
    3. 2.3. 3. PROCESSING(支付中 / 处理中)
    4. 2.4. 4. SUCCESS(支付成功)
    5. 2.5. 5. FAIL(支付失败)
    6. 2.6. 6. REFUNDING(退款中)
    7. 2.7. 7. REFUNDED(退款成功)
    8. 2.8. 8. CLOSED(已关闭 / 已取消)
  3. 3. 三、典型状态流转示例详解
  4. 4. 四、状态变更记录表的设计要点
    1. 4.1. 4.1 为何需要单独的记录表
    2. 4.2. 4.2 表结构示例
  5. 5. 五、项目中状态机的落地方式
    1. 5.1. 5.1 手写状态机
    2. 5.2. 5.2 使用 Spring StateMachine
  6. 6. 六、关键关注点解析
    1. 6.1. 1. 幂等性的保障
    2. 6.2. 2. 异常场景的处理
    3. 6.3. 3. 部分退款的处理
    4. 6.4. 4.数据一致性
    5. 6.5. 5.对账与统计
  7. 7. 七、总结