多くの構成図では、決済はショップから「Stripe」や「Adyen」への 1 本の矢印です。しかし本番環境では、決済は関係者の連鎖であり、2 つの時間軸を持ちます。顧客がチェックアウトページで待っている間に起きることと、数分後や数日後に Webhook で届くことです。決済のバグの多くは、この 2 つの間のすき間に潜んでいます。
このガイドでは、加盟店のエンジニアの視点でカード決済を図にします。関係者、ハッピーパス、ブラウザの結果が信頼できる情報源にならない理由、オーソリのあとに起きること、そしてお金と注文の状態がどこで変わるかを示す図です。特定のベンダーには依存しません。Stripe と Adyen は、決済サービスプロバイダーが詳細をどう文書化しているかの例として登場するだけです。
関係者
| 関係者 | カード決済での役割 |
|---|---|
| 顧客 | カード会員。ブラウザやアプリで支払う |
| チェックアウトのフロントエンド | 決済サービスプロバイダー(PSP)が提供するカード入力欄を載せた、あなたのチェックアウトページ |
| 決済バックエンド | 決済を作成し、注文の状態を管理するあなたのサービス |
| PSP | 連携先のゲートウェイ。API、カード入力欄、3-D セキュア、Webhook、レポートを提供する |
| アクワイアラー | 加盟店に代わってカード決済を受け付ける銀行。多くは PSP 経由で契約する |
| カードネットワーク | アクワイアラーとイシュアーの間で、オーソリと清算を中継する |
| イシュアー | カード会員の銀行。会員を認証し、承認または拒否し、資金を確保する |
ハッピーパス
- 顧客が「支払う」を選びます。 フロントエンドがバックエンドに、注文のチェックアウト開始を依頼します。
- バックエンドが PSP で決済を作成します。 金額、通貨、注文 ID、冪等性キーを指定し、PSP のクライアントトークンをフロントエンドに渡します。
- フロントエンドが PSP の入力欄でカード情報を集めます。 多くは iframe や SDK で、カード番号はブラウザから PSP に直接送られ、あなたのサーバーには一切触れません。
- イシュアーが 3-D セキュアについて判断します。 EMVCo は 2 つのフローを説明しています。フリクションレスフローでは、イシュアーがリスク評価に基づき、顧客に何も尋ねずに承認します。チャレンジフローでは、顧客がワンタイムコードや銀行アプリなどで確認します。地域によっては、これが強力な顧客認証の要件を満たす方法です。
- PSP がオーソリを送ります。 アクワイアラーとカードネットワークを経由してイシュアーに届き、イシュアーが承認して資金を確保します。
- フロントエンドが結果を表示します。 「ありがとうございました」と表示するのは構いませんが、まだ出荷するタイミングではありません。
- PSP が Webhook を送ります。 決済のオーソリが完了したことを知らせるもので、バックエンドはそれを検証して注文を更新します。
- バックエンドが売上を確定します。 注文の出荷時、デジタル商品ならその場で行います。
- PSP が入金します。 手数料を差し引いてまとめて入金され、照合ジョブが入金と注文を突き合わせます。
カード入力欄はコンプライアンスにも関わります。EC サイトが PSP の決済フォームを埋め込むだけで、自らはカードデータを保存も処理も送信もしない場合、はるかに長い SAQ A-EP ではなく、PCI DSS の SAQ A の対象になることがあります。PCI Security Standards Council の説明によれば、PCI DSS v4.0.1 では、そのためにページを悪意のあるスクリプトから守ることも求められます。どの質問票が適用されるかは、アクワイアラーに確認してください。
ブラウザが信頼できる情報源にならない理由
顧客は 3-D セキュアの途中でタブを閉じたり、イシュアーが承認した直後に接続を失ったり、悪いタイミングでページを再読み込みしたりします。注文の状態がブラウザからの通知に依存していると、支払い済みなのに未払いのままの注文や、失敗したのに支払い済みに見える注文が出てきます。信頼できる情報源は、PSP の Webhook か API です。ほかのメッセージストリームと同じように扱いましょう。
- 署名を検証する。 PSP は Webhook リクエストに署名します。たとえば Stripe は
Stripe-Signatureヘッダーで、Adyen は HMAC 署名で署名します。検証できないものはすべて拒否します。 - すぐに応答し、処理はあとで行う。 Stripe の Webhook ガイドは、複雑な処理の前に 2xx を返すよう求めています。検証したイベントをキューに入れて応答しましょう。
- 重複を想定する。 エンドポイントは同じイベントを複数回受け取ることがあります。処理済みのイベント ID を記録し、繰り返しは読み飛ばします。
- 順番に頼らない。 Stripe は、イベントが生成された順に配信されることを保証していないと明言しています。現在の状態に合わないイベントが届いたら、PSP の API から決済を取得しましょう。
- 再送を想定する。 Stripe は本番モードで、失敗した配信を指数バックオフで最大 3 日間再試行します。応答の遅いエンドポイントには、同じイベントがあとから再び届きます。
逆方向にも同じ規律が必要です。決済を作成または変更するすべてのリクエストに冪等性キーを付け、タイムアウト後に再試行するときは同じキーを使います。そうすれば、再試行で二重に請求されることはありません。キーは永久には残りません。Stripe は 24 時間後に削除することがあり、Adyen は 7〜14 日間保持します。
オーソリのあと:売上確定、返金、チャージバック、入金
- オーソリは資金を確保し、売上確定でお金が動く。 売上確定をしなければ、オーソリは期限切れになります。オンラインのカード決済では期限はおおむね 1 週間ほどで、Stripe はカードブランドごと、また決済を誰が開始したかごとに一覧にしています。期限切れの前に売上を確定しないと、確保した資金は解放されます。
- 売上確定も失敗することがある。 Adyen は、PSP が受け付けた売上確定をカードスキームが拒否するまれなケースを文書化しています。注文の状態には、その場合の経路も必要です。
- 返金は、売上確定済みの決済に対する新しい操作です。 非同期に完了し、これも Webhook で届きます。
- チャージバックはイシュアーから来ます。 カード会員が決済に異議を申し立てたときで、数週間後になることもあります。ネットワークによっては、まず照会から始まります。照会に答えないと、勝つのが難しいチャージバックに発展することがあります。
- 入金には照合が必要です。 PSP の入金レポートや精算レポートを使い、各入金を、それが精算する決済、返金、手数料、チャージバックと突き合わせます。
図
ダイアグラムの内容
- Customer
- Acquirer, card network, issuer
- Payment service provider
- Merchant systems · no raw card data
- Checkout web app
- Payments API
- Webhook handler
- Orders + payments
- Payment events
- Reconciliation job
- 1 Pay
- 2 Start checkout
- 3 Create payment, later capture
- 4 Card details (hosted fields)
- 5 3-D Secure, authorization
- 6 Webhook: authorized
- 7 Verified event
- 8 Update order
- orders, payments
- match payouts
- payout reports, daily
図の読み方は次のとおりです。
- 実線のエッジはチェックアウトそのものです。支払い(1)、チェックアウトの開始(2)、決済の作成(3)、PSP の入力欄から PSP へのカード情報の送信(4)、PSP とカードネットワークの間の 3-D セキュアとオーソリ(5)です。
- 破線のエッジはあとで起きることです。Webhook(6)、キューに入った検証済みのイベント(7)、注文を更新する決済 API(8)です。夜間の照合ジョブは、PSP の入金レポートを読んで注文と突き合わせます。
- フレームは加盟店のシステムを示します。生のカードデータはこの中に入りません。カード番号は PSP の入力欄から PSP に直接送られます。
決済フローの描き方
- ステップに番号を振る。 決済は一連の流れです。番号があれば、別のシーケンス図がなくても順番が読み取れます。
- 同期と非同期を分ける。 チェックアウトのリクエストは実線、Webhook、キュー、バッチジョブは破線にします。
- カードデータの境界を示す。 どのコンポーネントがカード番号を見られるかを示します。ホスティッドフィールドを使うなら、自社のコンポーネントは 1 つも見られないはずです。
- 注文の状態が変わる場所を示す。 状態を管理するのは 1 つのコンポーネントにし、ブラウザではなく検証済みのイベントで更新します。
- お金の操作に名前を付ける。 オーソリ、売上確定、返金、入金は、タイミングの異なる別々のイベントです。ラベルを付けましょう。
- 重要な失敗ケースを描く。 作成時のタイムアウト(同じ冪等性キーで再試行)、重複した Webhook(重複を除去)、途中で放棄された 3-D セキュアのチャレンジ(注文を期限切れにする)、期限切れのオーソリ(再オーソリかキャンセル)です。
同じフローをシーケンスで見るには、決済フロー図のユースケースを参照してください。イベントの面はイベント駆動アーキテクチャ図のガイドを、セキュリティレビューは脅威モデリングのためのデータフロー図を参照してください。
Archboard で作る
- Google または GitHub でサインインし、図を作成します。
- 顧客用に C4 モデルパックの 人物 を、決済サービスプロバイダー用と、アクワイアラー・カードネットワーク・イシュアー用に C4 モデルパックの 外部システム ノードを 2 つ追加します。
- 加盟店のシステム用に基本パックの フレーム を追加し、その中に C4 モデルパックの コンテナ ノードを 4 つ置きます。チェックアウト用 Web アプリ、決済 API、Webhook ハンドラー、照合ジョブです。
- フレームの中に、注文と決済用としてデータパックの Postgres を、決済イベント用としてデータパックの キュー を追加します。
- エッジに順番どおり番号を振り、Webhook、キュー、照合のエッジを破線にして、線のスタイルを説明する基本パックの Markdown ブロックを追加します。
この例で使うダイアグラムノードは 11 個で、Free プランの範囲に収まります。
よくある質問
決済ゲートウェイと決済プロセッサーの違いは何ですか? ゲートウェイは、あなたのシステムが通信するインターフェースです。API、カード入力欄、Webhook がそれにあたります。プロセッサーは、取引をカードネットワークに通します。多くの PSP は、アクワイアリング契約とあわせて両方を提供しています。
ブラウザが決済成功と言ったら、注文を支払い済みにしてよいですか? 顧客には確認画面を表示してかまいませんが、注文の状態を変えるのは、検証済みの Webhook か PSP の API での確認に基づく場合だけにしましょう。
なぜ先にオーソリを取り、あとで売上を確定するのですか? 出荷したものだけを請求するためです。オーソリで資金を確保し、商品を届けるときに売上確定でお金を動かします。オーソリの有効期限に注意しましょう。
順番どおりに届かない Webhook に対応する必要はありますか? あります。ハンドラーを冪等にし、ありえない遷移を拒否するステートマシンとして注文を管理し、迷ったら PSP から最新の決済を取得しましょう。
ホスティッドフィールドを使えば PCI 準拠になりますか? 対象範囲は狭まりますが、なくなるわけではありません。自己問診票への回答と、チェックアウトページの保護は引き続き必要です。どの問診票が適用されるかはアクワイアラーに確認してください。