支付狀態機設計之一:狀態定義與轉換規則
一般的業務系統,狀態管理出錯頂多造成資料不一致,花時間修復就好;支付系統則不同,一個非法的狀態轉換,可能造成持卡人被重複扣款、退款金額錯誤、帳務無法對帳,甚至觸發監管機關的調查。 這就是為什麼金流支付產業的工程師對「狀態機」的要求,比其他後端工程師嚴格許多;不是寫個 if/else 判斷 status 就算數,而是要在 Domain 層、應用層、資料庫層三個層次都建立防護,讓非法轉換在物理上無法發生。 本文從信用卡交易出發,完整拆解一筆訂單從建立到結算的所有狀態,說明每條轉換背後的業務邏輯,並延伸到電子支付的狀態差異、非法轉換的防護設計,以及可以直接套用的 .NET 實作骨架。 本文是狀態機系列的第一篇,聚焦在信用卡訂單的完整狀態集合、每條合法轉換背後的業務觸發條件;三層防護的實作細節和電子支付的狀態機差異,則留到第二篇展開。 信用卡訂單的完整狀態集合 在開始探討轉換圖之前,我們先把每個狀態的語義定義清楚,這會比記憶狀態名稱更重要,因為面試官不會在意面試者使用了什麼英文單字,而是在意的是是否理解每個狀態代表的業務意涵。 INITIATED(已建立) 訂單剛建立,使用者已按下「確認付款」,系統已準備好發出授權請求,但尚未收到任何回應,這是所有訂單的起始狀態;在這個狀態下,資料庫應已記錄訂單金額、商品資訊、使用者 ID、時間戳記,但 authorization_code、gateway_transaction_id 這些欄位尚為空值。 AUTHORIZED(已授權) 發卡行(Issuer)回傳核准碼(Approval Code),確認持卡人的卡片有效、額度足夠,此時系統應將授權碼、授權金額、授權時間存入資料庫;AUTHORIZED是一個容易產生誤解的狀態,其代表額度被凍結但資金尚未移動,持卡人的帳戶在這個瞬間沒有任何資金流出,只是有一筆「保留額度」;這就是為什麼飯店可以先凍結 5,000 元押金,退房後才根據實際消費金額請款。 CAPTURED(已請款) 商家確認可以請款(商品出貨、服務交付),發出 Capture 請求,這一步才是正式告訴銀行「我要這筆錢」;Capture 金額的關鍵規則是可以小於或等於授權金額,但不可以超過,超過授權金額的 Capture 請求會被收單行(Acquirer)拒絕,且可能觸發持卡人的詐欺警示。 SETTLED(已結算) 收單行完成日終批次清算後,資金在 T+1 或 T+2 工作天實際從發卡行撥到收單行、再撥到商家帳戶,這是資金真正移動的時刻。 FAILED(失敗) 授權請求被發卡行拒絕(額度不足、風控攔截、卡片過期等),或在授權過程中發生技術錯誤;FAILED 是一個終止狀態,代表失敗的訂單不應在原訂單上重試,而應建立新的訂單記錄,以保持完整的稽核軌跡。 VOIDED(已取消) 在 CAPTURED 之前商家主動取消授權,Void 操作的特點是:持卡人的凍結額度立即解除,且完全沒有資金移動,這和退款有根本的差異。 REFUNDED(已退款) 在 SETTLED 之後商家對持卡人執行退款,退款是反向資金流:錢從商家帳戶流回持卡人,通常需要 3 到 5 個工作天才能出現在持卡人的帳單上。 DISPUTED(爭議中) 持卡人向發卡行申訴,主張這筆交易有問題(未收到商品、遭到詐騙、對交易不認識等);發卡行審查後,會透過卡組織通知收單行,收單行再以 Webhook 通知商家系統;進入 DISPUTED 狀態後,對應金額可能被暫時凍結,商家需在限期內提交舉證資料。 合法的狀態轉換與業務觸發條件 INITIATED → AUTHORIZED 觸發條件:發卡行回傳成功的授權碼(Response Code 00);系統在這個轉換發生時,必須同時儲存以下資料: authorization_code // 發卡行核准碼,後續 Capture 必須帶上 authorized_amount // 授權金額(可能和訂單金額不同) authorized_at // 授權時間戳(計算授權有效期的基準) acquirer_reference // 收單行參考號 card_bin // 卡號前 8 碼(用於費率計算和對帳) 授權有效期的工程含義:授權通常有 7 天的有效期(部分產業如預訂、住宿可達 30 天),系統需要一支背景排程任務,定期掃描「AUTHORIZED 超過有效期且未 Capture」的訂單,主動發出 Void 請求並更新狀態,否則持卡人的額度會被無限期佔用。 ...