Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Idempotency Keys - The Double-Charge Trap

2026年10月17日に「Dev.to」が公開したITニュース「Idempotency Keys - The Double-Charge Trap」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

ネットワークエラーなどで支払いボタンを二度押すと、二重課金される可能性がある。これを防ぐため、リクエストごとに一意の「冪等性キー」を付与する。バックエンドはこのキーで処理済みか判断し、済んでいれば同じ結果を返すことで、二重課金や重複処理を避け、安全な再試行を可能にする。

出典: Idempotency Keys - The Double-Charge Trap | Dev.to公開日:

ITニュース解説

オンラインで商品を購入する際、「購入」ボタンをクリックした後、ウェブページが応答しなくなったり、エラーメッセージが表示されたりして、思わずもう一度ボタンを押してしまった経験はないだろうか。その結果、同じ商品に対して二重に請求されてしまう「二重課金」は、ユーザーにとって非常に困惑する問題であり、サービス提供者にとっても信頼を損なう事態である。この問題の根本的な原因の一つは、決済処理が完了したにもかかわらず、その結果を伝えるサーバーからの応答(レスポンス)が、ネットワークの不具合やタイムアウト、ブラウザのフリーズなどによってユーザーの画面に届かないことにある。このような状況では、ユーザーインターフェース(UI)は処理が失敗したと判断し、ユーザーは再試行してしまう。ここで重要なのは、タイムアウトは「何が起こったか不明」を意味し、「支払いが失敗した」とは限らない点である。

この二重課金の問題を解決するために、「べき等キー(Idempotency Key)」という概念が導入される。「べき等性」とは、「同じ操作を何度繰り返しても、一度だけ実行した場合と同じ結果になる」という性質を指す。例えば、スマートフォンの通知設定を「オフ」にする操作は、一度オフにすれば何度同じ操作をしても結果は「オフ」のままであり、べき等である。しかし、銀行口座に50ドルを追加する操作は、一度実行すれば50ドル、二度実行すれば100ドルが追加されるため、そのままではべき等ではない。決済処理も同様で、通常は一度の操作につき一回の課金が発生するため、二度目の操作で二重課金が発生してしまう。

べき等キーは、この決済処理にべき等性をもたらすための特別な識別子である。ユーザーが決済を開始しようとする際、UIは一意なべき等キーを生成し、このキーを決済リクエストに含めてバックエンド(サーバー)に送信する。もし、最初の決済リクエストの応答が失われ、ユーザーが同じ決済を再試行した場合、UIは同じべき等キーを添えて再度リクエストを送信する。バックエンドは、この同じキーを持つリクエストを受け取ると、「このキーに対応する処理はすでに完了している」と判断し、新たな課金処理を行うことなく、前回の処理結果をユーザーに返す。これにより、ユーザーは何度リクエストを再送しても、課金は一度だけとなる。

この処理の具体的な流れを見てみよう。ユーザーが「支払う」ボタンをクリックすると、UIはまず、その注文に対して以前の決済試行で使用されたべき等キーがブラウザのsessionStorageに保存されていないかを確認する。既存のキーがなければ、crypto.randomUUID()のような安全な方法で全く新しい一意なキー(例: K1)を生成する。このK1は、ページがリロードされたり、ユーザーが一時的に他のタブに移動したりしても、同じタブ内であれば再利用できるように、UIからバックエンドにリクエストを送る前にsessionStorageに保存される。これにより、途中でページがクラッシュしても、次の試行で同じキーが使用されることを保証する。キーが保存されると、UIは「支払う」ボタンを無効化し、「処理中...」といったメッセージを表示して、K1を含む決済リクエストをバックエンドに送信する。

バックエンドが決済リクエストを受け取ると、最初にデータベースにK1が登録されていないかを確認する。K1がまだ登録されていなければ、バックエンドは「ユーザーID、べき等キー、注文ID、状態(processing)」といった情報をデータベースに記録し、その上で実際に決済プロバイダ(クレジットカード決済システムなど)に課金を依頼する。この際、バックエンドは決済プロバイダに対しても、K1から派生させたプロバイダ独自のべき等キーを渡し、プロバイダ側での二重課金も防ぐ。決済プロバイダからの成功応答を受け取ると、バックエンドはデータベースのK1の状態を「succeeded」に更新し、決済結果(例: 決済ID P900)を保存する。

もし、バックエンドが決済結果をUIに返す前にネットワークエラーが発生し、UIがタイムアウトした場合、UIは決済が失敗したと判断する。しかし、sessionStorageにK1が残っているため、UIは「決済状況を確認中...」と表示しながら、同じK1を添えて決済リクエストを再度バックエンドに送信する。バックエンドは、この再送されたリクエストを受け取ると、データベースでK1を検索し、その状態がすでに「succeeded」であることを確認する。この場合、バックエンドは決済プロバイダに再度課金を依頼することなく、データベースに保存されていたP900という決済結果をUIに返す。これにより、ユーザーは一度だけ課金され、決済成功の通知を受け取る。UIは最終的な決済結果が確定した時点で、sessionStorageからK1を削除する。

重要なのは、UIは決済の最終的な結果(成功、明確な失敗)が判明するまで、べき等キーをsessionStorageに保持し続けることである。タイムアウトやサーバーエラーなど、結果が不明な場合はキーを保持し、再試行時に同じキーを使うことで、安全に状態を確認できる。

より複雑なシナリオとして、ユーザーが決済処理中に注文内容(商品の数量、クーポン適用、配送先など)や支払い方法を変更したい場合がある。この場合、単に同じべき等キーを使い回すだけでは、変更された内容が反映されない可能性がある。これを解決するため、「スナップショット」という概念が導入される。べき等キーは「特定の時点の注文内容(スナップショット)に対する支払い」を表すものとなる。注文IDに加え、注文の変更ごとにバックエンドが更新する「注文バージョン(orderVersion)」や「支払い方法ID」などがスナップショットの要素となる。UIは、このスナップショットとキーを紐付けて管理し、スナップショットが同じであればキーを再利用し、スナップショットが異なれば、古い決済試行をキャンセルまたは解決してから新しいキーを生成することを求める。

システムは、これらの処理を複数の専門サービスに分けて担当させる。UI上の「キーマネージャー」がべき等キーの生成と管理を行い、ユーザー認証やリクエストの流量制御を行う「APIゲートウェイ」が窓口となる。「注文サービス」はカートの内容や注文バージョンを管理し、「決済サービス」はべき等キーのチェック、フィンガープリント(スナップショットのハッシュ値)の比較、実際の課金処理、結果の保存を行う。これらの情報は「べき等ストア」と呼ばれるデータベースに保存され、user_idとkeyの組み合わせで一意性を保証し、二重挿入を防ぐ。また、決済プロバイダからの最終的な決済結果を非同期に受け取るための「Webhookハンドラー」や、長期間「処理中」のまま止まっている決済試行を決済プロバイダに問い合わせて状態を解決する「Reconciliation Job」といった機能も組み込まれる。不要になった古いキーは「Cleanup Job」によって定期的に削除される。

決済が「処理中」のまま止まってしまった場合(例: バックエンドが決済プロバイダから応答を受け取る前にクラッシュした場合)、UIからの再試行リクエストに対して、バックエンドは「すでに処理中である」ことを示す応答を返し、新たな課金処理は行わない。これは、すでに課金が完了している可能性や、決済プロバイダでの処理が進行中の可能性があるためである。最終的な決済状態は、通常は決済プロバイダからのWebhook通知によって更新される。もしWebhookが届かない場合は、Reconciliation Jobが決済プロバイダに問い合わせ、そのべき等キー(決済サービスがプロバイダに渡したもの)を使って決済状態を確認し、データベースを更新する。これにより、ユーザーは一貫して正しい決済状態を知ることができ、二重課金の心配はなくなる。

べき等キーを利用したシステムにおいても、セキュリティは非常に重要である。UIのsessionStorageにはべき等キーのみを保存し、クレジットカード番号やCVV、その他の個人情報は決して保存してはならない。バックエンドは、受け取ったべき等キーだけでなく、リクエストを行ったユーザーがその注文の正当な所有者であるかを常に確認する必要がある。また、べき等キーはユーザーごとにスコープされ、他のユーザーの決済に影響を与えないようにする。注文の金額や決済ステータスなどの重要な情報は、常にバックエンドで管理し、UIからの値を鵜呑みにしてはならない。ユーザーがログアウトする際には、関連するべき等キーをsessionStorageからクリアすることも推奨される。

もしsessionStorageのデータが失われた場合(例えば、新しいタブを開いたり、別のデバイスで同じ注文を試みたりした場合)、UIは新しいべき等キーを生成してしまう。このシナリオでは、べき等キーだけでは二重課金を完全に防げない可能性がある。そのため、バックエンドでは追加の保護策が必要となる。「すでに支払い済み、または支払い処理が進行中の注文に対しては、新たな課金処理を開始できない」というルールを設けることで、より堅牢なシステムを構築できる。つまり、べき等キーは重複したリクエストを防ぎ、注文ステータスの管理は同じ注文に対する重複決済を防ぐ役割を担い、これらを組み合わせることで、ユーザーに安全で信頼性の高いオンライン決済体験を提供するのである。

関連コンテンツ

関連IT用語