【Stripe決済設計】多通貨対応の罠とUSD限定アーキテクチャの裏側
Stripeのサンドボックス環境で、クレジット単価変更機能のテストをしていたエンジニアが、10AED(アラブ首長国連邦ディルハム)建ての商品を1件追加しました。狙いは「ドルと円以外の通貨を試したらどうなるか」という単純な確認です。結果、管理ツールにエラーが表示され続け、詳細を確認しようとしてもクリックが反応しない状態になりました。この一件が、決済基盤をUSD(米ドル)専用で組んでいた理由を改めて浮き彫りにしました。
何が起きたか
担当エンジニアがStripeの商品管理画面から手動で10AEDの商品を作成したところ、自社の管理ツール(決済状況やクレジット付与状況を可視化する社内向け画面)に次の警告が表示され、消えなくなりました。
⚠ 未解決の未登録 Price 購入: 1 件 (返金 or 換算テーブル追加)「詳細を見る」を押しても反応がなく、原因を特定できないまま翌日の調査に持ち越されました。結論から言うと、これはバグではなく、対応していない通貨のPriceオブジェクトを検知した際に想定通り止まった状態でした。ただし、なぜ止まるのか、そしてなぜ最初から通貨を制限しているのかは、決済基盤全体の設計を見ないと理解できない話です。以降でその背景を整理します。
前提となる用語
本題に入る前に、この記事で使う用語を簡単に整理します。
- Stripe Price:商品に紐づく価格オブジェクトで、通貨(
currency)と金額(unit_amount)などを保持します[1]。1つの商品に複数通貨の価格を持たせることも技術的には可能です[2]。 - Admin(管理ツール):決済状況やクレジット付与状況を社内向けに可視化する自社実装の画面。今回エラーが出たのはこの画面です。
- クレジット:ユーザーがStripeで支払った金額に応じて付与される、サービス内で消費できるポイントの単位です。
USD限定という設計判断の背景
今回のシステムは、当初からUSD専用として設計されています。AEDやJPY(日本円)を含む他通貨は、仕様として最初から受け付けません。管理画面が10AEDの商品登録を拒否したのは、設計通りの挙動でした。
理由は、クレジット付与の計算式にあります。支払われた金額をクレジットへ変換するロジックが「1ドルあたり何クレジット付与するか」という前提で組まれており、データベースの列名、計算式、画面表示に至るまでドル建てで統一されています。ここに為替レートの管理機構は存在しません。つまり、AEDやJPYの金額がそのままドルの数値として扱われた場合、支払額とクレジット付与量の対応関係が崩れる恐れがあります。
この判断は思いつきではなく、社内の設計仕様書にあらかじめ明文化されていました。最初にこの制約を聞いたときは「Stripeは135以上の通貨に対応している[3]のに、なぜ自前でここまで絞るのか」という疑問もありました。しかし為替レートの変動リスクを吸収する仕組みを持たないまま多通貨を許すより、対応していない通貨を入口で弾く方が事故の発生確率を大きく下げられます。実際にStripe公式も、複数通貨対応には為替変動リスクの軽減策としてAdaptive Pricingのような専用機構の利用を推奨しています[2]。専用の仕組みを持たずに多通貨を許容する設計は、公式が想定する運用からも外れることになります。
多通貨混在で何が壊れるのか
クレジット計算が壊れる経路は単純です。「1ドル=100クレジット」という換算ルールしか実装されていない状態で、AEDやJPYの金額がそのままこの式に投入されると、実際の支払額とは無関係な数のクレジットが付与されます。10AEDは日本円で数百円程度の金額ですが、システムが「10ドル」として処理すれば、本来の10倍前後のクレジットを不当に付与しかねません。逆に日本円建ての大きな金額(例えば10,000円)がそのまま「10,000ドル」として処理されれば、極端に過大なクレジットが発生する可能性もあります。
この種の不整合は、決済ログを個別に見ても気づきにくいという厄介さがあります。Stripe側の決済自体は正常に完了しているため、エラーとして検知されるのは「換算テーブルに存在しない通貨のPriceが購入された」というAdmin側の警告だけです。今回のケースでは、実際にAED商品に対してテスト決済が1件発生しており、それが「未解決の未登録Price購入: 1件」という警告バナーの原因になりました。この警告表示自体が判定と復旧作業のトリガーになります。処理の分岐を整理すると以下のようになります。
今回の10AED商品は、Priceオブジェクトの作成に加えて実際にテスト決済が1件発生していました。そのため、Adminが未登録通貨の購入として検知し、警告を出し続けました。為替換算テーブルという仕組みそのものが存在しないため、警告メッセージにある「換算テーブル追加」という選択肢は現状の実装では使えず、実質的には返金かレコード削除の二択になります。
USD限定設計と多通貨許容設計の比較
| 観点 | USD限定設計(採用した方式) | 多通貨許容設計(見送った方式) |
|---|---|---|
| クレジット計算式 | 1ドルあたりの固定レートのみ | 通貨ごとの為替レート換算が必要 |
| 為替変動への対応 | 不要(USD以外は入口で拒否) | レート変動を吸収する仕組みが必須[2] |
| 実装コスト | 低い(単一通貨前提のまま) | 高い(換算テーブル・レート更新バッチ等が必要) |
| 事故発生時の影響範囲 | 登録時点で拒否されるため限定的 | 誤ったクレジット付与が広範囲に波及しうる |
多通貨許容設計を選ばなかったのは、実装コストの高さそのものよりも、換算ロジックの欠落がクレジット付与量という「お金に直結する数値」の破綻に直結する点を避けたかったためです。
調査と復旧の経緯
警告が出た時点で、担当エンジニアはまずStripe側でAED商品をアーカイブする対応を試みました。アーカイブとは、Stripe上でPriceを非表示にし新規の購入を止める操作です。しかし、この操作だけではAdmin側の警告表示は消えませんでした。すでに登録済みのレコードがデータベースに残ったままだったためです。最終的に、データベース担当のエンジニアが該当レコードを直接削除したことで、警告表示は解消しました。
この経緯からわかるのは、Stripe側の操作とAdmin側のデータが独立して保持されている点です。Stripeで商品を止めても、自社DBに取り込まれたレコードは自動的には消えません。今回はテスト決済が1件発生していたため、本番環境であれば返金対応まで含めた切り分けが必要になっていたはずです。
エラー表示の改善
当初、管理画面のエラー表示は「Request failed with status code 400」という汎用的なメッセージしか表示されず、原因の特定に時間がかかりました。調査の結果、画面側の参照箇所を1行修正することで、より具体的な「invalid_currency:aed」というエラーメッセージを表示できるようになりました。この修正により、同様の問題が発生した際の原因特定が容易になり、実践的な改善につながりました。
📦 旧状態(Before)
- Stripe上に10AEDのPriceオブジェクトが存在
- Admin画面に「未解決の未登録 Price 購入: 1件」の警告が表示
- 「詳細を見る」を押しても反応がなく原因調査が進まない
- エラーメッセージは「Request failed with status code 400」のみ
✅ 新状態(After)
- Stripe側でAED商品をアーカイブし新規購入を停止
- データベース側の該当レコードを削除
- Admin画面の警告表示が解消
- エラーメッセージを「invalid_currency:aed」と具体的に表示するよう改善
現場の声
担当エンジニアより:
「お疲れ様です。すみません、まだ確認途中でしたが、・Stripe商品で円とドル以外の商品追加ができないかチェックしようとした・手動追加で10AED(アラブ首長国連邦ディルハム)のStripe商品を追加・管理ツール側でエラー表示がでて「詳細を見る」を押しても反応しない といった挙動が確認されました。」
調査担当エンジニアより:
「そもそも、このシステムは 米ドル(USD)専用として作られています。AED(アラブ首長国連邦ディルハム)や日本円(JPY)など、ドル以外の通貨は最初から受け付けない仕様です。Admin の登録画面が拒否したのは、バグではなく 設計通りの正しい動作 です。」
現状の制約・未対応の範囲
今回の対応でわかった制約をいくつか整理しておきます。まず、為替レートを管理する仕組みが存在しないため、警告メッセージに表示される「換算テーブル追加」という選択肢は現状のシステムでは実行できません。次に、Stripe側でPriceをアーカイブしても、自社データベースに取り込まれたレコードは自動的に削除されない点です。これは今回、手動でのデータベース操作が必要になった直接の原因でした。また、Admin側の登録画面ではUSD以外の通貨を拒否する一方、Stripe管理画面から直接AEDなどの商品を作成すること自体は防げていません。入口での抑止はAdmin経由の操作に限られており、Stripe側の操作を制限する仕組みは別途必要になります。
よくある質問
なぜStripe上での商品作成自体を止めなかったのですか
今回の対応はAdmin側での検知・拒否とデータベースレコードの削除にとどまっており、Stripe側での商品作成自体を制限する対応は行われていません。そのため、Stripe管理画面から直接他通貨のPriceを作成することは引き続き可能な状態です。
将来的に多通貨対応をする予定はありますか
スレッド上のやり取りからは、多通貨対応そのものよりも「対応していない通貨を安全に弾く」ことが当面の方針として確認できます。多通貨対応を進める場合は、為替レートの管理機構と換算テーブルの実装が前提になると考えられますが、その着手時期については今回の情報からは判断できません。
まとめ
今回の警告表示が消えなかった直接の原因は、Stripe上でアーカイブした後もAdmin側のデータベースに未登録通貨のレコードが残り続けていたことでした。対応としては、Stripe側でのPriceアーカイブとデータベース側のレコード削除を組み合わせることで解消しています。加えて、エラー表示を「Request failed with status code 400」から「invalid_currency:aed」のように具体的なメッセージに改善し、今後の原因特定を容易にしました。
そもそもの背景として、このシステムはクレジット付与の計算式を「1ドルあたりの固定レート」でしか実装しておらず、為替レートを管理する仕組みを持っていません。この状態で複数通貨を許容すると、支払額とクレジット付与量の対応関係が壊れる事故につながります。そのため、対応していない通貨は登録の入口で拒否するという設計を当初から採用していました。10AEDという小さなテスト商品が、その設計判断の妥当性を改めて確認する機会になった格好です。同じ境界で悩む方の参考になれば幸いです。























