結論
冪等性は「高級な最適化」ではなく、決済 API を本番で壊さないための最低限の安全装置 です。
Stripe の公式 API でも、create / update 系の安全な再試行のために Idempotency-Key が前提になっています。
PayPal や Square でも同様の仕組みがあり、主要な決済 API では共通の基本知識です。
何を防ぐのか
典型的な事故は次の 3 つです。
- 決済 API への POST がタイムアウトした
- ユーザーが購入ボタンを連打した
- ワーカーやジョブが失敗して同じ処理を再実行した
冪等性がないと、このとき「サーバー側では成功していたが、アプリは失敗だと思って再送する」という最悪の二重課金パターンが起きます。
Stripe での基本ルール
Stripe の idempotent requests では、同じ意味の create / update リクエストを 同じキーで再送 します。
押さえるべき点は以下です。
Idempotency-Keyはクライアントではなく サーバー側で決める- 同じ論理操作には同じキーを使う
- 別の操作には別のキーを使う
- 一度使ったキーを、別パラメータの別操作へ流用しない
Stripe は最初のレスポンス結果を保存し、同じキーの再送には同じ結果を返します。
成功だけでなく、失敗や 500 も含めて同じ結果が返る点が重要です。
内部注文 ID とどう結びつけるか
おすすめは、内部注文 ID を先に作ってから、その注文に対する API リクエストの冪等性キーを派生させる ことです。
order_20260412_001
-> checkout-session:order_20260412_001
-> refund:order_20260412_001:1
こうしておくと、調査時に「このキーはどの注文のどの操作か」がすぐ分かります。
どこで使うべきか
create 系 API
- Checkout Session 作成
- PaymentIntent 作成
- Refund 作成
- Subscription 作成
update 系 API
- capture
- amount update
- metadata update
一方で、GET や自然に idempotent な処理は別問題です。
実装パターン
悪い例
await stripe.checkout.sessions.create(payload);
通信エラー時にこのまま再送すると、同じ意図の Session が複数できる余地があります。
良い例
const order = await db.orders.insert({ ... });
const session = await stripe.checkout.sessions.create(payload, {
idempotencyKey: `checkout-session:${order.id}`
});
これで「この注文の checkout session 作成」という論理操作が一意になります。
冪等性があっても防げないこと
業務ロジックの重複
同じ Session を一回しか作らなくても、Webhook を二回処理すれば二重反映は起きます。
Webhook 側は event.id で別に重複防止が必要です。
意味の違う再実行
同じキーでパラメータだけ変えて再送するのは危険です。
「同じ論理操作」かどうかをサーバー側で決める必要があります。
UI 連打の完全防止
フロント側でもボタン disable や pending 表示は必要です。
冪等性は最後の砦であって、UI 改善の代わりではありません。
主要サービスの違い
| サービス | 代表的な仕組み | メモ |
|---|---|---|
| Stripe | Idempotency-Key | create / update の再試行を安全にする |
| PayPal | PayPal-Request-Id | 同じ POST の再送で最新状態を返す |
| Square | idempotency_key | CreatePayment などで必須の場面がある |
つまづきポイント
キーをクライアントが自由に決める
ユーザー操作単位で安定しないキーになると、同じ注文でも違うキーで飛びやすくなります。
返金と購入で同じキー設計を使い回す
購入と返金は別操作です。
refund:{orderId}:{attempt} のように意味単位で分けた方が安全です。
冪等性を入れたから内部注文の unique 制約は不要だと思う
それは別レイヤーです。
DB の unique 制約、注文 ID、Webhook の重複防止は残ります。
併読推奨
- 冪等性キーとは
- Stripe Webhook で起きやすい失敗を運用視点で10個に整理する
- Stripe で決済機能を実装する手順をサンプルコード付きで追う
- 決済システム運用で事故を減らすチェックリストを作る
よくある質問
冪等性キーと注文 ID は同じものですか?
違います。注文 ID は業務上の一意性、冪等性キーは API 再試行の安全性のために使います。
Webhook の重複防止にも冪等性キーを使いますか?
Webhook 側は通常 event ID で重複処理を防ぎます。API リクエスト側の冪等性とは役割が違います。