はじめに
ある日突然、PMやクライアントから「サービスにクレジットカード決済を導入したい」と言われたら、あなたはどうしますか?
「Stripeとか使えば一瞬でしょ?」
そう思うかもしれません。確かに、現代の決済代行サービス(Payment Gateway)のSDKは非常に優秀で、数行のコードで決済フォームを表示できます。
しかし、決済システムの実装は 「正常に完了した時」よりも「何か問題が起きた時」 に真価が問われます。
- ユーザーが決済ボタンを2回連打したら?
- 決済処理中にサーバーが落ちたら?
- 「払ったのに商品が届かない!」とクレームが来たら?
これらの問題に対処するには、SDKの向こう側にある「決済の仕組み」そのものを理解しておく必要があります。本記事では、ブラックボックスになりがちなクレジットカード決済の裏側を、エンジニア視点で分かりやすく解き明かします。
第1章:決済の世界地図を描く(5つのアクター)
クレジットカード決済は、あなたのサービスとユーザーの間だけで完結するものではありません。裏では主に5つのプレイヤーがバケツリレーのように情報を回しています。
ここを理解していないと、エラーが起きた時に「誰が原因なのか」が全く分かりません。
登場人物紹介
- User (Holder / 会員): クレジットカードを使って買い物をする人。
- Merchant (加盟店): あなたのサービス。商品を販売し、代金を受け取る側。
- Payment Gateway (PG / 決済代行会社): Stripe, SBペイメントサービス, GMO-PGなど。Merchantとカード業界をつなぐ仲介役。エンジニアが直接やり取りするのはここ。
- Acquirer (アクワイアラ / 加盟店契約会社): Merchantを審査し、カードの取り扱いを許可する会社。売上の入金もここから行われる。
- Issuer (イシュア / カード発行会社): Userにカードを発行している会社(楽天カード、三井住友カードなど)。最終的にユーザーの口座からお金を引き落とす権限を持つ。
お金の流れと情報の流れ
ユーザーがカード番号を入力した瞬間、情報は PG → Acquirer → Visa/Mastercard等の国際ブランドネットワーク → Issuer へと瞬時に駆け巡り、「このカードは有効か?」「残高はあるか?」がチェックされます。
【ここに図解を入れる】
第2章:どうやって実装する? 2つの主要パターン
「決済を導入する」と言っても、実装方法には大きく分けて2つのパターンがあります。セキュリティ要件とUX(ユーザー体験)に関わる重要な選択です。
1. リンク型(リダイレクト型)決済
ユーザーが決済ボタンを押すと、PGが用意した外部の決済画面に遷移(リダイレクト)し、そこでカード情報を入力する方法。
- メリット: 実装が一番簡単。自社サーバーでカード情報を一切扱わないため、セキュリティ負担が非常に軽い。
- デメリット: 画面遷移が発生するため、UXが少し損なわれる(カゴ落ちのリスクが上がる)。
2. トークン型(JavaScript型)決済
自社の決済ページ上に、PGが提供するJavaScript(SDK)を使ってカード入力フォームを埋め込む方法。
- メリット: 画面遷移がなく、シームレスなUXを提供できる。現代の主流。
- デメリット: リンク型よりは実装工数がかかる。
重要: どちらの方式でも、現代のWeb開発では**「自社サーバーに生(なま)のカード番号を通過させない(非保持化)」**が鉄則です。トークン型では、ブラウザから直接PGにカード情報を送り、代わりに「トークン」と呼ばれる乱数を受け取ります。自社サーバーはこのトークンを使って決済をリクエストします。
第3章:決済のライフサイクル「オーソリ」と「キャプチャ」
エンジニアが最も混同しやすく、かつ重要な概念が「オーソリ」と「キャプチャ」の分離です。これを理解していないと、トラブルの元になります。
オーソリ (Authorization / 与信枠確保)
「このカードは使えるか?」「1万円の買い物をする残高(利用枠)はあるか?」をIssuerに確認し、OKであればその利用枠をキープ(確保)する処理です。
この時点では、まだユーザーの口座からお金は引き落とされませんし、あなたの会社の売上としても確定していません。
キャプチャ (Capture / 売上確定)
オーソリでキープしておいた枠を、実際に**「売上」として確定させる処理**です。この処理が行われて初めて、カード会社から加盟店への入金が約束され、ユーザーへの請求が確定します。
なぜ分かれているのか?
ECサイトを例に考えると分かりやすいです。
- 注文時(オーソリ): ユーザーが「注文確定」ボタンを押したタイミング。在庫を確保し、カードの利用枠も確保します。
- 発送時(キャプチャ): 実際に商品を倉庫から発送したタイミング。ここで初めて売上を確定させます。
もし、オーソリと同時にキャプチャ(即時売上)してしまうと、**「注文を受けたが在庫がなくてキャンセル」**という場合に、ユーザーにはすでに請求が発生してしまっており、面倒な返金処理が必要になります。オーソリだけなら、枠を開放(キャンセル)するだけで済み、ユーザーへの実害がありません。
第4章:【実録】エンジニアが陥る「決済の落とし穴」
理論は分かりましたが、現場では何が起きるのでしょうか?よくある「失敗エピソード」を紹介します。(※これは架空のエピソードですが、現場で非常によくある話です)
エピソード1:テスト環境の罠
開発時、PGが提供するテスト用カード番号で何度も決済テストを行い、完璧に動作することを確認。「よし、リリースだ!」と本番環境に切り替えた直後、実際のユーザーから「決済エラーになる」との報告が多発。
原因: テスト環境では甘めに設定されていたセキュリティチェック(3Dセキュア認証など)が、本番環境では厳格に適用されていたため。
教訓: テスト環境での成功過信は禁物。本番に近い条件(特にエラー系のテスト)を網羅する必要がある。
エピソード2:Webhookの再送による二重処理
決済完了を知らせるPGからのWebhookを受け取り、DBのステータスを「入金済み」にして商品付与のバッチを動かす実装をしていた。ある日、ネットワークの瞬断で自社サーバーがWebhookに対して一度エラーを返してしまった。
PGの仕様では「Webhookが失敗したら一定時間後に再送する」となっていたため、数分後に同じWebhookが再送されてきた。サーバーは復旧していたので正常に処理したが、プログラムは「同じ決済通知が2回来た」ことを考慮しておらず、商品を二重に付与してしまった。
教訓: Webhookは「複数回届く可能性がある」前提で実装しなければならない(冪等性の担保)。
第5章:堅牢なシステムのための基本設計
失敗エピソードを踏まえ、初心者が意識すべき設計のポイントを2つだけ紹介します。
1. ステート(状態)管理を徹底する
決済取引には必ず「状態」があります。DBには、単に決済IDだけでなく、現在のステータスを管理するカラムが必要です。
-
pending: 決済処理中(オーソリ待ち) -
authorized: オーソリ成功(枠確保済み) -
captured: 売上確定済み -
failed: 決済失敗 -
canceled: キャンセル済み
Webhookを受け取った時も、必ず現在のDBの状態を確認し、「pending の時だけ authorized に更新する」といった遷移のバリデーションを行うことで、不正な状態遷移や二重処理を防げます。
2. 冪等性(Idempotency)を意識する
エピソード2で触れた通り、「同じリクエストが複数回来ても、結果が同じになる(副作用が一度しか起きない)」性質を**冪等性(べきとうせい)**と呼びます。
決済APIをコールする際、ネットワークエラーでタイムアウトした場合、エンジニアは「決済は成功したのか?失敗したのか?」が分かりません。
多くのPGでは、リクエストヘッダーに一意な「注文ID」や「冪等性キー(Idempotency-Key)」を含めることで、万が一同じキーで再試行しても、PG側が「これはさっき処理したリクエストだ」と判断し、前回と同じ結果を返してくれる仕組みがあります。これを活用しましょう。
おわりに:決済は「信頼」のインフラ
クレジットカード決済の導入は、単に機能を追加するだけでなく、ユーザーの大切なお金を預かるという「責任」を負うことです。
仕組みは複雑に見えますが、登場人物の役割と、オーソリ/キャプチャのフロー、そして「通信は失敗するものである」という前提さえ押さえておけば、恐れることはありません。
この記事が、あなたのサービスの信頼性を高める一助となれば幸いです。