PAY.JP

冪等リクエスト

冪等リクエストを使用して二重決済を防止する方法

ネットワークエラーやタイムアウトで二重決済など、同じリクエストが複数回実行されることを防ぐには冪等リクエストを使用します。

冪等リクエストを使用する際は、UUID やタイムスタンプ、ランダム文字列などの一意なキーを生成し、リトライが必要となった場合に備えてサーバー側へ保存します。 ネットワークエラーやタイムアウトでレスポンスを受け取れなかった場合など、同じリクエストを意図的に再試行する場合のみ同じ冪等キーを再利用します。

冪等リクエストの基本

// 冪等キーを生成(UUIDなど一意な値を使用)
function generateIdempotencyKey() {
  return crypto.randomUUID();
}

const idempotencyKey = generateIdempotencyKey();

const response = await fetch('https://api.pay.jp/v2/payment_flows', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey  // 冪等キーを指定
  },
  body: JSON.stringify({
    amount: 1000,
    currency: 'jpy'
  })
});

冪等キーの要件

項目要件
最大文字数255文字
使用可能文字英数字(a-z, A-Z, 0-9)、ハイフン(-)、アンダースコア(_)のみ
対応HTTPメソッドPOST, PUT, PATCH

冪等キーの保持期間

冪等キーは最低24時間保持されます。その間、同一キーによるリクエストは同一リクエストとして扱われ、初回のレスポンスが返却されます(パラメーターが異なる場合はエラーとなります)。なお、24時間経過後の削除タイミングは保証されないため、新しいリクエストには常に新しいキー(UUID など)を生成して指定してください。

エラーレスポンスの扱い

初回のリクエストがエラーになった場合も、そのエラーレスポンスが保存されます。同じキーで再試行しても処理は実行されず、同じエラーレスポンスが返却されます。

初回のリクエストの結果同じキーで再試行した場合対応方針
レスポンスを受け取れなかった(ネットワークエラー、タイムアウト、502、504)初回のリクエストが完了していればそのレスポンスが返却され、処理中であれば完了を待ってから返却される同じキーで再試行
400、402、404 など同じエラーレスポンスが返却される原因を解消し、新しいキーで再実行
500同じエラーレスポンスが返却される処理が完了したかは不確定なため、オブジェクトの取得や Webhook で状態を確認してから、必要に応じて新しいキーで再実行

422(バリデーションエラー)および後述の冪等リクエストエラーは保存されません。422 エラーはパラメーターを修正すれば、同じキーで再試行できます。

冪等リクエストエラーの処理

冪等リクエストに関連するエラーコードは以下のとおりです。

エラーコードステータス説明対応方針
invalid_idempotency_key400冪等キーのフォーマットが不正キーの形式を確認し修正
idempotency_conflict409同じキーで異なるパラメーターのリクエストが送信された新しいキーで再実行
idempotency_timeout409元のリクエストの完了待ちがタイムアウト同じキーで再試行
idempotency_infrastructure_error503冪等チェック機能が一時的に利用不可リトライ可能
// idempotencyKey は呼び出し側で生成・保存し、再試行時には同じキーを渡す
async function makeIdempotentRequest(params, idempotencyKey) {
  const response = await fetch('https://api.pay.jp/v2/payment_flows', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey
    },
    body: JSON.stringify(params)
  });

  if (response.ok) {
    return await response.json();
  }

  const error = await response.json();

  if (error.code === 'idempotency_conflict') {
    console.error('同じ冪等キーで異なるリクエストが送信されました');
    // この関数内で自動リトライは行わず、呼び出し側での対処(確認・再実行など)に委ねる
    throw error;
  }

  if (error.code === 'idempotency_timeout') {
    console.warn('元のリクエストの完了待ちがタイムアウトしました');
    // 同じ冪等キーで再試行するか、ユーザーに確認してから新しいキーで再実行するかは、
    // ビジネスロジックに応じて呼び出し側で制御してください
  }

  if (error.code === 'idempotency_infrastructure_error') {
    console.warn('冪等チェック機能が一時的に利用不可です');
    // リトライ可能。待機時間を徐々に長くしながら再試行
  }

  throw error;
}

On this page