訂單系統最常見的 bug,不是某個畫面寫錯,而是**狀態流轉沒有被嚴格約束**:已出貨的訂單又被改回待付款、已退款的訂單再次扣款、取消後仍然發貨。這些都源自把「狀態」當成一個可任意覆寫的欄位,而不是一台**只能依規則轉移的狀態機**。
更新於 2026/06/18
為什麼訂單要用「狀態機」來建模?
訂單系統最常見的 bug,不是某個畫面寫錯,而是狀態流轉沒有被嚴格約束:已出貨的訂單又被改回待付款、已退款的訂單再次扣款、取消後仍然發貨。這些都源自把「狀態」當成一個可任意覆寫的欄位,而不是一台只能依規則轉移的狀態機。
狀態機的核心是三件事:
- 狀態(State):訂單在某一刻的處境,例如「待付款」。
- 事件(Event):外部觸發,例如「使用者完成付款」「金流回拋成功」。
- 轉移(Transition):在某狀態下收到某事件,才允許走到下一個狀態;其餘一律拒絕。
核心狀態與流轉
一筆訂單從建立到結束,會經過以下主要狀態:
- 待付款(Pending) —— 訂單已建立、尚未收款。可因逾時自動轉「已取消」。
- 已付款(Paid) —— 金流確認收款,等待出貨。
- 已出貨(Shipped) —— 商品交付物流,進入配送。
- 已完成(Completed) —— 買家簽收或鑑賞期結束,交易定案。
- 已取消(Cancelled) —— 付款前由買家或系統終止。
- 已退款(Refunded) —— 付款後退回款項,交易反向結束。
關鍵設計原則
- 終態不可逆:
已完成、已取消、已退款為終態,不允許再轉出任何狀態。 - 取消有時間邊界:只有
待付款/已付款(未出貨)能取消;一旦已出貨,就只能走退貨退款流程而非直接取消。 - 退款需先有收款:
已退款只能從已付款或已出貨進入,不可能從待付款直接退款。 - 每次轉移都要留痕:狀態變更應寫入事件日誌(誰、何時、因何事件),便於對帳與爭議處理。
延伸思考
- 並發與冪等:金流回拋可能重送,狀態機要保證「已付款 → 已付款」是冪等的、不會重複出貨。
- 逾時的自動轉移:
待付款的取消多半由排程觸發,屬於系統事件而非使用者事件。 - 退貨流程的展開:實務上
已出貨 → 已退款之間常還有「退貨申請中/退貨運送中」等子狀態,可視需求再細分。
讀完這篇了嗎?
建立於 2026/06/18 ai-內容生成演示系列-訂單狀態機.mdx