# Devolucion PIX

Inicia una devolucion (total o parcial) de una transaccion PIX recibida.

## Endpoint

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

## Headers

| Header | Tipo | Obligatorio | Descripcion |
|--------|------|-------------|-------------|
| `Authorization` | String | Si | `ApiKey {client_id}:{client_secret}` |
| `Content-Type` | String | Si | `application/json` |
| `hmac` | String | Si | Firma HMAC-SHA512 del body ([mas informacion](/es/hmac)) |
| `Idempotency-Key` | String | No | Clave unica para evitar procesamiento duplicado (max 256 chars) |
| `X-Key-Case` | String | No | Defina como `camelCase` para recibir los campos de la respuesta en camelCase (default es snake_case) |

## Request Body

| Campo | Tipo | Obligatorio | Descripcion | Ejemplo |
|-------|------|-------------|-------------|---------|
| `original_transaction_id` | String | Si | ID de la transaccion PIX original recibida (vea aliases abajo) | `"7popu57v6us7p6pcicgq12345"` |
| `amount` | Integer | No | Valor a devolver en **centavos** en el request. Si se omite, devuelve el valor total de la transaccion original. | `3000` (R$ 30,00) |
| `reason` | String | No | Codigo de devolucion BACEN (vea tabla abajo). Default: `MD06`. No validado localmente - enviado directo al BACEN. | `"MD06"` |
| `description` | String | No | Descripcion de la devolucion. Usada como `return_reason` en el BACEN (hasta 140 caracteres). Default: `"Devolução PIX"`. | `"Devolución solicitada por el cliente"` |

::: tip Aliases aceptados
Para flexibilidad de integracion, el backend acepta multiples nombres para cada campo (primero encontrado gana):

- `original_transaction_id` (canonico)
  - `original_e2e_id` - acepta el E2E ID (prefijo `E`) de la transaccion original
  - `originalTransactionId` - camelCase
  - `transaction_id` - fallback
  - `end_to_end_id` - fallback
- `reason` (canonico)
  - `return_code` - alias directo
:::

::: warning Validacion de ownership
El backend valida si la transaccion original pertenece al **mismo merchant** de la API Key que esta llamando el endpoint. Si usted intenta devolver una transaccion de otro merchant, recibe HTTP 404 `"original transaction not found"` - misma respuesta de transaccion inexistente (por seguridad).
:::

::: danger Valores monetarios (request × response)
El `amount` del **request** es en **centavos** (R$ 1,00 = `100`). El `amount` de la **response** y de los webhooks es en **subcentavos** / unidades base (R$ 1,00 = `10000`). Ejemplo: envie `3000` para devolver R$ 30,00; la response retorna `300000`.

**NUNCA envie float/decimal**. Siempre envie entero en centavos en el request y divida valores de respuesta por 10.000 para mostrar en reales.
:::

::: warning Devolucion parcial -- como acompanar el valor remanescente
Para devolucion parcial, informe un `amount` menor que el valor original. El valor total de las devoluciones de una misma transaccion no puede exceder el valor original recibido.

El backend rastrea el acumulado en `total_refunded` y `remaining_refundable` en los webhooks `pix.refund.completed` y `pix.return.received`:

- `total_refunded` = suma de todas las devoluciones ya ejecutadas para aquel `end_to_end_id` original
- `remaining_refundable` = `amount_original - total_refunded` (cuanto aun puede ser devuelto)
- `is_partial` = `true` si esa devolucion fue parcial

Antes de hacer una segunda devolucion parcial, consulte el ultimo webhook `pix.refund.completed` para saber el `remaining_refundable` - si usted excede, el backend retorna HTTP 422 "Valor da devolução excede o valor original da transação".
:::

## Codigos de Devolucion

| Codigo | Descripcion |
|--------|-------------|
| `MD06` | Devolucion por acuerdo entre las partes |
| `BE08` | Fraude |
| `AM09` | Valor incorrecto |
| `SL02` | Error de liquidacion |
| `RR04` | Transaccion no reconocida (BACEN) |
| `FR01` | Fraude reportada en la ponta receptora (codigo BACEN observado en flujos MED) |

::: info Validacion del reason code
Monetarie **no valida** el campo `reason` localmente. El codigo es encaminado al BACEN via PACS.004.

- **Codigos BACEN validos** para devolucion PACS.004: `MD06`, `BE08`, `AM09`, `SL02`, `RR04`, `FR01`.
- **Descripciones amigables en el webhook `pix.return.received`**: el backend mantiene un mapa local (`return_reasons`) para `MD06`, `BE08`, `FR01`, `SL02`. Codigos fuera de ese mapa (ej: `RR04`, `AM09`) caen en descripcion generica `"Return from counterparty ({code})"` en el webhook.

El controller tambien acepta `return_code` como alias de `reason`.

Si usted envia un codigo no-BACEN, la devolucion sera rechazada por el BACEN posteriormente (422 asincronico via webhook `pix.payout.failed` con `reason_code` llenado).
:::

## Ejemplo

```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"
```

## Respuesta de Exito -- 202 (async)

Camino mas comun - devolucion aceptada y encolada.

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

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

::: info E2E ID con prefijo `D`
Diferente de una transaccion PIX OUT comun (E2E prefijo `E`), una devolucion tiene E2E con prefijo **`D`** (de "Devolucion"). Ese prefijo identifica el tipo ISO 20022 `pacs.004` en el BACEN. El `transaction_id` interno tambien tiene prefijo `PIXRET` para facilitar identificacion.
:::

## Respuesta de Exito -- 200 (fast-track)

Camino raro - devolucion liquidada sincronicamente antes de que la respuesta HTTP volviera.

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

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `worked` | Boolean | `true` indica que la solicitud fue aceptada |
| `transaction_id` | String | Identificador de la devolucion (prefijo `PIXRET`). Use para consultar status. |
| `end_to_end_id` | String | End-to-End ID de la devolucion en el SPI/BACEN (prefijo `D`, no `E`) |
| `amount` | Integer | Valor de la devolucion en **subcentavos** (÷ 10.000 para reales). `300000` = R$ 30,00 |
| `status` | String | `accepted` (HTTP 202 - en procesamiento) o `settled` (HTTP 200 - liquidada). **Nunca `processing`** en este campo del POST. |
| `detail` | String | Mensaje descriptivo |

## Respuesta de Error (404)

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

## Respuesta de Error (422) -- Saldo insuficiente

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

Ocurre cuando el saldo seguro (`min(TB, PG)` - ver [Saldo](/es/bank-balance)) es menor que el `amount` solicitado. Devolucion exige que la cuenta origen tenga saldo ≥ valor a devolver.

## Respuesta de Error (422) -- Valor excedido

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

Ocurre cuando el total de devoluciones (actual + anteriores) excederia el `amount` de la transaccion original. Devoluciones parciales son permitidas, pero la suma no puede sobrepasar el valor recibido.

## Respuesta de Error (400) -- original_transaction_id ausente

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

## Respuesta de Error (401)

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

::: tip Plazo para devolucion (BACEN)
Plazos definidos por el **BACEN** (no enforced localmente por el backend Monetarie - enviado directo al SPI y validado alli):

- **`MD06` (acuerdo)**: hasta **90 dias corridos** despues del recibimiento de la transaccion original
- **`BE08` (fraude)**: siguiendo los plazos del **MED regulatorio BACEN** (flujo de contestacion, ver [Infracciones](/es/infractions))
- **`FR01` (fraude reportada)**: mismo plazo MED BACEN
- **Demas codigos (`AM09`, `SL02`, `RR04`)**: validacion caso-a-caso en el BACEN; para logica operacional, trate como plazo maximo de 30 dias

El backend **no bloquea** devoluciones fuera del plazo - pasan en el POST (HTTP 202 `accepted`) y son rechazadas despues por el BACEN via webhook `pix.payout.failed` con `reason_code` especifico. Su sistema debe validar la edad de la transaccion original antes de llamar la API si necesita fail-fast.
:::

::: warning PACS.004 x MED: devoluciones iniciadas por infraccion BACEN
Si la devolucion que usted esta iniciando es **consecuencia de una infraccion BACEN** (notificacion via `pix.refund.requested`), **no llame este endpoint**. La devolucion MED es ejecutada automaticamente por el backend cuando el `analysis_result=AGREED` es decidido (defensa no enviada en el plazo O acatamiento manual via merchant portal). Llamar `POST /pix/refund` en paralelo puede generar duplicidad.

Este endpoint es para devoluciones **voluntarias** iniciadas por usted (ej: devolver un PIX recibido de mas, cancelar una venta, acuerdo comercial). Ver [Infracciones (flujo completo)](/es/infractions) para distinguir los dos escenarios.
:::
