Payment lifecycle
Understand checkout, payment and subscription states and decide when to fulfil an order.
Checkout sessions
| Status | Meaning |
|---|---|
open | The customer can still open the hosted checkout and pay. |
complete | A payment for the session was confirmed. |
expired | The 24-hour session expired or a related renewal link was closed. |
A checkout status is not a substitute for the payment record. Use the
payment_id returned on a completed session to retrieve the payment.
Payments
A payment record exists from the moment the customer submits the hosted
checkout's details form, before any card details are entered. It starts as
processing, so GET /payments?checkout_session_id=… finds an attempt that is
still under way, and an abandoned checkout leaves a processing or failed
payment behind.
| Status | Meaning | Terminal? | Fulfil? |
|---|---|---|---|
processing | The final gateway result is not known yet. | No | No |
succeeded | The payment was authenticated and recorded. | No: a refund still changes it | Yes |
failed | The attempt failed or was cancelled. | Almost always; see the recovery window below | No |
partially_refunded | Some of the successful payment was returned. | No: a further refund makes it refunded | Already fulfilled; apply your refund policy |
refunded | The full payment amount was returned. | Yes | Already fulfilled; apply your refund policy |
The browser's return URL carries payment_id, status and session_id as
convenient hints. Do not fulfil from those query parameters. Fulfil when you
receive a verified payment.succeeded event, or after GET /payments/:id
returns succeeded.
Deliveries can be delayed or duplicated. Make fulfilment idempotent using the
event id or payment id. If a payment stays processing, keep checking rather
than creating another charge automatically.
The recovery window
An attempt whose result never reached Utopia — the customer closed the tab, a
callback was lost — is looked up again at the card processor for 7 days after
the payment was created. That covers a payment still processing and one closed
as failed before the processor gave its verdict, which is why failed is not
quite final: a late capture found this way settles as a normal succeeded
payment and sends payment.succeeded then. After 7 days an unresolved payment
is left as it stands.
What happens after checkout
- Your server creates a checkout session and sends the customer to its
checkout_url. - The customer fills in their details and submits the form. The payment record
is created as
processing. - They enter their card on the processor's secure page. Card details never reach Utopia or your server.
- They come back to Utopia's result page, which confirms the outcome with the
processor. On success the session becomes
complete, the paymentsucceeded, andpayment.succeededis queued for your endpoints. A decline leaves the sessionopenand sendspayment.failed. - The customer is shown the outcome, and where they go next depends on
return_url.
A declined card shows a failure page telling the customer the payment was not
successful and to retry or use another payment method. What return_url changes:
- With a
return_url, a successful payment is shown briefly and then the browser goes to your URL withpayment_id,statusandsession_idappended. After a decline the page offers a link back to your URL instead — it does not redirect on its own, and it does not offer a second attempt, so handle the retry on your own page. - Without a
return_url, the result page is the end of the journey. After a decline it offers Try payment again, which reopens the same checkout session so the customer can try another card. The session stays usable until it is paid or its 24 hours run out.
If the processor's answer has not arrived by the time the customer returns, the
page checks ten times over about 45 seconds and then offers a Check payment
status button. The payment stays processing and the recovery window above
resolves it.
Subscriptions
Subscriptions use active, on_hold and cancelled. A successful renewal
creates a normal payment whose billing_reason is subscription_cycle.
See Subscriptions for the two renewal engines and
cancellation behaviour.
A refund, made from the dashboard or with POST /v1/payments/{payment_id}/refunds
(see Refunds), updates the payment and emits
refund.succeeded.