はじめに
決済サービスの画面でカード情報を入力し終えると、自社サイトの「完了ページ」に戻ってきます。ここで「戻ってきた=決済成功」と判断してしまうと、実は簡単に破られてしまいます。
この記事では、決済の状態をどう管理し、どこを信用してどこを疑うのかを解説します。予約と決済という2つの状態が絡み合う部分は、宿泊予約システムでもっとも慎重な設計が必要な場所です。
「戻ってきた」を根拠にしない
完了ページのURLは誰でも開ける
決済後に戻ってくる完了ページのURLは、ブラウザのアドレス欄に直接入力すれば誰でも開けます。つまり「このページが表示された」という事実は、決済が成功した証拠にはなりません。
表示された画面は証拠にならない
完了ページを開いただけで予約を確定させる作りにすると、お金を払わずに予約が取れてしまいます。画面に何が表示されているかと、システムの中で何が起きたかは、常に別々に考える必要があります。
そこで完了ページでは、表示する前にサーバーから決済サービスへ問い合わせて、本当に登録が完了しているかを確認します。確認できて初めて「ご予約が完了しました」と表示します。
確認できないときは断定しない
問い合わせの結果、まだ処理中だったり、状況が判断できなかったりすることもあります。そんなとき「予約が完了しました」と書いてはいけません。
完了ページは「確定」「処理中」「確認できず」の3状態を持ち、それぞれ違う文言を表示します。確認できないときは「お手続きを確認しています。確認メールが届かない場合はご連絡ください」と正直に伝えます。不確かなことを断定しないのは、決済に限らず全部の機能に通じる原則です。
カード登録では「未払い」のままになる
今回はカードを登録するだけで課金しない方式なので、決済サービス上の支払いステータスは、いつまでも「未払い」のままです。ここを見て判断すると、永久に確定できません。
見るべきなのは「カード登録が成功したか」という別の項目でした。使う機能によって、確認すべき場所は変わります。ドキュメントを読み込むしかない部分です。
通知と画面の二重経路
どちらが先に届くか分からない
決済の結果は、2つの経路で自社サイトに伝わります。お客様のブラウザが戻ってくる経路と、決済サービスからサーバーへ直接届く通知(Webhook)です。
カード登録が完了
お客様が戻ってきたタイミング。遅い場合も、来ない場合もある
決済サービスから直接。確実だが数秒遅れることがある
予約管理システムへ確定を反映し、メールを送る
問題は、どちらが先に届くか分からないことです。お客様がすぐ戻ってくれば画面が先、ブラウザを閉じてしまえば通知だけ。両方から同じ確定処理が呼ばれる可能性があります。
確定処理は1回だけ走らせる
そこで、確定処理の入り口に「早い者勝ちの関門」を設けました。データベースの更新を条件付きで行い、まだ確定していない状態のときだけ確定へ進めます。
条件に合わなければ更新は行われず、その処理は何もせず終了します。同時に2つの経路から呼ばれても、実際に確定処理を行うのは1つだけ。予約確認メールが2通届く事故も、これで防げます。
「確認してから更新」ではなく「条件付きで更新」
「今の状態を読む → 未確定なら確定する」と2段階で書くと、その間に別の処理が割り込む余地が生まれます。「未確定であれば確定にする」という1回の操作で書くと、データベースが順番を保証してくれます。
通知の重複は識別子で弾く
決済サービスからの通知は、同じ内容が複数回届くことがあります。これは異常ではなく、届かなかった場合に備えた正常な再送です。
そこで通知に付いている固有のイベントIDをデータベースに記録し、同じIDが来たら「処理済み」として即座に返します。データベース側で同じIDを2つ登録できない制約をかけているので、同時に届いても片方だけが通ります。
処理に失敗したらエラーを返す
通知の処理中に問題が起きたとき、「とりあえず成功」と返してしまうと、決済サービスは再送してくれません。そこで失敗時は明確にエラーを返し、再送を促します。
重複防止の仕組みがあるので、再送されても二重処理にはなりません。重複対策があるからこそ、安心してエラーを返せるという関係になっています。
予約の状態をどう表現するか
状態は細かく分ける
予約の状態は「未確定・確定」の2つでは足りません。実際には9つの状態を用意しました。
状態を細かく分けておくと、「今この予約に何が起きているか」が一目で分かり、管理画面での対応もしやすくなります。逆に状態が粗いと、「確定しているけれど何かおかしい」という予約が埋もれてしまいます。
未処理の通知は「無視」ではなく「記録」する
決済サービスからは、自社サイトが対応していない種類の通知も届きます。これらを何も残さず捨てると、後から「あの通知は届いていたのか」を調べられません。
そこで対応していない通知も「対象外として記録した」という形でデータベースに残しています。ログを見なくても、通知の到着履歴がデータとして追える状態にしておくと、トラブル時の調査がぐっと楽になります。
まとめ
決済フローの状態管理で大事にしたのは、次の3点です。
- 画面の表示を証拠にしない — 完了ページでもサーバーから決済サービスへ確認しに行く
- 確定処理は条件付き更新で1回だけ — 2経路から呼ばれても、実行されるのは1つ
- 通知はIDで重複排除し、失敗は正直に返す — 重複対策があるから再送を歓迎できる
金額の守り方については「価格改ざん・二重課金を防ぐ決済セキュリティ」で解説しています。