Solicitudes idempotentes
Intenta de nuevo las operaciones de pedido y pago de DEUNA sin crear una operación duplicada.
En esta página
Utiliza una clave de idempotencia cuando un fallo de red o un tiempo de espera del cliente dejan incierta la respuesta de una solicitud que cambia de estado. DEUNA asocia la clave con la operación y el cuerpo de la solicitud, y luego reproduce la respuesta almacenada en lugar de procesar la misma operación nuevamente.
Flujo de reintento#
El comerciante es responsable de la generación, persistencia y temporización de reintentos de las claves. DEUNA es responsable de la asociación entre la clave, la operación, el cuerpo de la solicitud y la respuesta almacenada dentro del período de retención documentado.
Operaciones soportadas#
enviar X-Idempotency-Key con las siguientes operaciones públicas:
| Operación | Método y ruta | Referencia |
|---|---|---|
| Crear un pedido · Flujo opcional | POST /merchants/orders | Crear un pedido |
| Crear un pago · Compra V2 | POST /v2/merchants/orders/purchase | Crear un pago |
| Capturar un pago autorizado | POST /v2/merchants/orders/{order_token}/capture | Capturar un pago |
| Anular una autorización | POST /merchants/orders/{order_token}/void | Anular una autorización |
| Reembolsar un pago | POST /v2/merchants/orders/{order_token}/refund | Reembolsar un pago |
No asuma que todas POST las operaciones soportan la idempotencia. Utilice la lista de operaciones anterior o verifique si la referencia del punto final incluye... X-Idempotency-Key header.
Generar y persistir la clave#
Generar un UUID v4 en su backend comercial para cada operación distinta. Almacénelo junto con su propio registro de operación antes de llamar a DEUNA.
const idempotencyKey = crypto.randomUUID();Siga estas instrucciones:
- Reutilizar la misma clave solo al intentar la misma operación.
- Enviar el mismo cuerpo de solicitud en cada intento. DEUNA compara el hash del cuerpo, por lo que incluso un cuerpo semánticamente equivalente, pero serializado de forma diferente, puede ser rechazado.
- Generar una nueva clave para un nuevo pago, captura, cancelación, reembolso o intento de creación de pedido.
- Generar una nueva clave cuando se corrigen los datos de la solicitud después de una validación o error de negocio.
- No derivar claves de una secuencia corta que pueda colisionar en diferentes sistemas o entornos.
La clave está restringida al comerciante y a la operación, pero su integración debe tratarla como única a nivel global y nunca reutilizarla para otras acciones.
Enviar la solicitud#
Agregue la clave a la solicitud original y a cada intento de reintento de esa solicitud. Este ejemplo de "Purchase V2" utiliza un token de pedido que la integración ya ha creado:
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 respuesta X-Idempotency-Replay: true, DEUNA devuelve el resultado almacenado para una solicitud anterior con la misma clave y cuerpo. Maneje su código de estado y payload exactamente como lo haría con la respuesta original.
Comprenda el período de retención.#
La asociación clave-cuerpo comienza cuando DEUNA recibe la clave por primera vez. La ventana de reproducción de la respuesta comienza cuando DEUNA almacena el resultado completo, y su comportamiento depende de ese resultado:
| Resultado original | Comportamiento de reproducción |
|---|---|
| Respuesta exitosa | DEUNA almacena la respuesta para De 24 horas por defecto, después de que la operación se completa. Un reintento con la misma clave y cuerpo durante ese período devuelve el código de estado, los encabezados y el payload almacenados. |
4xx o 5xx respuesta | DEUNA almacena la respuesta de error durante aproximadamente 2 segundos para absorber duplicados inmediatos. La asociación entre la clave y el cuerpo de la solicitud original permanece durante todo el período de 24 horas. |
| Solicitud en curso | Una solicitud concurrente espera brevemente el primer resultado. Si la primera solicitud aún está en ejecución, DEUNA puede devolver HTTP 409 con IS-4409. |
Después de que expire el período de retención de la respuesta exitosa, la misma clave se puede tratar como una nueva operación. Nunca dependa de este comportamiento: cree una nueva clave para cada nueva operación comercial, incluso cuando una clave anterior haya expirado.
Para una respuesta de error, no reutilice la clave con los datos de solicitud corregidos. Debido a que la asociación del cuerpo original permanece activa, el cuerpo modificado se devuelve IS-4401 hasta que finalice el período de retención. Corrija la solicitud y envíela como una nueva operación con una nueva clave.
Maneje los reintentos y los conflictos#
| Situación | Qué devuelve DEUNA | Qué debe hacer su integración |
|---|---|---|
| Misma clave y mismo cuerpo después de una solicitud exitosa completada | La respuesta almacenada con X-Idempotency-Replay: true | Aceptar la respuesta y reconciliar el estado actual del pedido cuando no es final. |
| Misma clave con un cuerpo diferente | HTTP 400, código IS-4401 | No intente de nuevo esta combinación. Use una nueva clave solo cuando el cuerpo modificado represente una operación corregida o nueva. |
| Duplicado que llega mientras la primera solicitud está en curso | HTTP 409, código IS-4409, cuando finaliza la espera en curso | Retroceda y luego intente de nuevo la solicitud exacta con la misma clave. Reconcilie el pedido original antes de iniciar un nuevo intento. |
| Se ha excedido el límite de velocidad | HTTP 429, lo que puede incluir código IS-4429 | Cumpla Retry-After cuando esté presente, luego intente de nuevo con el mismo cuerpo y clave. |
| Tiempo de espera o pérdida de conexión del cliente | El cliente puede no recibir respuesta, aunque el procesamiento continúa | Conserve la clave y el cuerpo, reconcilie el pedido y solo intente de nuevo según el procedimiento documentado de tiempo de espera. |
| Validación o error de negocio no recuperable | un 4xx respuesta | Corrija la solicitud y utilice una nueva clave. No intente de nuevo un cuerpo modificado bajo la antigua clave. |
Utilice un retroceso exponencial limitado con jitter. No envíe múltiples intentos concurrentes para la misma operación de negocio.
Ver Errores de la API para el modelo de código de respuesta y tiempos de espera del cliente para el procedimiento de reconciliación.
Verifique el comportamiento en el entorno de prueba#
- Envíe una solicitud válida y compatible a
https://api.sandbox.deuna.iocon una nueva clave UUID v4. - Guarde su código de estado, encabezados de respuesta y payload.
- Envíe la misma solicitud exactamente nuevamente con la misma clave y cuerpo.
- Verifique que la segunda respuesta tenga el mismo resultado e incluya
X-Idempotency-Replay: true. - Durante una prueba negativa separada, modifique el cuerpo pero mantenga la clave y verifique que DEUNA devuelva un código HTTP
400conIS-4401.
No reutilice ninguna de las claves de prueba para otra operación en el entorno de prueba.