# Idempotência A idempotência permite a resubmissão segura de solicitações, eliminando o risco de duplicar operações involuntariamente. ## O Que é Idempotência? A idempotência garante que fazer a mesma chamada à API múltiplas vezes tem o mesmo efeito que fazê-la uma vez. Isto é crucial quando: - Ocorrem timeouts de rede - Não tem certeza se uma solicitação foi processada - Precisa tentar novamente solicitações falhadas de forma segura ## Como Funciona Para fazer uma solicitação idempotente, inclua um cabeçalho `Idempotency-Key` com um identificador único: ```bash curl -L -X POST 'https://api.test.easypay.pt/2.0/single' \ -H 'AccountId: 2b0f63e2-9fb5-4e52-aca0-b4bf0339bbe6' \ -H 'ApiKey: eae4aa59-8e5b-4ec2-887d-b02768481a92' \ -H 'Idempotency-Key: 435e08a0-e5a9-4216-acb5-44d6b96de612' \ -H 'Content-Type: application/json' \ --data-raw '{ "type": "sale", "value": 10.00, "currency": "EUR", "method": "cc" }' ``` ### O Que Acontece 1. **Primeira Solicitação**: A Easypay processa a solicitação e armazena a resposta com a chave de idempotência 2. **Solicitações Duplicadas**: Solicitações subsequentes com a mesma chave retornam a resposta armazenada sem reprocessar 3. **Payload Diferente**: Se o corpo da solicitação diferir, é retornado um erro para prevenir uso acidental incorreto ## Detetar Solicitações Repetidas Quando uma solicitação é uma repetição, a resposta inclui um cabeçalho `Idempotency-Replay`: ``` Idempotency-Replay: true ``` Isto ajuda-o a distinguir entre solicitações originais e repetidas na lógica da sua aplicação. ## Gerar Chaves de Idempotência **Boas Práticas**: - Use UUIDs (v4) ou ULIDs para chaves de idempotência - Gere uma nova chave para cada operação única - Comprimento máximo da chave: **50 caracteres** - As chaves devem ter entropia suficiente para prevenir colisões **Exemplos**: ```javascript // JavaScript (usando UUID v4) const { v4: uuidv4 } = require('uuid'); const idempotencyKey = uuidv4(); ``` ```python # Python (usando UUID v4) import uuid idempotency_key = str(uuid.uuid4()) ``` ```php // PHP (usando ramsey/uuid) use Ramsey\Uuid\Uuid; $idempotencyKey = Uuid::uuid4()->toString(); ``` ## Comportamento da Chave ### Chaves Específicas da Conta Duas solicitações com a mesma `Idempotency-Key` mas diferentes cabeçalhos `AccountId` são tratadas como **solicitações diferentes**. ``` Solicitação 1: AccountId: account-1, Idempotency-Key: key-123 Solicitação 2: AccountId: account-2, Idempotency-Key: key-123 Resultado: Ambas as solicitações são processadas independentemente ``` ### Comparação do Corpo da Solicitação A camada de idempotência compara o corpo da solicitação das solicitações recebidas com o original: - **Mesmo corpo**: Retorna a resposta em cache - **Corpo diferente**: Retorna um erro para prevenir uso acidental incorreto ```json { "status": "error", "message": "Idempotency Error: Request body differs from original request", "code": "IDEMPOTENCY_MISMATCH" } ``` ### Erros Transitórios As chaves de idempotência **não** são armazenadas para erros transitórios, permitindo-lhe tentar novamente com segurança: - `429 Too Many Requests` - `502 Bad Gateway` - `503 Service Unavailable` Para estes erros, pode tentar novamente com a mesma chave de idempotência. ### Expiração da Chave As chaves de idempotência são automaticamente removidas do sistema **24 horas** após a criação. Após a expiração, a mesma chave pode ser usada para uma nova solicitação. ## Quando Usar Idempotência ### Operações Suportadas As chaves de idempotência são suportadas em: - Solicitações **POST** - Solicitações **PATCH** ### Não Necessário Para - Solicitações **GET** (inerentemente idempotentes) - Solicitações **DELETE** (inerentemente idempotentes) ### Casos de Uso **Essencial**: - Criação de pagamentos - Processamento de reembolsos - Qualquer operação que modifique dados financeiros **Recomendado**: - Qualquer solicitação POST ou PATCH - Operações que criam ou atualizam recursos - Solicitações que podem ser tentadas novamente ## Estratégia de Retentativa com Idempotência Ao usar chaves de idempotência, siga esta estratégia de retentativa: ```javascript async function safeRequest(url, payload, maxRetries = 3) { const idempotencyKey = generateUUID(); for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, { method: 'POST', headers: { 'AccountId': process.env.EASYPAY_ACCOUNT_ID, 'ApiKey': process.env.EASYPAY_API_KEY, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const shouldRetry = response.headers.get('X-Easypay-Should-Retry'); if (response.ok) { return await response.json(); } if (shouldRetry === 'true' && i < maxRetries - 1) { await sleep(Math.pow(2, i) * 1000); // Backoff exponencial continue; } throw new Error(`Request failed: ${response.status}`); } catch (error) { if (i < maxRetries - 1) { await sleep(Math.pow(2, i) * 1000); continue; } throw error; } } } ``` ## Boas Práticas 1. **Use Sempre para Pagamentos**: Inclua chaves de idempotência em todas as solicitações relacionadas com pagamentos 2. **Gere Chaves Novas**: Crie uma nova chave de idempotência para cada operação única 3. **Armazene Chaves**: Guarde chaves de idempotência na sua base de dados para rastrear quais operações foram concluídas 4. **Verifique o Cabeçalho de Repetição**: Use o cabeçalho `Idempotency-Replay` para detetar solicitações repetidas 5. **Respeite a Expiração**: Não reutilize chaves após 24 horas 6. **Trate Incompatibilidades**: Se receber um erro de incompatibilidade de idempotência, gere uma nova chave ## Exemplo: Criar um Pagamento com Segurança ```javascript const axios = require('axios'); const { v4: uuidv4 } = require('uuid'); async function createPayment(paymentData) { const idempotencyKey = uuidv4(); try { const response = await axios.post( 'https://api.prod.easypay.pt/2.0/single', paymentData, { headers: { 'AccountId': process.env.EASYPAY_ACCOUNT_ID, 'ApiKey': process.env.EASYPAY_API_KEY, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json' } } ); // Verificar se esta foi uma solicitação repetida const wasReplayed = response.headers['idempotency-replay'] === 'true'; if (wasReplayed) { console.log('Este pagamento já foi processado'); } return response.data; } catch (error) { // Verificar se devemos tentar novamente const shouldRetry = error.response?.headers['x-easypay-should-retry']; if (shouldRetry === 'true') { // Tentar novamente com a mesma chave de idempotência // ... implementar lógica de retentativa } throw error; } } ``` ## Próximos Passos - [Tratamento de Erros](/docs/error-handling) - Aprenda sobre respostas de erro e estratégias de retentativa - [Início Rápido](/docs/quickstart) - Faça a sua primeira chamada idempotente à API - [Referência da API](/openapi) - Veja quais endpoints suportam idempotência