銀行エンジニアリング 30:資源APIと振込の「依頼」(範囲・所有者検査・冪等)
トークンを持つアプリが何を読めて、何をしてはいけないのかを追います。特に振込は、アプリは『依頼』までで、送金を決めるのは顧客本人です。
v8以降に実装した台帳・記帳エンジンなどを実際に動かし、正常時と異常時の両方から銀行システムの構造を学ぶ。
Prerequisite: BANKING SYSTEMLEARNING GOALS
このページで学ぶこと
- 範囲(scope)と所有者検査の二重の確認
- 振込の依頼と承認を分ける理由
- 応答が消えたときの冪等な照会
PLAIN LANGUAGE
まず、ここだけつかむ
トークンを持つアプリでも、同意された範囲の、本人の情報しか読めません。範囲と所有者をDBが毎回確かめます。
振込は、アプリは『依頼』までです。資金が動くのは、顧客が既存の画面でワンタイムコードを確認したときだけです。
応答が消えたときは、新しいキーで再依頼せず、元のキーで照会します。
DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
範囲(scope)
トークンで許された操作の種類です。
必要な操作だけを許して、被害を小さくするためです。
accounts.read/balances.read/transactions.read/transfers.write。不足は OB011。
所有者検査
読もうとしている対象が、その顧客のものかを確かめることです。
IDを推測して他人の口座を読むのを防ぐためです。
口座のIDを推測しても『見つからない』(OB030)。
冪等キー
同じ依頼を何度送っても、1回分として扱うための印です。
通信が切れて再送しても、二重の依頼を作らないためです。
Idempotency-Key。同じ内容は元の依頼を返し、内容が違えば OB020。
Level 1 基礎:アプリが読めるのは、同意された範囲の、本人の情報だけ
トークンがあっても、何でも読めるわけではありません。読む前に、次を毎回確かめます。
①トークンが有効か。②その同意がまだ有効か(取り消されていないか)。③アプリが停止されていないか。④必要な範囲が同意されているか。⑤読もうとしている口座が、その顧客のものか。
| API | 必要な範囲 | 内容 |
|---|---|---|
| GET /api/open-banking/v1/accounts | accounts.read | 本人の口座の一覧。他人の口座は含まれない |
| GET .../accounts/{id}/balances | balances.read | 残高(整数の円)と時点 |
| GET .../accounts/{id}/transactions | transactions.read | 追記専用の元帳から作る取引。新しい順 |
| POST /api/open-banking/v1/transfers | transfers.write | 振込の依頼。Idempotency-Key が必須 |
| GET .../transfers/{id}、?idempotency_key= | transfers.write | そのアプリが依頼した振込の状態 |
| GET .../events | 有効なトークンなら可 | 同意ごとのイベント列 |
Level 2 銀行業務:依頼と承認を分ける
第三者のアプリは、顧客に代わって『この人に1,500円送りたい』と依頼できます。しかし、実際にお金を動かすのは、顧客がMiniBankの画面で内容を確かめ、ワンタイムコード(TOTP)で承認したときだけです。アプリの依頼だけでは、資金は1円も動きません。
依頼の状態は、PENDING_CUSTOMER_APPROVAL(顧客の承認待ち)、COMPLETED、FAILED、EXPIRED、CANCELLEDなどです。顧客が何もしなければ期限で EXPIRED になります。
Level 3 システム:MiniBankでの実装
- public.ob_gateway は匿名でも呼べる入口だが、openbanking.resolve_token が毎回、トークン→同意→顧客とクライアントの状態を確かめる。無効なトークンはすべて OB010、範囲の不足は OB011。他人の口座IDは『見つからない』(OB030)
- 振込の依頼(openbanking.initiate_transfer)は、既存の public.request_transfer を呼ぶ。限度額・KYC・宛先の検査・呼出し回数の制限・冪等性は、既存の契約のまま。API側では再実装しない
- 同じ Idempotency-Key と同じ内容は、元の依頼をそのまま返す。同じキーで内容が違えば OB020(409)。新しいキーで再依頼しない
- 依頼の状態はイベント(transfer.initiated、transfer.status_changed)として、状態の変更と同じトランザクションで記録する
- アプリは依頼の承認も取消もできない。承認のAPIは存在せず、承認は顧客の既存の操作だけ
- トークンは監査ログにも残さない。監査には client と consent の識別子を入れ、外部アプリ経由であること(via=open_banking)も残す
- 1つの同意が残せる未承認の依頼は3件まで(OB021)。外部のアプリが、顧客本人の振込の依頼枠(1分5件)を使い切れないようにするため
- 同意が失効したあとは、そのアプリの依頼の状態変更をイベントにしない(顧客の承認は通常どおり完了する)
異常時:依頼の応答が届かなかった
| 段階 | 内容 |
|---|---|
| 症状 | アプリが振込を依頼したが、通信が切れて結果が分からない |
| やってはいけないこと | 新しいIdempotency-Keyで再依頼する(同じ振込が2件の依頼になる) |
| 正しい対応 | 元のキーで GET /transfers?idempotency_key=... を呼ぶ、または同じキー・同じ内容でPOSTし直す |
| 確認 | 依頼は1件のまま。状態がPENDING_CUSTOMER_APPROVALなら、顧客の承認を待つ |
Level 4 アーキテクチャ:既存の契約を再利用する
新しい入口ができるたびに、限度額やKYCの検査を作り直すと、入口ごとに抜けが生まれます。MiniBankは、外部からの依頼も、顧客本人が画面から行う依頼と同じ関数を通します。検査は1か所にあり、テストも1か所です。
どこを確認すればよいか
- 管理画面「Open Banking」の同意とイベント
- 顧客画面「振込の依頼と結果」(/app/transfer/requests):アプリが依頼した振込を、認証アプリ(TOTP)で承認する。心当たりのない依頼は承認せず、期限で失効させる
CHECK YOUR UNDERSTANDING
3問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1アプリが振込を依頼すると、残高は減りますか?
減りません。顧客が承認するまで、資金は動きません。
Q2別のアプリのトークンで、他のアプリが依頼した振込を見られますか?
見られません。そのアプリが依頼した振込だけです。
Q3依頼の応答が届かなかったとき、どうしますか?
元のキーで照会するか、同じキー・同じ内容で再送します。新しいキーは使いません。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
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-openbanking-oauth-consent同意とトークンの発行
- RELATED LESSON/docs/be-openbanking-events-webhooksイベントとWebhook
- RELATED LESSON/docs/transfer振込の依頼と確認(OTP/TOTP)
- CURRENT IMPLEMENTATION
supabase/migrations/20260930120000_v16_open_banking.sqlpublic.ob_gateway / openbanking.resolve_token匿名の入口。Bearerトークン→同意→顧客をDBが毎回解決し、所有者・scope・同意・クライアントの状態を確かめる - CURRENT IMPLEMENTATION
supabase/migrations/20260930120000_v16_open_banking.sqlopenbanking.initiate_transfer / openbanking.sync_transfer振込の依頼は既存のrequest_transferを呼ぶ。確認は顧客本人。冪等キーと状態のイベント化 - CURRENT IMPLEMENTATION
scripts/regression/suites/privilege.mjs匿名が実行できる関数の許可リスト(ob_gateway、ob_token の2つだけ)。ほかを匿名に開けると権限スイートが失敗する - CURRENT IMPLEMENTATION
scripts/banking/v16-openbanking.test.mjs登録・認可・PKCE・コード再使用・スコープ・所有者検査・同意の失効・振込の依頼と確認・イベント/Webhook・秘密の非保存のDBテスト