連打された
「購入ボタン」の悲劇
「あれ、反応しないな?」カチカチ...
たった数回のクリックが、ユーザーのクレジットカード明細を
同じ金額の請求で埋め尽くすことになりました。
- 送信ボタンを押しても、「処理中」のアニメーションが出ない
- 送信ボタンを一度押した後も、ボタンが押せる状態の(グレーにならない)ままである
- 完了画面が出る前にブラウザの「戻る」ボタンを押すと、エラーになる
- 過去に「二重登録されてます」という問い合わせを受けたことがある
ユーザーに金銭的被害を与える可能性があり、訴訟リスクに直結します。
01. インターネットは不安定である
ID: 001
金額: ¥10,000
[決済完了]
ID: 002
金額: ¥10,000
[決済完了]
「ボタンを押せば、すぐに画面が切り替わる」
それは開発環境(ローカル)だけの話です。
地下鉄の中や、電波の悪いカフェ、混雑したWi-Fi環境では、ボタンを押してから反応が返ってくるまでに数秒〜数十秒かかることはザラにあります。
この「空白の時間」にユーザーは何をするでしょうか?
「押せてないのかな?」と不安になり、もう一度ボタンを押します。
その結果、システムは律儀に「2回目の購入処理」を実行してしまいます。
悪意などありません。ただユーザーは不安だっただけなのです。
という性善説(願望)を捨てろ。
02. 二重決済を防ぐ3つの防壁
「連打禁止」と画面に書くのは対策ではありません。
システム側で物理的に防ぐ必要があります。
対策A:ボタンの無効化(フロントエンド)
最も基本かつ効果的な対策です。
ボタンがクリックされた瞬間に、JavaScriptでボタンを「無効化(Disabled)」状態にします。
また、ボタントップに「処理中...」などのグルグル回るアイコン(スピナー)を表示することで、
「ちゃんと動いていますよ」とユーザーを安心させることができます。
対策B:トークンによる制御(バックエンド)
画面を表示した時に、システム内部で「合言葉(ワンタイムトークン)」を発行しておきます。
ボタンが押されたら、その合言葉をサーバーで確認し、一度使われた合言葉は無効にします。
万が一2回目のリクエストが来ても、「この合言葉は使用済みです」としてエラーにすることができます。
対策C:ユニーク制約(データベース)
最後の砦です。
注文データなどに「同じ注文IDなら1つしか登録できない」という制約(ユニーク制約)をデータベースにかけておきます。
こうすれば、プログラムがミスをして2回登録しようとしても、データベース側で「重複エラー」として弾くことができます。
03. 信頼は「一瞬」で消える
ユーザーは「ちゃんと購入できたかな?」とドキドキしながらボタンを押します。
その緊張感の中で「二重決済」という裏切りが発生すれば、もう二度とそのサイトは使われません。
「当たり前のことを、当たり前に処理する」。
決済システムにおける最低限の品質保証なのです。