Requêtes idempotentes
Réessayer les opérations de commande et de paiement DEUNA sans créer une opération dupliquée.
Sur cette page
Utiliser une clé d'idempotence lorsque une panne de réseau ou un délai d'attente du client laisse incertain le résultat d'une requête modifiant l'état. DEUNA associe la clé à l'opération et au corps de la requête, puis réexécute la réponse stockée au lieu de traiter la même opération à nouveau.
Flux de réessayage#
Le commerçant est responsable de la génération, du stockage et de la gestion des délais de nouvelles tentatives. DEUNA est responsable de l'association entre la clé, l'opération, le corps de la requête et la réponse stockée, dans le délai de conservation documenté.
Opérations prises en charge#
Envoyer X-Idempotency-Key Avec les opérations suivantes :
| Fonctionnement | Méthode et chemin | Référence |
|---|---|---|
| Créer une commande · Flux optionnel | POST /merchants/orders | Créer une commande |
| Créer un paiement · Achat V2 | POST /v2/merchants/orders/purchase | Créer un paiement |
| Effectuer un paiement autorisé | POST /v2/merchants/orders/{order_token}/capture | Effectuer un paiement |
| Annuler une autorisation | POST /merchants/orders/{order_token}/void | Annuler une autorisation |
| Effectuer un remboursement | POST /v2/merchants/orders/{order_token}/refund | Effectuer un remboursement |
Ne présumez pas que chaque POST endpoint prend en charge l'idempotence. Utilisez la liste des opérations ci-dessus ou vérifiez si la référence de l'endpoint inclut X-Idempotency-Key header.
Générez et conservez la clé#
Générez un UUID v4 dans votre backend commercial pour chaque opération distincte. Stockez-le avec votre propre enregistrement d'opération avant d'appeler DEUNA.
const idempotencyKey = crypto.randomUUID();Suivez ces règles :
- Réutiliser la même clé uniquement lors de la tentative de nouvelle exécution de la même opération.
- Envoyer exactement le même corps de requête à chaque tentative. DEUNA compare le hachage du corps, de sorte qu'un corps sérialisé différemment, mais ayant le même sens, peut être rejeté.
- Générez une nouvelle clé pour un nouveau paiement, une transaction, une annulation, un remboursement ou une tentative de création de commande.
- Générez une nouvelle clé lorsque vous corrigez les données de la requête après une validation ou une erreur métier.
- Ne dérivez pas les clés à partir d'une séquence courte qui pourrait entrer en conflit dans différents systèmes ou environnements.
La clé est associée au commerçant et à l'opération, mais votre intégration doit toujours la considérer comme unique et ne jamais la réutiliser pour une autre action.
Envoyer la requête#
Ajoutez la clé à la requête originale et à chaque tentative de requête. Cet exemple Purchase V2 utilise un jeton de commande que l'intégration a déjà créé :
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))
}Si la réponse contient X-Idempotency-Replay: true, DEUNA renvoie le résultat stocké pour une requête précédente avec la même clé et corps. Traitez son code de statut et son payload exactement comme la réponse originale.
Comprenez la période de conservation.#
L'association clé-corps commence lorsque DEUNA reçoit initialement la clé. La période de réutilisation de la réponse commence lorsque DEUNA stocke le résultat, et son comportement dépend de ce résultat :
| Réponse originale | Comportement de réutilisation |
|---|---|
| Réponse réussie | DEUNA stocke la réponse pour 24 heures par défaut, après la fin de l'opération. Une nouvelle tentative avec la même clé et le même corps pendant cette période renvoie le code de statut, les en-têtes et le payload stockés. |
4xx ou 5xx Réponse | DEUNA stocke la réponse d'erreur pendant environ 2 secondes pour absorber les doublons immédiats. La relation entre la clé et le corps de la requête originale est maintenue pendant toute la période de 24 heures. |
| Requête en cours | Une requête concurrente attend brièvement le premier résultat. Si la première requête est toujours en cours, DEUNA peut renvoyer un code HTTP 409 et IS-4409. |
Une fois la période de rétention de la réponse réussie expirée, la même clé peut être traitée comme une nouvelle opération. Ne comptez jamais sur ce comportement : créez une nouvelle clé pour chaque nouvelle tentative, même lorsque l'ancienne clé a expiré.
En cas de réponse d'erreur, ne réutilisez pas la clé avec les nouvelles données de requête. Étant donné que l'association avec le corps original reste active, le corps modifié est IS-4401 renvoyé jusqu'à l'expiration de la période de rétention. Corrigez la requête et envoyez-la comme une nouvelle opération avec une nouvelle clé.
Gérer les tentatives et les conflits#
| Situation | Ce que DEUNA renvoie | Ce que votre intégration doit faire |
|---|---|---|
| Même clé et même corps après une requête réussie | La réponse stockée avec X-Idempotency-Replay: true | Acceptez la réponse, puis réglez l'état de la commande actuelle lorsque celle-ci n'est pas finale. |
| Même clé avec un corps différent | HTTP 400, code IS-4401 | Ne réessayez pas cette combinaison. Utilisez une nouvelle clé uniquement lorsque le corps modifié représente une opération corrigée ou nouvelle. |
| Un doublon arrive pendant que la première requête est toujours en cours | HTTP 409, code IS-4409, lorsque l'attente en cours se termine | Retirez, puis réessayez la requête exacte avec la même clé. Réglez l'ordre original avant de lancer une nouvelle tentative. |
| Le nombre de requêtes est dépassé | HTTP 429, ce qui peut inclure le code IS-4429 | Respectez Retry-After si présent, puis réessayez le même corps et la même clé. |
| Délai d'attente ou perte de connexion du client | Le client peut ne recevoir aucune réponse, alors que le traitement continue. | Conserver la clé et le corps de la requête, valider la commande et réessayer uniquement selon la procédure de délai d'attente documentée. |
| Validation ou erreur métier non répertoriée | Un 4xx Réponse | Corriger la requête et utiliser une nouvelle clé. Ne pas réessayer une requête avec un corps modifié sous l'ancienne clé. |
Utiliser un mécanisme de "backoff" exponentiel limité avec un jitter. Ne pas envoyer plusieurs tentatives concurrentes pour la même opération métier.
Voir Erreurs API pour le modèle de code de réponse et délai d'attente du client pour la procédure de réconciliation.
Vérifier le comportement dans l'environnement de test.#
- Envoyer une requête valide et prise en charge par
https://api.sandbox.deuna.ioavec une nouvelle clé UUID v4. - Enregistrer son code de statut, les en-têtes de réponse et le payload.
- Envoyer exactement la même requête avec la même clé et le même corps.
- Vérifier que la deuxième réponse a le même résultat et inclut
X-Idempotency-Replay: true. - Lors d'un test négatif séparé, modifier le corps mais conserver la clé et vérifier que DEUNA renvoie un code HTTP
400etIS-4401.
Ne réutiliser ni une clé de test pour une autre opération de sandbox.