# Devolução PIX

Inicia uma devolução (total ou parcial) de uma transação PIX recebida.

## Endpoint

```
POST /api/external/pix/refund
```

## Headers

| Header | Tipo | Obrigatório | Descrição |
|--------|------|-------------|-----------|
| `Authorization` | String | Sim | `ApiKey {client_id}:{client_secret}` |
| `Content-Type` | String | Sim | `application/json` |
| `hmac` | String | Sim | Assinatura HMAC-SHA512 do body ([saiba mais](/hmac)) |
| `Idempotency-Key` | String | Não | Chave única para evitar processamento duplicado (max 256 chars) |
| `X-Key-Case` | String | Não | Defina como `camelCase` para receber os campos da resposta em camelCase (padrão é snake_case) |

## Request Body

| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|-------|------|-------------|-----------|---------|
| `original_transaction_id` | String | Sim | ID da transação PIX original recebida (veja aliases abaixo) | `"7popu57v6us7p6pcicgq12345"` |
| `amount` | Integer | Não | Valor a devolver em **centavos** no request. Se omitido, devolve o valor total da transação original. | `3000` (R$ 30,00) |
| `reason` | String | Não | Código de devolução BACEN (veja tabela abaixo). Padrão: `MD06`. Não validado localmente - enviado direto ao BACEN. | `"MD06"` |
| `description` | String | Não | Descrição da devolução. Usada como `return_reason` no BACEN (até 140 caracteres). Padrão: `"Devolução PIX"`. | `"Devolução solicitada pelo cliente"` |

::: tip Aliases aceitos
Para flexibilidade de integração, o backend aceita múltiplos nomes para cada campo (primeiro encontrado vence):

- `original_transaction_id` (canônico)
  - `original_e2e_id` - aceite o E2E ID (prefixo `E`) da transação original
  - `originalTransactionId` - camelCase
  - `transaction_id` - fallback
  - `end_to_end_id` - fallback
- `reason` (canônico)
  - `return_code` - alias direto
:::

::: warning Validação de ownership
O backend valida se a transação original pertence ao **mesmo merchant** da API Key que está chamando o endpoint. Se você tentar devolver uma transação de outro merchant, recebe HTTP 404 `"original transaction not found"` - mesma resposta de transação inexistente (por segurança).
:::

::: danger Valores monetários (request × response)
O `amount` do **request** é em **centavos** (R$ 1,00 = `100`). O `amount` da **response** e dos webhooks é em **subcentavos** / unidades base (R$ 1,00 = `10000`). Exemplo: envie `3000` para devolver R$ 30,00; a response retorna `300000`.

**NUNCA envie float/decimal**. Sempre envie inteiro em centavos no request e divida valores de resposta por 10.000 para exibir em reais.
:::

::: warning Devolução parcial -- como acompanhar o valor remanescente
Para devolução parcial, informe um `amount` menor que o valor original. O valor total das devoluções de uma mesma transação não pode exceder o valor original recebido.

O backend rastreia o acumulado em `total_refunded` e `remaining_refundable` nos webhooks `pix.refund.completed` e `pix.return.received`:

- `total_refunded` = soma de todas as devoluções já executadas para aquele `end_to_end_id` original
- `remaining_refundable` = `amount_original - total_refunded` (quanto ainda pode ser devolvido)
- `is_partial` = `true` se essa devolução foi parcial

Antes de fazer uma segunda devolução parcial, consulte o último webhook `pix.refund.completed` para saber o `remaining_refundable` - se você exceder, o backend retorna HTTP 422 "Valor da devolução excede o valor original da transação".
:::

## Códigos de Devolução

| Código | Descrição |
|--------|-----------|
| `MD06` | Devolução por acordo entre as partes |
| `BE08` | Fraude |
| `AM09` | Valor incorreto |
| `SL02` | Erro de liquidação |
| `RR04` | Transação não reconhecida (BACEN) |
| `FR01` | Fraude reportada na ponta recebedora (código BACEN observado em fluxos MED) |

::: info Validação do reason code
O Monetarie **não valida** o campo `reason` localmente. O código é encaminhado ao BACEN via PACS.004.

- **Códigos BACEN válidos** para devolução PACS.004: `MD06`, `BE08`, `AM09`, `SL02`, `RR04`, `FR01`.
- **Descrições amigáveis no webhook `pix.return.received`**: o backend mantém um mapa local (`return_reasons`) para `MD06`, `BE08`, `FR01`, `SL02`. Códigos fora desse mapa (ex: `RR04`, `AM09`) caem em descrição genérica `"Return from counterparty ({code})"` no webhook.

O controller também aceita `return_code` como alias de `reason`.

Se você enviar um código não-BACEN, a devolução será rejeitada pelo BACEN posteriormente (422 assíncrono via webhook `pix.refund.failed` com `reason_code` preenchido).
:::

## Exemplo

```bash
BODY='{"amount":3000,"description":"Devolução acordo","original_transaction_id":"7popu57v6us7p6pcicgq12345","reason":"MD06"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')

curl -X POST https://api.monetarie.com/api/external/pix/refund \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -d "$BODY"
```

## Resposta de Sucesso -- 202 (async)

Caminho mais comum - devolução aceita e enfileirada.

```json
{
  "worked": true,
  "transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
  "end_to_end_id": "D24313102202604071509K14UmbMt6ck",
  "amount": 300000,
  "status": "accepted",
  "detail": "Devolucao processada"
}
```

HTTP 202 - acompanhe o resultado via `GET /transactions/:id` (use o `transaction_id` retornado) ou aguarde o webhook `pix.refund.completed` / `pix.payout.returned`.

::: info E2E ID com prefixo `D`
Diferente de uma transação PIX OUT comum (E2E prefixo `E`), uma devolução tem E2E com prefixo **`D`** (de "Devolução"). Esse prefixo identifica o tipo ISO 20022 `pacs.004` no BACEN. O `transaction_id` interno também tem prefixo `PIXRET` para facilitar identificação.
:::

## Resposta de Sucesso -- 200 (fast-track)

Caminho raro - devolução liquidada sincronamente antes da resposta HTTP voltar.

```json
{
  "worked": true,
  "transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
  "end_to_end_id": "D24313102202604071509K14UmbMt6ck",
  "amount": 300000,
  "status": "settled",
  "detail": "Devolução liquidada"
}
```

| Campo | Tipo | Descrição |
|-------|------|-----------|
| `worked` | Boolean | `true` indica que a requisição foi aceita |
| `transaction_id` | String | Identificador da devolução (prefixo `PIXRET`). Use para consultar status. |
| `end_to_end_id` | String | End-to-End ID da devolução no SPI/BACEN (prefixo `D`, não `E`) |
| `amount` | Integer | Valor da devolução em **subcentavos** (÷ 10.000 para reais). `300000` = R$ 30,00 |
| `status` | String | `accepted` (HTTP 202 - em processamento) ou `settled` (HTTP 200 - liquidada). **Nunca `processing`** neste campo do POST. |
| `detail` | String | Mensagem descritiva |

## Resposta de Erro (404)

```json
{
  "worked": false,
  "errors": {
    "not_found": "Transação original não encontrada"
  }
}
```

## Resposta de Erro (422) -- Saldo insuficiente

```json
{
  "worked": false,
  "errors": {
    "unprocessable_entity": "insufficient balance"
  }
}
```

Ocorre quando o saldo seguro (`min(TB, PG)` - ver [Saldo](/bank-balance)) é menor que o `amount` solicitado. Devolução exige que a conta origem tenha saldo ≥ valor a devolver.

## Resposta de Erro (422) -- Valor excedido

```json
{
  "worked": false,
  "errors": {
    "unprocessable_entity": "Valor da devolução excede o valor original da transação"
  }
}
```

Ocorre quando o total de devoluções (atual + anteriores) excederia o `amount` da transação original. Devoluções parciais são permitidas, mas a soma não pode ultrapassar o valor recebido.

## Resposta de Erro (400) -- original_transaction_id ausente

```json
{
  "worked": false,
  "errors": {
    "bad_request": "original_transaction_id is required"
  }
}
```

## Resposta de Erro (401)

```json
{
  "worked": false,
  "errors": {
    "unauthorized": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
  }
}
```

::: tip Prazo para devolução (BACEN)
Prazos definidos pelo **BACEN** (não enforced localmente pelo backend Monetarie - enviado direto ao SPI e validado lá):

- **`MD06` (acordo)**: até **90 dias corridos** após o recebimento da transação original
- **`BE08` (fraude)**: seguindo os prazos do **MED regulatório BACEN** (fluxo de contestação, ver [Infrações](/infractions))
- **`FR01` (fraude reportada)**: mesmo prazo MED BACEN
- **Demais códigos (`AM09`, `SL02`, `RR04`)**: validação caso-a-caso no BACEN; para lógica operacional, trate como prazo máximo de 30 dias

O backend **não bloqueia** devoluções fora do prazo - elas passam no POST (HTTP 202 `accepted`) e são rejeitadas depois pelo BACEN via webhook `pix.refund.failed` com `reason_code` específico. Seu sistema deve validar a idade da transação original antes de chamar a API se você precisa fail-fast.
:::

::: warning PACS.004 x MED: devoluções iniciadas por infração BACEN
Se a devolução que você está iniciando é **consequência de uma infração BACEN** (notificação via `pix.refund.requested`), **não chame este endpoint**. A devolução MED é executada automaticamente pelo backend quando o `analysis_result=AGREED` é decidido (defesa não submetida no prazo OU acatamento manual via merchant portal). Chamar `POST /pix/refund` em paralelo pode gerar duplicidade.

Este endpoint é para devoluções **voluntárias** iniciadas por você (ex: devolver um PIX recebido a mais, cancelar uma venda, acerto comercial). Ver [Infrações (fluxo completo)](/infractions) para distinguir os dois cenários.
:::
