订单状态图指南
订单状态图是什么、一条订单流程该有哪些状态,以及三张可以直接改的模板:电商、SaaS 订阅、外卖。
一、什么是订单状态图
订单状态图 画的是一张订单一生的状态:它可能处于哪些状态,以及什么事件会把它从一个状态推到下一个。它回答两个文档写不清楚的问题 —— 一共有哪些状态,和 哪个状态之后能接哪个。
关键区别在于:订单状态图画的是 状态,不是步骤。步骤是有人做的事(「打包」),状态是订单待着的那个处境,一直待到有事发生为止(「处理中」)。订单一生大部分时间都在待着 —— 等付款到账、等快递揽收、等退货期结束。画状态能把这些等待显出来,画步骤会把它们藏起来。
大部分团队其实已经有这张图了,只是没画出来。它散在一个 enum、一个 status 字段、和十几处 if 里。这几处一旦对不上 —— 接口说 SHIPPED,邮件表里写 in_transit,客服话术叫「已发出」—— 就会出一批谁也复现不了的 bug。把状态摆到一页纸上,是在客户之前先发现这种对不上的唯一办法。
顺手的画法是 Mermaid 的 stateDiagram-v2:方框是状态,箭头是流转,箭头上的字是触发它的事件。
二、订单流程里的常见状态
几乎所有行业的订单流程,都是同样 5 个状态加 1 个逃生口的变体:
- 待支付(Pending) —— 订单已经存在,但钱还没到位。这一步还没有任何实际动作发生。超时逻辑住在这里。
- 已确认(Confirmed) —— 付款已授权、库存已锁定。承诺从这一刻起是双向的。
- 处理中(Processing) —— 有人在实际处理它:拣货、打包、打面单。
- 已发货(Shipped) —— 东西已经离开你的控制,在承运商手里。你只能观察,不能干预。
- 已送达(Delivered) —— 客户拿到了。注意这在多数生意里 不是 终态 —— 退货和退款都在它后面。
- 已取消(Cancelled) —— 逃生口。从好几个地方都能到达,而且来路不同、原因不同(支付超时 / 客户主动取消 / 缺货)。
这就是那个骨架。它是刻意画到最小的 —— 从它开始,只有当你能说清「什么事件会到达这个状态」时才往上加。
看 Mermaid 源码
stateDiagram-v2
[*] --> Pending
Pending --> Confirmed: payment authorized
Confirmed --> Processing: warehouse picks up
Processing --> Shipped: handed to carrier
Shipped --> Delivered: customer signs
Delivered --> [*]
Pending --> Cancelled: payment failed or timeout
Confirmed --> Cancelled: customer cancels
Cancelled --> [*]三、订单状态图怎么画
三步。前两步是想,第三步才是动手。
第 1 步 · 把状态列全
别靠头脑风暴。去读 enum、读 status 字段、读你们发出去的那些交易邮件。这三处才是系统 真实 拥有的状态,而且它们通常互相对不上 —— 这个对不上就是这件事第一份价值。
然后对每个候选状态套一个判断:订单能在这里一直待着吗? 能待就是状态;如果总是毫秒级穿过去,那是步骤,该写在箭头上而不是画成方框。「扣款中」不是状态,「待支付」才是。
留意几个团队反复忘掉的状态:退款中(钱在路上,订单既没完成也没取消)、部分发货(多商品订单)、包裹丢失(承运商不再扫码了)、风控挂起。每一个漏画的,后面都会变成一批工单。
第 2 步 · 定义流转
每条箭头上写触发它的 事件,而不是结果状态。Pending --> Confirmed: 支付已授权 是有用的,Pending --> Confirmed: 确认 是句废话。
画完之后跑三遍检查:
可达性。 每个状态至少有一条进来的箭头,而且从 [*] 出发都能走到。走不到的状态就是 status 枚举里的死代码。
终止性。 每个状态要么能往下走,要么明确是终态(--> [*])。一个出不去的状态意味着订单永远卡在那里 —— 这是这张图能抓到的、最常见的一个真 bug。
没有隐藏的回边。 如果客服能把订单从 已发货 退回 处理中,就把那条箭头画出来。没画出来的后台操作,正是状态机烂掉的方式。
第 3 步 · 画出来
把状态和流转当普通句子写出来,让 text2diagram 去排版 —— 你写「订单初始是待支付,支付授权后变成已确认,然后是处理中、已发货、已送达;待支付和已确认状态下都可以取消」,它会还给你一段可以直接改的 stateDiagram-v2。
或者干脆不用打字:本页任何一张图都能一键在编辑器里打开,然后把状态名换成你自己的。这通常比从空白画布开始快 —— 费脑子的是图的形状,不是里面的词。
四、三张真实场景的订单状态图
三个真实形状。同一个六状态骨架,一旦业务具体起来,弯法完全不同。
例 1 · 电商订单
这里的复杂性全在 送达之后。已送达 是个中途站,不是终点:退货、退款、丢件理赔要么从它分出去,要么从 已发货 分出去。把 已送达 当终态的团队,最后都在状态机外面用表格处理退款。
看 Mermaid 源码
stateDiagram-v2
[*] --> Pending
Pending --> Confirmed: payment captured
Pending --> Cancelled: 30 min timeout
Confirmed --> Processing: stock reserved
Confirmed --> Refunding: customer cancels
Processing --> Shipped: tracking number issued
Shipped --> Delivered: proof of delivery
Shipped --> Lost: no scan for 14 days
Delivered --> ReturnRequested: within 7 days
ReturnRequested --> Refunding: return received
Refunding --> Refunded: money back
Lost --> Refunding: claim approved
Delivered --> [*]
Refunded --> [*]
Cancelled --> [*]例 2 · SaaS 订阅生命周期
订阅是一张永不结束的订单,所以电商图里是直线的地方,它是 环。逾期未付 --> 生效中(催收重试成功)和 已取消 --> 生效中(挽回)是最常被漏掉的两条箭头,而它们恰好是增长团队最关心的两条。
还要注意 取消中 和 已取消 是两个状态:用户已经提了退订,但付费周期结束前访问权还在。把这两个并成一个,就是「客户明明付到月底却被提前掐掉」的成因。
看 Mermaid 源码
stateDiagram-v2
[*] --> Trialing
Trialing --> Active: card charged
Trialing --> Expired: trial ends, no card
Active --> PastDue: renewal payment fails
PastDue --> Active: retry succeeds
PastDue --> Cancelled: 3 retries fail
Active --> Cancelling: user cancels
Cancelling --> Active: user resubscribes
Cancelling --> Cancelled: period ends
Cancelled --> Active: user comes back
Expired --> [*]
Cancelled --> [*]例 3 · 外卖订单
这张图有两点不一样。一是有 三方 —— 顾客、商家、骑手 —— 好几次流转的触发者既不是买家也不是你的系统。二是整件事 40 分钟内跑完,顾客是盯着状态实时变化的,任何一个没写清楚的状态都是一通客服电话。
商家拒单 值得单开一个方框,而不是并进 已取消:钱已经付了、说不的是商家,退款路径和话术都不一样。
看 Mermaid 源码
stateDiagram-v2
[*] --> Placed
Placed --> Accepted: restaurant confirms
Placed --> Rejected: restaurant is closed or busy
Accepted --> Preparing: kitchen starts
Preparing --> ReadyForPickup: food is bagged
ReadyForPickup --> PickedUp: courier collects
PickedUp --> Delivered: handed to customer
PickedUp --> Undeliverable: nobody answers
Rejected --> [*]
Delivered --> [*]
Undeliverable --> [*]图和 enum 要在同一个 PR 里改
订单状态图只有在保持为真的时候才值得画。这里的源码就是一段 Mermaid 纯文本,所以实际做法是把它和拥有 status 枚举的那份代码放在一起,改状态时同一个改动里一起改。落后一个版本的图比没有图更糟,因为大家会信它。
常见问题
订单状态和订单流程有什么区别?
订单状态 是某一刻的一个值 —— 这张订单现在 是 什么(
已发货)。订单流程 是整张图 —— 所有状态,加上它们之间所有合法的移动。换个说法:状态回答「我的订单在哪」,流程回答「它接下来会怎么样」。订单状态图画的是流程,状态只是图里的一个方框。这个区别在代码里是实的:状态是一个字段,流程是决定「哪些对这个字段的写入是允许的」那套规则。一张订单该有多少个状态?
多数生意 5 到 8 个;超过 12 个基本可以肯定,里面混进了伪装成状态的步骤。判断标准还是第 1 步那条:订单能在这里一直待着吗?「待支付」能,「扣款中」不能。真的超过 12 个,通常的解法是把多出来的细节挪到第二个字段(在
status旁边加一个fulfillment_stage),而不是继续加顶层状态 —— 因为每加一个状态,变多的是要推敲的流转,不只是要画的方框。已取消和已退款该合成一个状态吗?
钱真的动过的话,不该。
已取消是订单在你欠钱之前就停了;已退款是你欠了并且还了。两者的账务、话术、可逆性都不一样。分开还有一个好处:中间能放下退款中—— 退款已发起但还没到账的那段,没有这个框,工单就堆在那儿。订单该用状态图还是流程图?
订单本身用状态图。流程图画的是有人从上到下跑一遍的过程;订单不是从上到下跑的,它待在某个状态里,被事件推来推去,有时候还往回推。如果你还想画仓库那套 履约作业,那再配一张流程图 —— 那个确实是一串步骤。两张图,回答两个问题。
能导出吗?能放进我们的文档吗?
可以 —— 导出 SVG、PNG,或者 Mermaid 源码。放文档最值得用的是源码:GitHub、GitLab、Notion 和大部分静态站点生成器都原生渲染 Mermaid 代码块,粘文本能让这张图在 code review 里可 diff,而不是变成一张没人能改的过期图片。
免费吗?
免费。没登录每天 20 次,登录后每天 500 次。把本页任何一张模板图在编辑器里打开则完全不计次 —— 那条路径不打模型。