PAY.JP
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

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

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

エラーコードステータス説明対応方針
invalid_idempotency_key400冪等キーのフォーマットが不正キーの形式を確認し修正
idempotency_conflict409同じキーで異なるパラメータのリクエストが送信された新しいキーで再実行
idempotency_timeout409元のリクエストの完了待ちがタイムアウト同じキーで再試行
idempotency_infrastructure_error503冪等チェック機能が一時的に利用不可リトライ可能
async function makeIdempotentRequest(params) {
  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(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;
}