# 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