Richieste idempotenti
Riprova le operazioni di ordine e pagamento supportate da DEUNA senza creare un'operazione duplicata.
In questa pagina
Utilizza una chiave idempotente quando un errore di rete o un timeout del client rendono incerto il risultato di una richiesta che modifica lo stato. DEUNA associa la chiave all'operazione e al corpo della richiesta, quindi riproduce la risposta memorizzata invece di elaborare nuovamente la stessa operazione.
Flusso di riprova#
Il merchant possiede la generazione, la persistenza e la tempistica del retry. DEUNA possiede l'associazione tra la chiave, l'operazione, il corpo della richiesta e la risposta memorizzata entro la finestra di conservazione documentata.
Operazioni sostenute#
Invia X-Idempotency-Key con queste operazioni pubbliche:
| Operazioni | Metodo e percorso | Riferimento |
|---|---|---|
| Crea un ordine · Flusso opzionale | POST /merchants/orders | Crea un ordine |
| Crea un pagamento · Acquisto V2 | POST /v2/merchants/orders/purchase | Creare un pagamento |
| Acquisire un pagamento autorizzato | POST /v2/merchants/orders/{order_token}/capture | Cattura un pagamento |
| Annullare un'autorizzazione | POST /merchants/orders/{order_token}/void | Annullare un'autorizzazione |
| Rimborsare un pagamento | POST /v2/merchants/orders/{order_token}/refund | Rimborsare un pagamento |
Non presumere che ogni POST endpoint supporti l'idempotenza. Utilizza l'elenco delle operazioni sopra o verifica se il riferimento all'endpoint include X-Idempotency-Key header.
Genera e memorizza la chiave#
Genera un UUID v4 nel tuo backend merchant per ogni operazione commerciale distinta. Memorizzalo insieme al tuo record dell'operazione prima di chiamare DEUNA.
const idempotencyKey = crypto.randomUUID();Segui queste regole:
- Riutilizza la stessa chiave solo quando riprovi la stessa operazione.
- Invia esattamente lo stesso corpo della richiesta in ogni riprova. DEUNA confronta l'hash del corpo, quindi anche un corpo semanticamente equivalente serializzato in modo diverso può essere rifiutato.
- Genera una nuova chiave per un nuovo tentativo di cattura, annullamento, rimborso o creazione dell'ordine.
- Genera una nuova chiave quando correggi i dati della richiesta dopo un errore di validazione o commerciale.
- Non derivare le chiavi da una sequenza breve che può collidere in diversi sistemi o ambienti.
La chiave è valida per il merchant e l'operazione, ma la tua integrazione dovrebbe comunque trattarla come univoca e non riutilizzarla deliberatamente per un'altra azione.
Invia la richiesta#
Aggiungi la chiave alla richiesta originale e a ogni riprova di quella richiesta. Questo esempio di Acquisto V2 utilizza un token dell'ordine che l'integrazione ha già creato:
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))
}Se la risposta ha X-Idempotency-Replay: true, DEUNA ha restituito il risultato memorizzato per una richiesta precedente con la stessa chiave e corpo. Gestisci il suo codice di stato e il payload esattamente come per la risposta originale.
Comprendi la finestra di conservazione#
L'associazione chiave-corpo inizia quando DEUNA riceve per la prima volta la chiave. La finestra di riproduzione della risposta inizia quando DEUNA memorizza il risultato completato e il suo comportamento dipende da quel risultato:
| Risultato originale | Comportamento di riproduzione |
|---|---|
| Risposta di successo | DEUNA memorizza la risposta per 24 ore su 24 per impostazione predefinita dopo che l'operazione è stata completata. Un riprova con la stessa chiave e corpo durante quella finestra restituisce il codice di stato, gli header e il payload memorizzati. |
4xx o 5xx risposta | DEUNA memorizza la risposta di errore per circa 2 secondi per assorbire duplicati immediati. L'associazione tra la chiave e il corpo della richiesta originale rimane per l'intera durata di 24 ore. |
| Richiesta in corso | Una richiesta concorrente attende brevemente il primo risultato. Se la prima richiesta è ancora in esecuzione, DEUNA può restituire un codice HTTP 409 con IS-4409. |
Dopo la scadenza del periodo di conservazione della risposta di successo, la stessa chiave può essere trattata come una nuova operazione. Non fare affidamento su questo comportamento: creare una nuova chiave per ogni nuova operazione commerciale, anche quando una chiave precedente è scaduta.
Per una risposta di errore, non riutilizzare la chiave con i dati della richiesta corretti. Poiché l'associazione del corpo originale rimane attiva, il corpo modificato viene IS-4401 ritenuto fino alla scadenza del periodo di conservazione. Correggere la richiesta e inviarla come una nuova operazione con una nuova chiave.
Gestire i tentativi e i conflitti#
| Situazione | Cosa restituisce DEUNA | Cosa dovrebbe fare l'integrazione |
|---|---|---|
| Stessa chiave e stesso corpo dopo una richiesta di successo completata | La risposta memorizzata con X-Idempotency-Replay: true | accettare la risposta e riconciliare lo stato dell'ordine corrente quando non è definitivo. |
| Stessa chiave con un corpo diverso | HTTP 400, codice IS-4401 | Non riprovare questa combinazione. Utilizzare una nuova chiave solo quando il corpo modificato rappresenta un'operazione corretta o nuova. |
| Arriva un duplicato mentre la prima richiesta è ancora in esecuzione | HTTP 409, codice IS-4409, quando termina l'attesa in corso | Rallentare, quindi riprovare la richiesta esatta con la stessa chiave. Riconciliare l'ordine originale prima di avviare un nuovo tentativo. |
| È stato superato il limite di richieste | HTTP 429, che può includere codice IS-4429 | Rispettare Retry-After quando presente, quindi riprovare con lo stesso corpo e chiave. |
| Timeout o perdita di connessione del client | Il client potrebbe non ricevere risposta, anche se l'elaborazione continua | Conservare la chiave e il corpo, risolvere l'ordine e riprovare solo seguendo la procedura di timeout documentata. |
| Validazione o errore di business non riproducibile | A 4xx risposta | Correggere la richiesta e utilizzare una nuova chiave. Non riprovare un corpo modificato con la vecchia chiave. |
Utilizzare un backoff esponenziale limitato con jitter. Non inviare più tentativi concorrenti per la stessa operazione di business.
Vedi Errori API per il modello di codice di risposta e timeout dei client per la procedura di riconciliazione.
Verificare il comportamento nell'ambiente sandbox#
- Inviare una richiesta supportata valida a
https://api.sandbox.deuna.iocon una nuova chiave UUID v4. - Salvare il codice di stato, gli header di risposta e il payload.
- Inviare esattamente la stessa richiesta di nuovo con la stessa chiave e corpo.
- Verificare che la seconda risposta abbia lo stesso risultato e includa
X-Idempotency-Replay: true. - Durante un test negativo separato, modificare il corpo ma mantenere la chiave e verificare che DEUNA restituisca un codice HTTP
400conIS-4401.
Non riutilizzare né la chiave di test per un'altra operazione nell'ambiente sandbox.