<!-- ZenPays documentation · https://docs.zenpayz.com/docs/concepts/payment-flow -->

# Payment flow

A ZenPays payment has three participants: your server, your customer, and us.
You create an intent, your customer completes it, and we confirm the outcome and
settle the funds.

<Diagram
  slug="payment-flow"
  alt="Four swimlanes — your server, your customer, ZenPays and the payment provider. Your server creates a payment intent and redirects the customer to the hosted checkout, where they pick a method and confirm. ZenPays routes the payment to a provider, the provider calls back with the outcome, funds are credited net of fees, and your server receives a payment.succeeded webhook."
  caption="How a payment moves from intent to settled funds"
/>

## The steps

1. **You create a payment intent.** `POST /api/v1/payment-intents` with an
   amount and currency returns an intent in `requires_payment_method` and a
   hosted checkout URL. The intent expires after 20 minutes.

2. **Your customer completes it on the hosted checkout.** They choose a payment
   method from the options available for their currency and country.

3. **We route it.** When the checkout page loads payment options, we select a
   provider for that corridor. Which provider, and how it is chosen, is our
   concern — you never need to name one.

4. **The customer confirms.** The intent moves to `processing` under a lock, so
   a double submit cannot create two payments.

5. **The provider calls back.** We record the outcome on the transaction.

6. **Funds are credited.** Your wallet is credited net of fees *before* the
   intent is marked `succeeded` — so a `succeeded` intent always means the money
   reached your balance.

7. **You receive a webhook.** `payment.succeeded` (or `payment.failed`) is
   delivered to your endpoint. See [Webhooks](/docs/guides/webhooks/).

## Status transitions

An intent starts at `requires_payment_method`, moves to `processing` on
confirmation, and ends at `succeeded`.

:::note There is no `failed` intent status
A payment that fails returns the intent to `requires_payment_method` so the
customer can try a different method. The intent only leaves that state for
`succeeded`, or for `canceled` when it expires or is cancelled outright.

Judge a payment by its **transaction** status, or by the webhook you receive —
not by waiting for an intent status that never arrives.
:::

The transaction underneath the intent carries the detail: `initiated` →
`processing` → `success` or `failed`, plus states for the cases in between such
as `waiting_bank` and `waiting_customer`.

## What to build against

Treat the webhook as the source of truth, not the redirect. A customer can close
the tab before being returned to your site, and the payment still succeeds — the
webhook is what tells you.

Poll `GET /api/v1/payment-intents/{id}` only as a fallback if you have not
received a webhook.
