銀行エンジニアリング 06:支払の状態遷移(Payment State Machine)
支払は「成功か失敗か」の2つではありません。MiniBank v9の9つの状態と、許可された遷移だけを通す仕組みを学びます。
v8以降に実装した台帳・記帳エンジンなどを実際に動かし、正常時と異常時の両方から銀行システムの構造を学ぶ。
Prerequisite: BANKING SYSTEMLEARNING GOALS
このページで学ぶこと
- 9つの支払状態の意味
- 許可された遷移とDBでの強制
- 指図内容を変更できない理由
PLAIN LANGUAGE
まず、ここだけつかむ
支払には9つの状態があり、許可された遷移だけがDBで通ります。
TIMEOUTとRESULT_UNKNOWNは「失敗」ではなく「分からない」状態です。
一括処理でも支払は1件ずつ独立し、部分的な失敗は他を巻き戻しません。
DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
State Machine
状態と、許される状態の移り方の決まりです。
ありえない状態の変化を防ぐためです。
interbank.transitions と guard_transition()。違反は IB010。
Immutable Instruction
作成後に変更できない指図内容です。
送った内容と記録が食い違わないようにするためです。
金額・口座・相手行の変更は IB011 で拒否。
Partial Failure
まとめて処理した一部だけが失敗することです。
1件の失敗で全体を止めない設計が必要なためです。
決済バッチの PARTIAL と RETURN_POSTING_FAILED。
Level 1 基礎:状態とは「いま分かっていること」
支払の状態は、MiniBankがその支払について「いま何を知っているか」を表します。特に TIMEOUT と RESULT_UNKNOWN は「失敗」ではなく「分からない」という状態です。
| 状態 | 意味 | お金 |
|---|---|---|
| CREATED | 受付済み | まだ動いていない |
| AUTHORIZED | 資金確保済み | 顧客預金 → 未決済送金 |
| SENT | 決済システムへ送信済み | 未決済送金のまま |
| ACCEPTED | 決済システムが受理 | 未決済送金のまま |
| SETTLED | 決済済み | 未決済送金 → 中央銀行当座預金から支払 |
| REJECTED | 不成立 | 資金確保を取消して顧客へ戻す(資金確保前なら何も動かない) |
| TIMEOUT | 期限内に応答がない | 未決済送金のまま(結果は分からない) |
| RESULT_UNKNOWN | 結果不明。照会が必要 | 未決済送金のまま |
| RETURNED | 決済後に受取銀行から返却 | 中央銀行当座預金 → 顧客預金 |
Level 2 銀行業務:許可された遷移
この一覧は interbank.transitions テーブルそのものです。表にない遷移(例:SETTLED から CREATED)はDBが IB010 で拒否します。
- 01CREATED → AUTHORIZED / REJECTED
- 02AUTHORIZED → SENT
- 03SENT → ACCEPTED / REJECTED / TIMEOUT
- 04TIMEOUT → RESULT_UNKNOWN / ACCEPTED / REJECTED
- 05RESULT_UNKNOWN → ACCEPTED / REJECTED / SENT(条件付き再送)
- 06ACCEPTED → SETTLED
- 07SETTLED → RETURNED
Level 3 システム:MiniBankでの実装
- interbank.guard_transition() トリガーが、状態変更のたびに遷移表を確認し、revision を1つ進め、追記専用の interbank.payment_events に1行残す
- 送金額・口座・相手行などの指図内容は作成後に変更できない(IB011)。変更したいなら別の支払にする
- 支払行の削除はできない。不成立も返却も、状態として残る
- 画面が出す操作は src/domain/platform/interbank.ts の availableActions() が状態から決めるが、最終判断は常にDB側の関数
異常時:一括送信で結果がばらばらになった(Partial Failure)
| 段階 | 内容 |
|---|---|
| 症状 | 「資金確保済を送信」で、受理・拒否・応答なしが混在した |
| 考え方 | 支払は1件ずつ独立して状態を持つ。1件の拒否で他の支払を巻き戻す必要はない |
| 決済での部分失敗 | 決済バッチでは、決済と返却を支払ごとの別サブトランザクションで記帳する。返却先の口座が凍結されていると返却だけが失敗し(RETURN_POSTING_FAILED)、バッチは PARTIAL になる |
| 対応 | 口座の凍結を解除して次の決済実行で返却を再処理する。決済済みの事実は巻き戻さない |
Level 4 アーキテクチャ:状態をDBで守る理由
状態の正しさをアプリの if 文だけに任せると、別の画面・バッチ・手作業のSQLから不正な遷移が入り込みます。遷移表とトリガーで守れば、どの経路から更新しても同じ規則が適用され、遷移の履歴も漏れなく残ります。
どこを確認すればよいか
- 支払詳細の「MiniBank側:状態遷移」
- 決済バッチの「返却/未確認/失敗」件数と details
CHECK YOUR UNDERSTANDING
3問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1SETTLED から CREATED へ戻せますか?
戻せません。IB010で拒否されます。
Q2返却が失敗したとき、決済済みの事実も取り消されますか?
取り消されません。決済と返却は別のサブトランザクションです。
Q3不成立の支払は削除されますか?
されません。REJECTEDとして残ります。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
SOURCE TRAIL
根拠と関連教材
CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。
確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。
Implementation Status: IMPLEMENTED IN MINIBANK(この教材が扱うコードの範囲)
- RELATED LESSON/docs/be-interbank-basics銀行間振込の登場人物
- RELATED LESSON/docs/domain-modeling業務をデータモデルへ翻訳する
- CURRENT IMPLEMENTATION
supabase/migrations/20260924110000_v9_interbank_simulator.sqlinterbank.guard_transition / interbank.reconcile状態遷移の検査と未決済送金の照合 - CURRENT IMPLEMENTATION
src/domain/platform/interbank.tsavailableActions()状態ごとに画面が出す操作 - CURRENT IMPLEMENTATION
supabase/migrations/20260924110000_v9_interbank_simulator.sqlinterbank.apply_return返却(支払ごとのサブトランザクション) - CURRENT IMPLEMENTATION
supabase/migrations/20260930130000_v17_reliability.sqlinterbank.run_settlement決済バッチ(支払ごとのサブトランザクション。v17で、書込停止中は部分失敗として記録せず RY001 で止める)