> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Refund operations

> Handle Pix refund edge cases via BTG — distributed partial refunds (devoluções) across internal accounts and unblocking operations stuck in PROCESSING.

The Pix Indirect Plugin (BTG) processes Pix refunds (devoluções) through the [Refund a Received Pix Transfer](/en/reference/midaz/plugins/indirect-pix/refund-a-received-pix-transfer) endpoint. Two capabilities extend that flow for MED and fraud scenarios:

* **Distributed partial refunds** — return a refund from multiple internal accounts in a single operation
* **Unblock** — recover a refund or transfer that is stuck in `PROCESSING`

# Distributed partial refunds

***

A received Pix can split internally across several accounts. For example, the principal amount goes to the customer account and a fee goes to a fee account. A later MED or fraud refund can then debit part of the amount from each account.

The standard refund flow debits one account. To split the debit, send an optional `operations` array in the request body. **The `operations` array is the only signal** — there is no new endpoint, environment variable, or feature flag.

## Request

***

```json theme={null}
POST /v1/transfers/{transfer_id}/refunds
{
  "amount": "1000.82",
  "description": "MED Cappta",
  "operations": [
    { "accountAlias": "alias-conta-cliente", "amount": "0.82" },
    { "accountAlias": "alias-conta-fee", "amount": "1000.00" }
  ]
}
```

* Without `operations` → the current single-account flow runs unchanged.
* With `operations` → the plugin debits each `accountAlias` for its `amount` in Midaz, and BTG receives a single pacs.004 for the **total** refund value.

## Validation rules

***

| Rule                                              | Error                                  |
| ------------------------------------------------- | -------------------------------------- |
| `sum(operations[].amount)` must equal `amount`    | `400 PIX-0447` Operations Sum Mismatch |
| `accountAlias` must not repeat within the request | `400 PIX-0448` Duplicate Account Alias |
| each `amount` must be greater than `0`            | `400 PIX-0004` Invalid Field Values    |
| each `amount` must have at most 2 decimal places  | `400 PIX-0004` Invalid Field Values    |
| the request must contain at most 50 operations    | `400 PIX-0013` Limit Exceeded          |
| the original cash-in must exist                   | `404 PIX-0425` Cashin Not Found        |

<Note>
  The endpoint, BTG flow (pacs.004 with the total value), idempotency, and authentication match the standard refund. Only the internal Midaz debit composition changes.
</Note>

## Example — Cappta

***

The plugin split a R$ 50,000.00 cash-in: R$ 49,000.00 to the customer account and R$ 1,000.00 to a fee account. After fraud, the refund must be R$ 1,000.82. Only R$ 0.82 remains in the customer account. The `operations` array debits R$ 0.82 from the customer account and R$ 1,000.00 from the fee account. BTG receives a single R$ 1,000.82 refund with no manual ledger consolidation.

# Unblocking stuck operations

***

A reversal call to BTG can time out before BTG confirms it. The refund or transfer then stays stuck in `PROCESSING`, and Midaz still holds the funds. Two endpoints re-query BTG and drive the operation to its terminal state.

| Method | Endpoint                                                                                                 | Unblocks                                   |
| ------ | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `POST` | [`/v1/refunds/{refund_id}/unblock`](/en/reference/midaz/plugins/indirect-pix/unblock-a-pix-refund)       | A refund stuck in `PROCESSING`             |
| `POST` | [`/v1/transfers/{transfer_id}/unblock`](/en/reference/midaz/plugins/indirect-pix/unblock-a-pix-transfer) | A transfer stuck in `PENDING`/`PROCESSING` |

Both require the `X-Account-Id` header.

## How unblock works

***

The plugin re-queries the reversal or transfer status at BTG and:

* If BTG reports `CONFIRMED` or `ERROR` → dispatches the corresponding settlement and moves the operation to its terminal state.
* If BTG still reports `INITIATED`/`PROCESSING` → returns **HTTP 200** with no action — retry later.

A consistency guard aborts the operation if BTG's `entity`, `returnIdentification`, or `originalEndToEndId` diverge from the local record. This prevents settlement against the wrong transaction.

```json theme={null}
POST /v1/refunds/{refund_id}/unblock
X-Account-Id: <account-id>
```

The response includes the updated `refund`, a `message`, and the `btgStatus`.

## Handling a 404 from BTG

***

BTG can return `404` when it no longer has the reversal or transfer. The result then depends on the operation type and the opt-in `allowNotFoundUnblock` flag in the request body.

| Operation                                                                                                      | Default (strict)                                 | With `allowNotFoundUnblock: true`                                                                                                                                                      |
| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Transfer** ([`/v1/transfers/{id}/unblock`](/en/reference/midaz/plugins/indirect-pix/unblock-a-pix-transfer)) | Returns an error                                 | Reverts the Midaz hold (best-effort, idempotent), marks the cashout `FAILED` with `BTG_NOT_FOUND`, emits a `CASHOUT` outbound webhook, and returns `200` with `btgStatus: "NOT_FOUND"` |
| **Refund** ([`/v1/refunds/{id}/unblock`](/en/reference/midaz/plugins/indirect-pix/unblock-a-pix-refund))       | Returns `PIX-1012` (`ErrProviderRefundNotFound`) | Reverts the hold and drives the refund to its terminal state                                                                                                                           |

```json theme={null}
POST /v1/transfers/{transfer_id}/unblock
{
  "allowNotFoundUnblock": true
}
```

<Warning>
  `allowNotFoundUnblock` defaults to `false`. An empty or absent body, or `false`, preserves the strict behavior. A BTG 404 then returns an error and never reverts the hold. Opt in only when you have confirmed that the operation must be released.
</Warning>

<Note>
  The opt-in 404 recovery applies only to `CASHOUT` operations in `PENDING`/`PROCESSING` with a non-empty `endToEndId`. Cash-ins and terminal statuses never enter this branch.
</Note>

## Intra-PSP limitation

***

The transfer unblock flow resolves state by querying BTG. It does not apply to intra-PSP (internal) transfers, which have no BTG transaction. See [Intra-PSP transfers](/en/rails/pix/btg/indirect-pix-intra-psp).

# Next steps

***

* [Intra-PSP transfers](/en/rails/pix/btg/indirect-pix-intra-psp) — Internal P2P transfers and refunds
* [MED 2.0 — Funds Recovery](/en/rails/pix/btg/indirect-pix-med-2-funds-recovery) — Cross-account fraud recovery
* [Webhooks](/en/rails/pix/btg/indirect-pix-webhooks) — Refund and transfer event handling
* [API reference](/en/reference/midaz/plugins/indirect-pix/create-entry) — Full API documentation
