Solicitações idempotentes
Tente novamente as operações de pedido e pagamento da DEUNA sem criar uma operação duplicada.
Nesta página
Use uma chave de idempotência quando uma falha de rede ou um tempo limite do cliente deixam o resultado de uma solicitação de alteração de estado incerto. A DEUNA associa a chave à operação e ao corpo da solicitação, e então reproduz a resposta armazenada em vez de processar a mesma operação novamente.
Fluxo de repetição#
O comerciante é responsável pela geração, persistência e tempo de repetição. A DEUNA é responsável pela associação entre a chave, a operação, o corpo da solicitação e a resposta armazenada dentro da janela de retenção documentada.
Operações suportadas#
Enviar X-Idempotency-Key com as seguintes operações públicas:
| Operação | Método e caminho | Referência |
|---|---|---|
| Criar um pedido · Fluxo opcional | POST /merchants/orders | Criar um pedido |
| Criar um pagamento · Compra V2 | POST /v2/merchants/orders/purchase | Criar um pagamento |
| Capturar um pagamento autorizado | POST /v2/merchants/orders/{order_token}/capture | Capturar um pagamento |
| Anular uma autorização | POST /merchants/orders/{order_token}/void | Anular uma autorização |
| Reembolsar um pagamento | POST /v2/merchants/orders/{order_token}/refund | Reembolsar um pagamento |
Não assuma que todas POST as pontas de API suportam idempotência. Use a lista de operações acima ou verifique se a referência da ponta inclui X-Idempotency-Key header.
Gerar e persistir a chave#
Gere um UUID v4 em seu backend de comerciante para cada operação comercial distinta. Armazene-o com seu próprio registro de operação antes de chamar a DEUNA.
const idempotencyKey = crypto.randomUUID();Siga estas regras:
- Reutilize a mesma chave apenas ao tentar novamente a mesma operação.
- Envie o mesmo corpo de solicitação em cada tentativa. A DEUNA compara o hash do corpo, portanto, mesmo um corpo semanticamente equivalente serializado de forma diferente pode ser rejeitado.
- Gere uma nova chave para um novo pagamento, captura, cancelamento, reembolso ou tentativa de criação de pedido.
- Gere uma nova chave quando você corrige os dados da solicitação após uma validação ou erro comercial.
- Não derive chaves de uma sequência curta que pode colidir em diferentes sistemas ou ambientes.
A chave está vinculada ao comerciante e à operação, mas sua integração ainda deve tratá-la como globalmente única e nunca reutilizá-la intencionalmente para outra ação.
Enviar a solicitação#
Adicione a chave à solicitação original e a cada tentativa dessa solicitação. Este exemplo "Compra V2" usa um token de pedido que a integração já criou:
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 a resposta tiver X-Idempotency-Replay: true, a DEUNA retornou o resultado armazenado para uma solicitação anterior com a mesma chave e corpo. Trate seu código de status e payload exatamente como a resposta original.
Entenda a janela de retenção#
A associação chave-corpo começa quando a DEUNA recebe a chave pela primeira vez. A janela de reprodução da resposta começa quando a DEUNA armazena o resultado concluído, e seu comportamento depende desse resultado:
| Resultado original | Comportamento de reprodução |
|---|---|
| Resposta bem-sucedida | A DEUNA armazena a resposta por 24 horas por padrão após a conclusão da operação. Uma tentativa com a mesma chave e corpo durante esse período retorna o código de status, os cabeçalhos e o payload armazenados. |
4xx ou 5xx resposta | A DEUNA armazena a resposta de erro por aproximadamente 2 segundos para absorver duplicatas imediatas. A associação entre a chave e o corpo da solicitação original permanece por um período de 24 horas. |
| Solicitação em andamento | Uma solicitação concorrente aguarda brevemente o primeiro resultado. Se a primeira solicitação ainda estiver em execução, a DEUNA pode retornar um código HTTP 409 com IS-4409. |
Após o término do período de retenção da resposta bem-sucedida, a mesma chave pode ser tratada como uma nova operação. Nunca dependa desse comportamento: crie uma nova chave para cada nova tentativa, mesmo quando uma chave antiga expirou.
Para uma resposta de erro, não reutilize a chave com os dados da solicitação corrigidos. Como a associação do corpo original permanece ativa, o corpo alterado retorna IS-4401 até o término do período de retenção. Corrija a solicitação e envie-a como uma nova operação com uma nova chave.
Gerenciar retentativas e conflitos#
| Situação | O que a DEUNA retorna | O que sua integração deve fazer |
|---|---|---|
| Mesma chave e mesmo corpo após uma solicitação bem-sucedida | A resposta armazenada com X-Idempotency-Replay: true | Aceitar a resposta e reconciliar o estado atual do pedido quando não for final. |
| Mesma chave com um corpo diferente | HTTP 400, código IS-4401 | Não tente novamente essa combinação. Use uma nova chave somente quando o corpo alterado representar uma operação corrigida ou nova. |
| Duplicata chega enquanto a primeira solicitação ainda está em execução | HTTP 409, código IS-4409, quando a espera em andamento termina | Retorne, e tente novamente a solicitação exata com a mesma chave. Reconcilie o pedido original antes de iniciar uma nova tentativa. |
| Limite de taxa excedido | HTTP 429, o que pode incluir código IS-4429 | Respeite Retry-After quando presente, e tente novamente o mesmo corpo e chave. |
| Tempo limite do cliente ou perda de conexão | O cliente pode não receber resposta, embora o processamento continue | Mantenha a chave e o corpo, reconcilie o pedido e tente novamente apenas de acordo com o procedimento documentado de tempo limite. |
| Validação ou erro de negócio não recuperável | Um 4xx resposta | Corrija a solicitação e use uma nova chave. Não tente novamente um corpo alterado sob a mesma chave. |
Use um backoff exponencial limitado com jitter. Não envie múltiplas tentativas concorrentes para a mesma operação de negócio.
Veja Erros da API para o modelo de código de resposta e tempo limite do cliente para o procedimento de reconciliação.
Verifique o comportamento no ambiente de teste#
- Envie uma solicitação válida para
https://api.sandbox.deuna.iocom uma nova chave UUID v4. - Salve o código de status, os cabeçalhos de resposta e o payload.
- Envie a solicitação exata novamente com a mesma chave e corpo.
- Verifique se a segunda resposta tem o mesmo resultado e inclui
X-Idempotency-Replay: true. - Durante um teste negativo separado, altere o corpo, mas mantenha a chave e verifique se a DEUNA retorna um código HTTP
400comIS-4401.
Não reutilize nenhuma das chaves de teste para outra operação no ambiente de teste.