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

# Payment lifecycle

> The payment-order state machine — from AWAITING_APPROVAL to SUCCESS, FAILED, CANCELED, EXPIRED, or REFUNDED.

Every payment order traverses a deterministic sequence of states from creation to a final state. The states are visible in the `status` field of every order and they drive webhook events.

## States

| Status              | Type               | Meaning                                                                                                               |
| ------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `AWAITING_APPROVAL` | Initial (OUT only) | Outbound order created. Awaiting `PUT .../approve` or `PUT .../cancel`. **No money has moved.**                       |
| `PENDING`           | Transitional       | Order accepted by the platform and submitted to the provider. The provider has not yet returned a definitive outcome. |
| `PROCESSING`        | Transitional       | Provider has acknowledged the order and is executing it.                                                              |
| `SUCCESS`           | Final              | Provider confirmed settlement. Funds moved (in/out).                                                                  |
| `FAILED`            | Final              | Provider rejected or could not complete the order. `errorCode` and `errorMessage` are populated.                      |
| `CANCELED`          | Final              | Order canceled before reaching the provider. (Only possible for OUT orders in `AWAITING_APPROVAL`.)                   |
| `EXPIRED`           | Final              | Inbound dynamic QR/EMV expired before the payer settled.                                                              |
| `REFUNDED`          | Final              | A previously `SUCCESS` order was refunded.                                                                            |

A final state is **terminal** — `status` will not change again. To reverse a `SUCCESS`, you create a new order in the opposite direction (or a refund flow, where supported).

## Inbound (IN) state machine

```mermaid theme={"dark"}
stateDiagram-v2
    [*] --> PENDING: POST (direction: IN)
    PENDING --> PROCESSING: provider accepts
    PROCESSING --> SUCCESS: payer settles
    PROCESSING --> FAILED: provider rejects
    PROCESSING --> EXPIRED: payer does not settle within expiresIn
    PENDING --> EXPIRED: dynamic QR expires
    SUCCESS --> REFUNDED: refund flow
```

Inbound orders start in `PENDING` immediately. There is no approval step — the platform submits to the provider as part of `POST`.

| Transition               | When                                                                                |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `PENDING` → `PROCESSING` | Provider has issued the PIX payment instrument (QR rendered, copy-paste available). |
| `PROCESSING` → `SUCCESS` | A payer has scanned/paid; the PIX network confirms settlement.                      |
| `PROCESSING` → `EXPIRED` | `expiresIn` elapses without settlement.                                             |
| `PROCESSING` → `FAILED`  | Provider rejects the order (rare for PIX).                                          |
| `SUCCESS` → `REFUNDED`   | Operator initiates a refund.                                                        |

## Outbound (OUT) state machine

```mermaid theme={"dark"}
stateDiagram-v2
    [*] --> AWAITING_APPROVAL: POST (direction: OUT)
    AWAITING_APPROVAL --> PENDING: PUT .../approve
    AWAITING_APPROVAL --> CANCELED: PUT .../cancel
    PENDING --> PROCESSING: provider accepts
    PROCESSING --> SUCCESS: provider settles
    PROCESSING --> FAILED: provider rejects
```

Outbound orders pause in `AWAITING_APPROVAL` until you explicitly approve. This gate is where you implement business rules — dual control, anti-fraud, balance verification.

| Transition                       | When                                                                                                       |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `AWAITING_APPROVAL` → `PENDING`  | `PUT .../approve` succeeds. Funds become `locked` on the wallet.                                           |
| `AWAITING_APPROVAL` → `CANCELED` | `PUT .../cancel`. No money moved. Order is permanently canceled.                                           |
| `PENDING` → `PROCESSING`         | Provider acknowledged receipt.                                                                             |
| `PROCESSING` → `SUCCESS`         | Funds settled at the recipient. `locked` decreases, `amount` decreases.                                    |
| `PROCESSING` → `FAILED`          | Provider rejected or recipient unreachable. `locked` decreases, `amount` unchanged. `errorCode` populated. |

## What you can do per state

| Action             | `AWAITING_APPROVAL` |                 `PENDING`                 | `PROCESSING` | `SUCCESS` | `FAILED` | `CANCELED` | `EXPIRED` | `REFUNDED` |
| ------------------ | :-----------------: | :---------------------------------------: | :----------: | :-------: | :------: | :--------: | :-------: | :--------: |
| `GET` (read order) |          ✅          |                     ✅                     |       ✅      |     ✅     |     ✅    |      ✅     |     ✅     |      ✅     |
| `PUT .../approve`  |     ✅ (OUT only)    | ❌ → `PAYMENT_ORDER_NOT_AWAITING_APPROVAL` |       ❌      |     ❌     |     ❌    |      ❌     |     ❌     |      ❌     |
| `PUT .../cancel`   |     ✅ (OUT only)    |     ❌ → `PAYMENT_ORDER_INVALID_STATE`     |       ❌      |     ❌     |     ❌    |      ❌     |     ❌     |      ❌     |

`approve` and `cancel` are valid **only** in `AWAITING_APPROVAL`. Any attempt at another state returns `PAYMENT_ORDER_NOT_AWAITING_APPROVAL` or `PAYMENT_ORDER_INVALID_STATE` (HTTP 422).

## Webhook events per transition

Every state transition fires a webhook event:

| Transition     | Event                      |
| -------------- | -------------------------- |
| Order created  | `payment_order.created`    |
| → `PROCESSING` | `payment_order.processing` |
| → `SUCCESS`    | `payment_order.success`    |
| → `FAILED`     | `payment_order.failed`     |
| → `CANCELED`   | `payment_order.canceled`   |
| → `EXPIRED`    | `payment_order.expired`    |
| → `REFUNDED`   | `payment_order.refunded`   |
| Approved       | `payment_order.approved`   |

See [Payment events](/get-started/webhooks/payment-events) for full event payloads.

## Polling vs. webhooks

* **Webhooks** are the recommended source of truth — they push immediately on transition.
* **Polling** (`GET .../paymentOrders/{id}`) is a valid fallback when you cannot expose a webhook endpoint, but be mindful of [rate limits](/get-started/concepts/rate-limiting).

A reasonable polling cadence for transient states:

| State        | Polling interval   |
| ------------ | ------------------ |
| `PENDING`    | Every 5–10 seconds |
| `PROCESSING` | Every 5–10 seconds |
| Final states | Stop polling       |

## Error fields on `FAILED`

When `status` is `FAILED`, the order exposes:

```json theme={"dark"}
{
  "status": "FAILED",
  "errorCode": "INSUFFICIENT_FUNDS",
  "errorMessage": "Insufficient available balance to complete this transfer."
}
```

| Field          | Notes                                                                                         |
| -------------- | --------------------------------------------------------------------------------------------- |
| `errorCode`    | Provider-reported code in `UPPER_SNAKE_CASE`. The catalog evolves with provider integrations. |
| `errorMessage` | Human-readable description. Suitable for direct display in some cases.                        |

Common `errorCode` values include `INSUFFICIENT_FUNDS`, `RECIPIENT_INVALID`, `LIMIT_EXCEEDED`. Branch on `errorCode`, not `errorMessage`.

## `EXPIRED` is final, even if the payer pays late

For inbound dynamic QR codes, `EXPIRED` is **terminal**. If the payer pays after expiration:

* The PIX network typically rejects the payment at their end (the QR is no longer valid).
* In rare cases where settlement is force-completed, the funds may still appear in your wallet. They will not transition the order — they remain as an unattributed balance entry visible in the wallet ledger.

To handle this safely: do not promise the payer immediate fulfillment based solely on `SUCCESS`. Reconcile against your local ledger.

## State invariants

A few rules to internalize:

* A payment order cannot move from a final state to a transitional state.
* `direction` is set at creation and never changes.
* `idempotencyKey` is set at creation and never changes.
* `amount` and `network` are set at creation and never change.
* `errorCode` and `errorMessage` are populated only on `FAILED`; `null` otherwise.
* `processedAt` is `null` until the order leaves `PENDING` for the first time; once set, it does not change.

## Next

<CardGroup cols={3}>
  <Card title="Payments overview" icon="money-bill-transfer" href="/get-started/payments/overview">
    The payment-order resource model.
  </Card>

  <Card title="Pix cash-in tutorial" icon="arrow-down-to-line" href="/get-started/payments/pix-cash-in">
    End-to-end inbound flow.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/get-started/webhooks/overview">
    Receive state transitions in real time.
  </Card>
</CardGroup>
