# Payment Lifecycle

This page is the one place payment status is defined. Other guides link here rather than restate it.

---

## Status fields

Every response that carries a payment — `GET /payments/{payment_id}`, `POST /initialize-payment`, `POST /finalize-payment`, `POST /payments/{payment_id}/capture` — and the `PaymentCompleted` webhook carry the same status fields:

| Field | Values | Use it for |
|-------|--------|------------|
| `lifecycleStatus` | the seven lifecycle values below | **Branch on this.** A closed enum: a new value is a documented, versioned change. |
| `status` | the previous values today; the lifecycle values after the switch announced in the [changelog](https://docs.fynex.ai/payments-api/v2/docs#tag/changelog) (no earlier than 2026-12-28) | Existing integrations. Read `lifecycleStatus` now and the switch does not affect you. |
| `legacyStatus` | the previous values (`provider_completed`, `settled`, …) — deprecated | Only while migrating off the previous values. It is removed after a notice period. |
| `settlementStatus` | `pending`, `in_flight`, `settled`, `deposit_confirmed` — on `GET /payments/{payment_id}` only, once money is captured | How far captured money has travelled. |

In GraphQL, `GenericPayment.lifecycleStatus` is the same closed enum (`PaymentLifecycleStatus`); `GenericPayment.status` is the internal value and does not switch.

---

## Lifecycle status

| `lifecycleStatus` | Meaning | Final? |
|-------------------|---------|--------|
| `pending` | Created, being routed, or waiting on the provider or the customer (3-D Secure, a redirect). Nothing is reserved on the card yet. | No |
| `under_review` | Held by a compliance or provider risk review before authorization. It resolves to `pending` (review cleared, processing resumes), `captured`, `failed` or `cancelled`. | No |
| `authorized` | Funds are reserved on the card and waiting for your capture (manual capture only). | No |
| `captured` | Money was taken. It stays `captured` through settlement, after a partial refund, and while a refund is in flight or has failed. | No — it can still be refunded |
| `refunded` | The whole captured amount was refunded. | **Yes** |
| `failed` | Declined or errored before any money was taken. `failureCode`, `retry` and `failureCategory` say why and whether to retry. | **Yes**, with one exception (below) |
| `cancelled` | Called off before any money was taken, including an expired checkout. | **Yes**, with one exception (below) |

**The one exception.** A payment is recorded `failed`, or `cancelled` by an expired checkout, when Fynex could not see that the charge went through. If the provider's own authoritative event later proves the money was taken, the payment is corrected to `captured`. No ordinary event can revive a `failed` or `cancelled` payment; only that proof can.

### Previous values

`legacyStatus` (and `status`, until the switch) carries the previous, finer-grained values. Each maps to exactly one lifecycle status:

| `legacyStatus` | `lifecycleStatus` | `settlementStatus` |
|----------------|-------------------|--------------------|
| `draft`, `new`, `routed`, `provider_pending` | `pending` | — |
| `compliance_review`, `provider_risk_review` | `under_review` | — |
| `authorized` | `authorized` | — |
| `provider_completed` | `captured` | `pending` |
| `funds_in_flight` | `captured` | `in_flight` |
| `settled` | `captured` | `settled` |
| `deposit_confirmed` | `captured` | `deposit_confirmed` |
| `refund_pending`, `refund_failed`, `refund_cancelled` | `captured` | the stage reached before the refund |
| `refunded` | `refunded` | the stage reached before the refund |
| `failed` | `failed` | — |
| `cancelled` | `cancelled` | — |

---

## Transitions

```flow
caption: The main transitions. Every transition is listed in the table below.
node create 0 1 | Create | | | end
node pending 1 1 | pending | Every payment starts here | | warn
node review 2 0 | under_review | Compliance or risk review | | warn
node captured 3 1 | captured | Successful payment rests | | ok
node refunded 4 1 | refunded | Whole amount refunded | | end ok
node authorized 2 2 | authorized | Waits for your capture
node failed 2 3 | failed / cancelled | Declined, voided or expired | | end error
edge create pending
edge pending review vh both | review
edge pending captured | automatic capture
edge pending authorized vh | manual capture
edge pending failed vh
edge review captured hv
edge authorized captured hv | capture
edge authorized failed
edge captured refunded | full refund
edge captured failed dashed vh both | rare / correction
```

| From | To | When |
|------|----|------|
| `pending` | `authorized` | The provider authorized a manual-capture payment. |
| `pending` | `captured` | The provider authorized and captured an automatic-capture payment. |
| `pending` | `under_review` | A compliance or risk review holds the payment before authorization. |
| `pending` | `failed` / `cancelled` | Declined, errored, 3-D Secure abandoned, or the checkout expired. |
| `under_review` | `pending` | A compliance review cleared the payment; processing resumes. |
| `under_review` | `captured` | A provider risk review released the payment. |
| `under_review` | `failed` / `cancelled` | The review declined it, or it was not resolved in time. |
| `authorized` | `captured` | You captured it (`POST /payments/{payment_id}/capture`). |
| `authorized` | `under_review` | The provider placed it in risk review. |
| `authorized` | `failed` / `cancelled` | The capture failed, or the provider voided the authorization. |
| `captured` | `refunded` | Refunds reached the whole captured amount. |
| `captured` | `failed` / `cancelled` | Rare: the provider reversed a capture it had reported. |
| `failed` / `cancelled` | `captured` | Only a provider-proven correction (see above). |

A partial refund, a refund in flight and a failed refund leave the payment `captured`; each refund has its own status (see [Refunds](#refunds)).

**Initial, intermediate, final.** Every payment starts `pending`. `under_review` and `authorized` are intermediate: something still has to happen. `captured` is where a successful payment rests; only refunds move it on. `refunded`, `failed` and `cancelled` are final.

**Retrying a failed payment.** Branch on `retry`: `safe` means the same request with the same `Idempotency-Key` can succeed; `fix_first` and `never` mean it cannot. On hosted checkout the buyer can try again on the same session after a decline — the payment goes back to `pending` for the new attempt.

---

## What you can do next

`GET /payments/{payment_id}` answers this for you: `allowedActions` lists exactly the actions below that the payment accepts right now (`capture`, `partial_capture`, `refund`, `partial_refund`; empty when none), and `captureMode` says whether it captures automatically or waits for your capture. The table spells out the rules behind it.

| Action | Allowed when | Result |
|--------|--------------|--------|
| Capture — `POST /payments/{payment_id}/capture` | `lifecycleStatus` is `authorized` and the payment was created with manual capture | `captured` |
| Refund — `POST /payments/{payment_id}/refund` | `lifecycleStatus` is `captured`, `settlementStatus` is not `in_flight`, no refund is in flight, and refunds are enabled for your account | A refund object with `status: pending`; the payment stays `captured` until the whole amount is refunded |
| Cancel a refund — `POST /refunds/{refund_id}/cancel` | The refund is still `pending` (supported for card refunds) | Refund `cancelled`; the payment stays `captured` |
| Cancel an authorization | Not available yet. An uncaptured authorization is not released by Fynex; the card issuer releases the hold on its own schedule. | — |

A capture or refund requested outside these conditions is refused — with `409` when the payment's status does not allow it.

### Manual and automatic capture

A payment captures automatically when created with `autoSettlement: true` on `POST /initialize-payment`, or when the request leaves `autoSettlement` out and your account's default is automatic capture. Otherwise — including `autoSettlement` left out on an account without that default — it is a manual-capture payment: it rests at `authorized` until you capture it.

### Partial capture

Pass an `amount` lower than the authorized amount to `POST /payments/{payment_id}/capture`. The payment becomes `captured`, the same as a full capture; the capture response's `amount` is what was captured. A second capture of the same authorization is not supported.

### Cancellation and expiry

`cancelled` is set by the system, not by an API call: the provider voided the payment, or a hosted checkout expired before payment (`failureCode` `9002`, "checkout session expired"). There is no Fynex-side expiry of an uncaptured authorization.

A customer who abandons 3-D Secure ends in whichever expiry comes first: the authentication session's, which fails the payment with `failureCode` `3003` (authentication not completed), or the hosted checkout's, which cancels it with `9002`. Neither authorized anything, and neither is revived by replaying the request — the customer has to pay again.

---

## Refunds

Each refund is its own object with its own `status` — `pending`, `succeeded`, `failed` or `cancelled` — on `GET /payments/{payment_id}/refunds` and `GET /refunds/{refund_id}`. The payment's `lifecycleStatus`:

- stays `captured` while a refund is `pending`, after it `failed` or was `cancelled`, and after a partial refund;
- becomes `refunded` once succeeded refunds add up to the whole captured amount.

Only one refund can be `pending` at a time. See [Captures & Refunds](https://docs.fynex.ai/payments-api/v2/docs#tag/captures-refunds) for the requests.

---

## Webhooks

| Event | Fires when | Status it carries |
|-------|-----------|-------------------|
| `PaymentCompleted` | The payment reaches `captured`, `failed` or `cancelled` — once per outcome. A payment reported `failed` and later proven `captured` (see the exception above) sends a second event with `lifecycleStatus: captured`; a repeat of an outcome already sent is not re-sent. | The same `status`, `lifecycleStatus` and `legacyStatus` as `GET /payments/{payment_id}` |
| `PaymentRefunded` | A refund reaches `succeeded`. | The refund's amount; `partial` says whether this refund was less than the payment |

A `PaymentCompleted` event is not by itself a success: branch on `lifecycleStatus`. There is no event yet for `authorized`, `under_review`, a failed refund or settlement progress — poll `GET /payments/{payment_id}` for those.

---

## Payout status

Payouts represent wallet-to-payee transfers sent via the banking provider.

### Status values

| Status | Meaning | Terminal? |
|--------|---------|-----------|
| `pending` | Payout created; funds held in the wallet; not yet submitted to the banking provider. Above the seller's approval threshold it also carries `approvalStatus: awaiting_approval` until approved. | No |
| `dispatching` | Claimed by the dispatcher; submission to the banking provider is in progress. | No |
| `processing` | Payout submitted to the banking provider. | No |
| `manual_reconciliation` | The provider's answer could not be matched to the row; an operator resolves it. It does not move on its own — raise it with Fynex. | No |
| `completed` | The banking provider confirmed the payment reached the destination. | **Yes** |
| `failed` | The banking provider rejected the payment or an error occurred. See `failureCode` and `failureDescription`. | **Yes** |
| `cancelled` | Payout was cancelled before it reached the banking provider. | **Yes** |

### Status transitions

| From | To | Trigger |
|------|----|---------|
| `pending` | `dispatching` | Internal dispatcher claims the row. |
| `dispatching` | `processing` | Submission to the banking provider accepted. |
| `processing` | `completed` | The banking provider's webhook confirms success. |
| `processing` | `failed` | The banking provider rejects or a timeout occurs. |
| `dispatching` | `manual_reconciliation` | The provider's answer could not be matched to the row; parked for an operator. |
| `manual_reconciliation` | `dispatching` | An operator resumes the dispatch. |
| `pending` | `cancelled` | Seller or system cancels before dispatch. |

### State diagram

```flow
node create 0 0 | POST /payouts | | | end
node pending 1 0 | pending | Funds held in the wallet | | warn
node dispatching 2 0 | dispatching | Submission in progress
node processing 3 0 | processing | Submitted to provider
node completed 4 0 | completed | Terminal | | end ok
node failed 4 1 | failed | Terminal | | end error
node manual 2 1 | manual_reconciliation | Answer unmatched; operator | | warn
node cancelled 1 1 | cancelled | Terminal, before dispatch | | end
edge create pending
edge pending dispatching | claimed
edge dispatching processing | accepted
edge processing completed | confirmed
edge processing failed | rejected / timeout
edge dispatching manual
edge manual dispatching | operator resumes
edge pending cancelled | seller or system
```

### Payout amount field

Send the payout amount as `amount`, a decimal string in **major units** (`"49.99"` for £49.99), like every other endpoint. The integer `amountMinor` (`4999`) is a deprecated fallback that `amount` overrides when both are present; responses return both.

---

---

## Failure codes

When a payment reaches `failed`, the response includes a numeric `failureCode`:

| Code | Name | Stage |
|------|------|-------|
| `0` | None | — |
| `1001` | Validation failed | validation |
| `1002` | Risk rejected | risk |
| `1003` | Routing failed | routing |
| `1004` | Compliance screening declined the transaction | compliance |
| `1005` | Compliance screening paused the transaction for review (and timed out or was rejected on review) | compliance |
| `1006` | Initialization interrupted before the provider was reached (retry safely) | checkout |
| `1007` | Card expired (rejected before the provider is called) | validation |
| `1008` | Card brand not accepted by the seller's acquiring agreement — refused by Fynex before the provider is called, or by the acquirer when the payment is set up or authorized; no funds move in any case | validation, or authorization when the acquirer refuses |
| `2001` | Provider declined | authorization |
| `2002` | Provider error | authorization |
| `2003` | Provider timeout | authorization |
| `2004` | Refund requested before settlement was ready (transient) | — (refund responses carry no stage) |
| `2005` | Address verification (AVS) failed | authorization |
| `3001` | Capture failed | capture |
| `3002` | Authentication required — issuer soft decline asking for 3-D Secure | authorization |
| `3003` | Authentication not completed — the customer did not finish 3-D Secure before the session expired; nothing was authorized | checkout |
| `4001` | Settlement failed | settlement |
| `5001` | Deposit-confirm timeout | deposit_confirm |
| `9001` | Cancelled by merchant | — |
| `9002` | Cancelled by system | — |
| `9999` | Unknown | — |

On `POST /initialize-payment`, `1007` and `1008` are also answered with HTTP `422` and a machine-readable `code` (`card_expired`, `card_brand_not_supported`) — replaying the same `Idempotency-Key` returns the same `422`. See *Common 422 responses* in [Errors](https://docs.fynex.ai/payments-api/v2/docs#tag/errors).

The `failureDescription` field carries a human-readable message from the provider. The `failureStage` field (`validation`, `risk`, `compliance`, `routing`, `checkout`, `authorization`, `capture`, `settlement`, `deposit_confirm`) tells you where in the pipeline the failure occurred — read it from the response rather than from the stage column above, which is indicative. The `retry` and `failureCategory` fields beside `failureCode` say whether re-sending can help; the full catalogue is in [Errors](https://docs.fynex.ai/payments-api/v2/docs#tag/errors).

---

---

## Polling for status

Webhooks are the fast path; polling is the backstop:

- Poll `GET /payments-api/v1/payments/{payment_id}` (bearer token; the path value is your `externalOrderRef`). Stop on `refunded`, `failed` or `cancelled`, or on `captured` if you do not need settlement progress.
- Poll `GET /payments-api/v1/payouts/{id}` for payout status.
- While a customer is on the hosted checkout page, the browser can use the SSE stream at `GET /checkout/{session_id}/events`, which uses its own checkout vocabulary.

See [Polling & SSE](https://docs.fynex.ai/payments-api/v2/docs#tag/polling-sse) for details.

## See also

- **[Captures & Refunds](https://docs.fynex.ai/payments-api/v2/docs#tag/captures-refunds)** — How to capture authorized payments and issue refunds.
- **[Payouts](https://docs.fynex.ai/payments-api/v2/docs#tag/payouts)** — Create and track wallet-to-payee payouts.
- **[Polling & SSE](https://docs.fynex.ai/payments-api/v2/docs#tag/polling-sse)** — Monitor payment status changes in real time.
- **[3DS Authentication](https://docs.fynex.ai/payments-api/v2/docs#tag/3ds)** — Handle 3DS challenges in the server-to-server flow.
