> ## 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

> Advanced refund handling in the Pix Indirect Plugin (BTG): distributed partial refunds across multiple internal accounts, and unblocking refunds or transfers stuck in PROCESSING.

The Pix Indirect Plugin (BTG) processes Pix refunds (devoluções) through the existing [Refund a Received Pix Transfer](/en/reference/midaz/plugins/indirect-pix/refund-a-received-pix-transfer) endpoint. Two capabilities extend that flow for real-world 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

***

When a received Pix is distributed internally across several accounts — for example, the principal amount to the customer's account and a fee to a fee account — a later MED/fraud refund may need to be debited partially from each of those accounts.

The standard refund flow debits from a single account. To split the debit, send an optional `operations` array in the request body. **The presence of `operations` is the only signal** — there is no new endpoint, env var, 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 OPERATIONS_SUM_MISMATCH`                   |
| `accountAlias` must not repeat within the request                      | `400 DUPLICATE_ACCOUNT_ALIAS`                   |
| each `amount` must be greater than `0`                                 | `400`                                           |
| each `amount` must have at most 2 decimal places                       | `400`                                           |
| each `accountAlias` must have participated in the original transaction | `422 ACCOUNT_ALIAS_NOT_IN_ORIGINAL_TRANSACTION` |
| the original transaction must exist in Midaz                           | `422 ORIGINAL_TRANSACTION_NOT_FOUND`            |

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

## Example — Cappta

***

A R$ 50,000.00 cash-in was split as R$ 49,000.00 to the customer account and R$ 1,000.00 to a fee account. After fraud, the required refund is R$ 1,000.82, but 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, while BTG receives a single R$ 1,000.82 refund — no manual ledger consolidation required.

# Unblocking stuck operations

***

A refund or transfer whose reversal call to BTG times out before confirmation can remain stuck in `PROCESSING`, with the hold still applied in Midaz. Two dedicated endpoints re-query BTG and drive the operation to its correct 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/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, preventing 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

***

When BTG no longer has the reversal/transfer (returns `404`), behavior 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`/`TRANSFER` 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/absent body or `false` preserves the strict behavior (a BTG 404 returns an error and never reverts the hold). Only opt in when you have confirmed the operation should 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 queries BTG for status, which does not apply to intra-PSP (internal) transfers — there is no BTG transaction. Calling unblock on an intra-PSP transfer returns a synchronous error (`PIX-0418`). Proper intra-PSP unblock logic is a future enhancement. See [Intra-PSP transfers](/en/midaz/plugins/pix/indirect/indirect-pix-intra-psp).

# Next steps

***

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