メインコンテンツへスキップ
バージョン: 最新版

べき等性

重複した課金、顧客、その他のリソースを作成せずに、APIリクエストを安全に再試行します。べき等キーを使用して信頼性の高い決済連携を構築する方法を学びます。

概要

ネットワークの問題、タイムアウト、サーバーエラーにより、APIリクエストが失敗したり、結果が不明確になることがあります。べき等性により、重複操作を心配することなく、リクエストを安全に再試行できます。Idempotency-Keyヘッダーを提供することで、Omiseは同じリクエストが複数回送信されても同じ結果を生成することを保証します。

クイックスタート
  • POST/PATCHリクエストにIdempotency-Keyヘッダーを追加
  • 操作ごとに一意のキーを使用(UUID推奨)
  • 同じキーは同じ結果を返す(24時間キャッシュ)
  • 課金作成とお金の操作に不可欠
  • ネットワーク問題時の重複決済を防止

べき等性とは

べき等性とは、操作を複数回実行しても同じ結果が得られることを意味します。決済処理では、これは非常に重要です:

べき等性なしの場合

1. 課金リクエストを送信 → ネットワークタイムアウト
2. 成功した?不明。再試行する?
3. 再試行 → 重複課金!顧客に二重請求 💥

べき等性ありの場合

1. べき等キー付きで課金を送信 → ネットワークタイムアウト
2. 同じキーで再試行 → 同じ結果を返す
3. 重複課金なし ✅

べき等性の仕組み

  1. リクエストを送信 Idempotency-Keyヘッダー付き
  2. Omiseが処理 し、結果を保存
  3. 24時間以内に同じキーで再試行すると:
    • Omiseはキャッシュされた結果を返す
    • 新しい操作は実行されない
    • 同じレスポンスステータスコードとボディ

フロー例

# 最初のリクエスト(ネットワークタイムアウト)
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d "amount=100000" \
-d "currency=thb" \
-d "card=tokn_test_..."
# レスポンス: (タイムアウト - 成功したか不明)

# 同じキーで再試行
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d "amount=100000" \
-d "currency=thb" \
-d "card=tokn_test_..."
# レスポンス: 元の課金を返す(新しいものではない)

いつべき等性を使用するか

常に使用すべき場合:

課金の作成

POST /charges

最も重要 - 重複決済を防止

顧客の作成

POST /customers

重複した顧客レコードを防止

返金の作成

POST /charges/:id/refunds

重複した返金を防止

振込の作成

POST /transfers

重複した支払いを防止

受取人の作成

POST /recipients

重複した受取人レコードを防止

すべてのPOSTリクエスト リソースを作成するすべてのPOSTリクエストにべき等キーを使用

PATCHリクエスト 更新もべき等性で安全に再試行可能

不要な場合:

GETリクエスト データの読み取りは既にべき等(副作用なし)

DELETEリクエスト 削除は自然にべき等(2回削除 = 同じ結果)


Idempotency-Keyヘッダー

ヘッダー形式

Idempotency-Key: <一意の文字列>

キーの要件

要件説明
形式最大255文字の任意の文字列
一意性操作ごとに一意である必要がある
文字英数字とハイフン推奨
大文字小文字区別する: key-1KEY-1
有効期限24時間保存

推奨: UUIDを使用

# UUIDv4形式(推奨)
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

なぜUUID?

  • ✅ 一意性を保証
  • ✅ 衝突リスクなし
  • ✅ 標準形式
  • ✅ すべての言語で利用可能

実装例

Ruby

require 'omise'
require 'securerandom'

Omise.api_key = ENV['OMISE_SECRET_KEY']

# 一意のべき等キーを生成
idempotency_key = SecureRandom.uuid

begin
charge = Omise::Charge.create({
amount: 100000,
currency: 'thb',
card: token
}, {
'Idempotency-Key' => idempotency_key
})

puts "課金作成: #{charge.id}"

rescue Omise::Error => e
if e.http_status >= 500 || e.message.include?('timeout')
# 同じキーで安全に再試行
charge = Omise::Charge.create({
amount: 100000,
currency: 'thb',
card: token
}, {
'Idempotency-Key' => idempotency_key # 同じキー!
})
else
raise
end
end

Python

import omise
import uuid

omise.api_secret = os.environ['OMISE_SECRET_KEY']

# 一意のべき等キーを生成
idempotency_key = str(uuid.uuid4())

try:
charge = omise.Charge.create(
amount=100000,
currency='thb',
card=token,
headers={'Idempotency-Key': idempotency_key}
)
print(f"課金作成: {charge.id}")

except omise.errors.BaseError as e:
if e.http_status >= 500:
# 同じキーで安全に再試行
charge = omise.Charge.create(
amount=100000,
currency='thb',
card=token,
headers={'Idempotency-Key': idempotency_key} # 同じキー!
)
else:
raise

Node.js

const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const { v4: uuidv4 } = require('uuid');

async function createChargeWithRetry(chargeData, maxRetries = 3) {
// 一意のべき等キーを生成
const idempotencyKey = uuidv4();

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const charge = await omise.charges.create({
...chargeData,
headers: {
'Idempotency-Key': idempotencyKey
}
});

console.log(`課金作成: ${charge.id}`);
return charge;

} catch (error) {
const isServerError = error.statusCode >= 500;
const isTimeout = error.code === 'ETIMEDOUT';
const isLastAttempt = attempt === maxRetries - 1;

if ((isServerError || isTimeout) && !isLastAttempt) {
// 同じキーで安全に再試行
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}

throw error;
}
}
}

// 使用方法
createChargeWithRetry({
amount: 100000,
currency: 'thb',
card: 'tokn_test_...'
});

リトライロジックのベストプラクティス

1. 一時的なエラーのみ再試行

function isRetryable(error) {
// サーバーエラーは再試行
if (error.statusCode >= 500) return true;

// タイムアウトは再試行
if (error.code === 'ETIMEDOUT') return true;
if (error.code === 'ECONNRESET') return true;

// クライアントエラーは再試行しない
if (error.statusCode >= 400 && error.statusCode < 500) return false;

return false;
}

2. 指数バックオフを使用

import time
import random

def exponential_backoff(attempt, base_delay=1, max_delay=60):
"""ジッター付き遅延を計算"""
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1) # 10%のジッターを追加
return delay + jitter

クイックリファレンス

べき等ヘッダー

Idempotency-Key: <一意の文字列>

いつ使用するか

リクエストタイプべき等性を使用?
POST (作成)✅ 常に
PATCH (更新)✅ 推奨
GET (読み取り)❌ 不要
DELETE❌ 不要

キーの有効期限

  • 保存: 最初の使用から24時間
  • 期限切れ: 24時間後は新しいリソースを作成

リトライの判断ツリー

リクエストが失敗した?
├─ はい → エラータイプを確認
│ ├─ 5xx サーバーエラー → 同じキーで再試行
│ ├─ ネットワークタイムアウト → 同じキーで再試行
│ ├─ 4xx クライアントエラー → リクエストを修正、新しいキーを使用
│ └─ その他のエラー → 再試行しない
└─ いいえ → 成功!

関連リソース


次へ: APIバージョニングでバージョン変更を管理し、下位互換性を維持する方法を学びましょう。


FAQ

同じべき等キーを異なるパラメータで使用した場合はどうなりますか?

APIは新しいパラメータに関係なく、最初のリクエストのキャッシュされたレスポンスを返します。これは重複操作を防ぐための意図的な動作です。各操作には必ず一意のキーを使用してください。

べき等キーの有効期間はどのくらいですか?

べき等キーは最初のリクエストから24時間有効です。24時間後に同じキーを使用すると、キャッシュされたレスポンスを返す代わりに新しいリソースが作成されます。

GETリクエストにはべき等キーが必要ですか?

いいえ、GETリクエストはデータを読み取るだけでリソースを作成または変更しないため、本質的にべき等です。べき等キーはPOSTおよびPATCHリクエストにのみ必要です。

4xxエラーを同じべき等キーで再試行すべきですか?

いいえ、4xxエラーはリクエストに問題があることを示しています(無効なパラメータ、認証失敗など)。問題を修正し、修正されたリクエストには新しいべき等キーを使用してください。元のエラーは古いキーでキャッシュされています。

異なるエンドポイント間で同じべき等キーを使用できますか?

べき等キーはエンドポイントごとにスコープされています。/charges/customersエンドポイントで使用された同じキーは、独立して扱われます。ただし、混乱を避けるためにグローバルに一意のキー(UUID)を使用することがベストプラクティスです。

べき等キーに最適な形式は何ですか?

UUID(v4)は一意性が保証され、すべてのプログラミング言語で利用可能なため推奨されます。また、追跡を容易にするために、内部の注文/取引ID(例:order-12345)からキーを派生させることもできます。