Webhookとステータス管理で作る壊れない決済フロー

「決済ページから戻ってきた」を信用しない。状態遷移と通知の二重処理を防ぐ設計

Webhookステータス管理冪等性予約確定状態遷移
読了時間: 9分

はじめに

決済サービスの画面でカード情報を入力し終えると、自社サイトの「完了ページ」に戻ってきます。ここで「戻ってきた=決済成功」と判断してしまうと、実は簡単に破られてしまいます。

この記事では、決済の状態をどう管理し、どこを信用してどこを疑うのかを解説します。予約と決済という2つの状態が絡み合う部分は、宿泊予約システムでもっとも慎重な設計が必要な場所です。

「戻ってきた」を根拠にしない

完了ページのURLは誰でも開ける

決済後に戻ってくる完了ページのURLは、ブラウザのアドレス欄に直接入力すれば誰でも開けます。つまり「このページが表示された」という事実は、決済が成功した証拠にはなりません。

そこで完了ページでは、表示する前にサーバーから決済サービスへ問い合わせて、本当に登録が完了しているかを確認します。確認できて初めて「ご予約が完了しました」と表示します。

確認できないときは断定しない

問い合わせの結果、まだ処理中だったり、状況が判断できなかったりすることもあります。そんなとき「予約が完了しました」と書いてはいけません。

完了ページは「確定」「処理中」「確認できず」の3状態を持ち、それぞれ違う文言を表示します。確認できないときは「お手続きを確認しています。確認メールが届かない場合はご連絡ください」と正直に伝えます。不確かなことを断定しないのは、決済に限らず全部の機能に通じる原則です。

カード登録では「未払い」のままになる

今回はカードを登録するだけで課金しない方式なので、決済サービス上の支払いステータスは、いつまでも「未払い」のままです。ここを見て判断すると、永久に確定できません。

見るべきなのは「カード登録が成功したか」という別の項目でした。使う機能によって、確認すべき場所は変わります。ドキュメントを読み込むしかない部分です。

通知と画面の二重経路

どちらが先に届くか分からない

決済の結果は、2つの経路で自社サイトに伝わります。お客様のブラウザが戻ってくる経路と、決済サービスからサーバーへ直接届く通知(Webhook)です。

決済結果が伝わる2つの経路
決済サービス

カード登録が完了

ブラウザが完了ページへ

お客様が戻ってきたタイミング。遅い場合も、来ない場合もある

サーバーへ通知

決済サービスから直接。確実だが数秒遅れることがある

どちらが先でも同じ結果になるように
予約確定処理

予約管理システムへ確定を反映し、メールを送る

問題は、どちらが先に届くか分からないことです。お客様がすぐ戻ってくれば画面が先、ブラウザを閉じてしまえば通知だけ。両方から同じ確定処理が呼ばれる可能性があります。

確定処理は1回だけ走らせる

そこで、確定処理の入り口に「早い者勝ちの関門」を設けました。データベースの更新を条件付きで行い、まだ確定していない状態のときだけ確定へ進めます。

条件に合わなければ更新は行われず、その処理は何もせず終了します。同時に2つの経路から呼ばれても、実際に確定処理を行うのは1つだけ。予約確認メールが2通届く事故も、これで防げます。

通知の重複は識別子で弾く

決済サービスからの通知は、同じ内容が複数回届くことがあります。これは異常ではなく、届かなかった場合に備えた正常な再送です。

そこで通知に付いている固有のイベントIDをデータベースに記録し、同じIDが来たら「処理済み」として即座に返します。データベース側で同じIDを2つ登録できない制約をかけているので、同時に届いても片方だけが通ります。

処理に失敗したらエラーを返す

通知の処理中に問題が起きたとき、「とりあえず成功」と返してしまうと、決済サービスは再送してくれません。そこで失敗時は明確にエラーを返し、再送を促します。

重複防止の仕組みがあるので、再送されても二重処理にはなりません。重複対策があるからこそ、安心してエラーを返せるという関係になっています。

予約の状態をどう表現するか

状態は細かく分ける

予約の状態は「未確定・確定」の2つでは足りません。実際には9つの状態を用意しました。

状態を細かく分けておくと、「今この予約に何が起きているか」が一目で分かり、管理画面での対応もしやすくなります。逆に状態が粗いと、「確定しているけれど何かおかしい」という予約が埋もれてしまいます。

未処理の通知は「無視」ではなく「記録」する

決済サービスからは、自社サイトが対応していない種類の通知も届きます。これらを何も残さず捨てると、後から「あの通知は届いていたのか」を調べられません。

そこで対応していない通知も「対象外として記録した」という形でデータベースに残しています。ログを見なくても、通知の到着履歴がデータとして追える状態にしておくと、トラブル時の調査がぐっと楽になります。

まとめ

決済フローの状態管理で大事にしたのは、次の3点です。

  1. 画面の表示を証拠にしない — 完了ページでもサーバーから決済サービスへ確認しに行く
  2. 確定処理は条件付き更新で1回だけ — 2経路から呼ばれても、実行されるのは1つ
  3. 通知はIDで重複排除し、失敗は正直に返す — 重複対策があるから再送を歓迎できる

金額の守り方については「価格改ざん・二重課金を防ぐ決済セキュリティ」で解説しています。