冪等なリクエスト
DEUNA の注文および支払い操作の再試行を、重複した操作を作成せずに実行します。
ネットワーク障害またはクライアントタイムアウトにより、ステート変更リクエストの結果が不確実な場合に、一貫性キーを使用します。 DEUNA は、キーを操作とリクエストボディに関連付け、保存された応答を再実行します。
再試行フロー#
キーの生成、保存、および再試行タイミングは、商社に依存します。 DEUNA は、キー、操作、リクエストボディ、およびドキュメント化された保持期間内の保存された応答との関連性を管理します。
サポートされている操作#
送信 X-Idempotency-Key 以下の操作に対して:
| 操作 | メソッドとパス | 参照 |
|---|---|---|
| 注文の作成 · オプションフロー | POST /merchants/orders | 注文を作成する |
| 支払いを作成 · 購買 V2 | POST /v2/merchants/orders/purchase | 支払いを作成 |
| 承認された決済を実行 | POST /v2/merchants/orders/{order_token}/capture | 支払いをキャプチャ |
| 承認を無効化 | POST /merchants/orders/{order_token}/void | 承認を無効化 |
| 支払いを返金 | POST /v2/merchants/orders/{order_token}/refund | 支払いを返金 |
すべての POST エンドポイントが一貫性をサポートしているとは限りません。 上記の操作リストを使用するか、エンドポイントの参照に一貫性に関する情報が含まれているかどうかを確認してください。 X-Idempotency-Key header.
キーを生成して保存#
各固有のビジネス操作に対して、Node.js で 1 つの UUID v4 を生成します。 DEUNA に呼び出す前に、独自の操作レコードに保存します。
const idempotencyKey = crypto.randomUUID();以下のルールに従ってください:
- 同じ操作の再試行時に、同じキーのみを使用します。
- すべての再試行で、正確なリクエストボディを送信します。 DEUNA はボディのハッシュを比較するため、異なる形式でシリアライズされた、意味的に同等のボディは拒否されます。
- 新しい支払い、キャプチャ、キャンセル、払い戻し、または注文作成の試行に対して、新しいキーを生成します。
- リクエストデータを検証またはビジネスエラー後に修正した場合、新しいキーを生成します。
- システムや環境間で衝突する可能性のある短いシーケンスからキーを生成しないでください。
キーは商社と操作にスコープされますが、統合は、グローバルに一意として扱い、別の操作に対して意図的に再利用しないでください。
リクエストを送信#
元のリクエストと、そのすべての再試行にキーを追加します。 この Purchase V2 の例では、統合がすでに作成した注文トークンを使用しています:
curl --request POST \
--url https://api.sandbox.deuna.io/v2/merchants/orders/purchase \
--header "X-Api-Key: YOUR_PRIVATE_API_KEY" \
--header "X-Idempotency-Key: 9f510c4a-0f7d-4c31-94c5-590b85e4d231" \
--header "Content-Type: application/json" \
--data '{
"order_token": "ORDER_TOKEN",
"payer_info": {
"email": "buyer@example.com",
"external_user_id": "customer_1042"
},
"payment_source": {
"method_type": "credit_card",
"payment_method": "PAYMENT_METHOD_TOKEN"
}
}'const response = await fetch("https://api.sandbox.deuna.io/v2/merchants/orders/purchase", {
method: "POST",
headers: {
"X-Api-Key": "YOUR_PRIVATE_API_KEY",
"X-Idempotency-Key": "9f510c4a-0f7d-4c31-94c5-590b85e4d231",
"Content-Type": "application/json"
},
body: JSON.stringify({
"order_token": "ORDER_TOKEN",
"payer_info": {
"email": "buyer@example.com",
"external_user_id": "customer_1042"
},
"payment_source": {
"method_type": "credit_card",
"payment_method": "PAYMENT_METHOD_TOKEN"
}
})
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"POST",
"https://api.sandbox.deuna.io/v2/merchants/orders/purchase",
headers={
"X-Api-Key": "YOUR_PRIVATE_API_KEY",
"X-Idempotency-Key": "9f510c4a-0f7d-4c31-94c5-590b85e4d231",
"Content-Type": "application/json"
},
json={
"order_token": "ORDER_TOKEN",
"payer_info": {
"email": "buyer@example.com",
"external_user_id": "customer_1042"
},
"payment_source": {
"method_type": "credit_card",
"payment_method": "PAYMENT_METHOD_TOKEN"
}
},
)
response.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/v2/merchants/orders/purchase",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: YOUR_PRIVATE_API_KEY",
"X-Idempotency-Key: 9f510c4a-0f7d-4c31-94c5-590b85e4d231",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"order_token" => "ORDER_TOKEN",
"payer_info" => [
"email" => "buyer@example.com",
"external_user_id" => "customer_1042"
],
"payment_source" => [
"method_type" => "credit_card",
"payment_method" => "PAYMENT_METHOD_TOKEN"
]
])
]);
$response = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/v2/merchants/orders/purchase"))
.header("X-Api-Key", "YOUR_PRIVATE_API_KEY")
.header("X-Idempotency-Key", "9f510c4a-0f7d-4c31-94c5-590b85e4d231")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\n \"order_token\": \"ORDER_TOKEN\",\n \"payer_info\": {\n \"email\": \"buyer@example.com\",\n \"external_user_id\": \"customer_1042\"\n },\n \"payment_source\": {\n \"method_type\": \"credit_card\",\n \"payment_method\": \"PAYMENT_METHOD_TOKEN\"\n }\n }"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
request, err := http.NewRequest("POST", "https://api.sandbox.deuna.io/v2/merchants/orders/purchase", strings.NewReader("{\n \"order_token\": \"ORDER_TOKEN\",\n \"payer_info\": {\n \"email\": \"buyer@example.com\",\n \"external_user_id\": \"customer_1042\"\n },\n \"payment_source\": {\n \"method_type\": \"credit_card\",\n \"payment_method\": \"PAYMENT_METHOD_TOKEN\"\n }\n }"))
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "YOUR_PRIVATE_API_KEY")
request.Header.Set("X-Idempotency-Key", "9f510c4a-0f7d-4c31-94c5-590b85e4d231")
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}応答に X-Idempotency-Replay: trueが含まれている場合、DEUNA は、同じキーとボディを持つ以前のリクエストの保存された結果を返します。 応答のステータスコードとペイロードを、元の応答と同じように処理します。
保持期間を理解#
キーとボディの関連性は、DEUNA がキーを受信したときに開始されます。 保存された結果の再実行ウィンドウは、DEUNA が結果を保存したときに開始され、その結果によって制御されます。
| 元の結果 | 再実行の動作 |
|---|---|
| 成功した応答 | DEUNA は、操作が完了した後に、デフォルトで 24時間 を保存します。 同じキーとボディで、その期間中に再試行すると、保存されたステータスコード、ヘッダー、およびペイロードが返されます。 |
4xx または 5xx 応答 | DEUNA は、約 2 秒 直ちに重複を処理するために。キーと元のリクエストボディ間の関連性は、24 時間の有効期間全体に維持されます。 |
| リクエストは現在実行中 | 競合するリクエストは、最初の結果を待機します。最初のリクエストがまだ実行中の場合、DEUNA は HTTP 409 ~ IS-4409. |
正常な応答の保持期間が終了した後、同じキーは新しい操作として扱われます。この動作に依存しないでください。新しいビジネス試行ごとに、古いキーが期限切れになった場合でも、新しいキーを作成してください。
エラー応答の場合、修正されたリクエストデータでキーを再利用しないでください。元のボディとの関連性が維持されているため、変更されたボディが返されます。 IS-4401 保持期間が終了するまで。リクエストを修正し、新しいキーを使用して新しい操作として送信してください。
リトライと競合の処理#
| 状況 | DEUNA が返す情報 | 統合側で実行すべき処理 |
|---|---|---|
| 正常に完了したリクエスト後の、同じキーとボディ | 保存されたレスポンス X-Idempotency-Replay: true | レスポンスを受け入れ、最終的な状態でない場合に現在の注文の状態を再調整します。 |
| 同じキーで異なるボディを使用 | HTTP 400、コード IS-4401 | 同じ組み合わせでの再試行は行わないでください。変更されたボディが修正または新しい操作を表す場合にのみ、新しいキーを使用してください。 |
| 最初のリクエストが実行中の間に重複リクエストが到着 | HTTP 409、コード IS-4409、待機が完了したとき | バックオフし、同じキーを使用して正確なリクエストを再試行します。新しい試行を開始する前に、元の注文を再調整してください。 |
| レート制限を超過 | HTTP 429, 含まれる可能性のあるコード IS-4429 | 名誉 Retry-After 存在する場合、同じリクエストボディとキーで再試行します。 |
| クライアントのタイムアウトまたは接続の喪失 | クライアントは、処理が継続中でも応答を受け取らない場合があります。 | キーとボディを保持し、注文を照合し、ドキュメント化されたタイムアウト手順に従ってのみ再試行してください。 |
| 再試行できない検証またはビジネスエラー | A 4xx 応答 | リクエストを修正し、新しいキーを使用してください。 変更されたボディを古いキーで再試行しないでください。 |
制限付きの指数関数的バックオフとジャイターを使用してください。同じビジネス操作に対して複数の並行リトライを送信しないでください。
参照 APIのエラー レスポンスコードモデルと クライアントタイムアウト および、整合性プロセスのために。
サンドボックス環境での動作を確認#
- 有効なサポートされているリクエストを送信し、
https://api.sandbox.deuna.io新しい UUID v4 キーを使用してください。 - ステータスコード、レスポンスヘッダー、およびペイロードを保存してください。
- 同じキーとボディを使用して、正確なリクエストを再度送信してください。
- 2番目のレスポンスが同じ結果を含んでいることを確認してください。
X-Idempotency-Replay: true. - 別のネガティブテストでは、ボディを変更し、キーは保持して、DEUNAがHTTPを返すことを確認してください。
400~IS-4401.
テスト用のキーを別のサンドボックス操作には再利用しないでください。