Concepts

Payment lifecycle

How a checkout session, its order and each payment move between statuses, which events each move sends, and why no status is final too early.

4 min read

A one-time checkout involves three records. The checkout session is the payment link your server creates. The order is what your server asked the payer to pay, keyed by your order_id. A payment is one transaction the payer signed, which Fianto broadcasts to Solana and follows until it is final. Each has its own statuses, and they move together only at a few points.

Order and payment statuses
How an order's and a payment's status can changeOrderfinalizedexpiresnew session, same order_idlate settlementPENDINGPAIDEXPIREDA cancelled session leaves it PENDINGPaymentfound by sweepfinalizedfailstransfer landsfailsalso superseded at finalized,though its funds landedPROCESSINGCONFIRMEDSUCCEEDEDFAILED

Scroll sideways to see the whole diagram →

Show as text
  1. Order: `PENDING` → `PAID` at finalized, or `PENDING` → `EXPIRED` when its session expires.
  2. `EXPIRED` → `PAID` when a late payment still settles.
  3. `EXPIRED` → `PENDING` when you create a new session with the same `order_id`.
  4. A cancelled session leaves the order `PENDING`. An order is never `CANCELED`.
  5. Payment: `PROCESSING` → `CONFIRMED` → `SUCCEEDED` at finalized.
  6. A payment can fail from `PROCESSING` or `CONFIRMED`, and `FAILED` → `PROCESSING` if the transfer lands after all.
  7. A payment whose session was superseded (a newer session for the order, or changed order terms) is recorded `FAILED` at finalized, even though its funds landed. It never shows as `SUCCEEDED` first.
  8. Payments found by the reference sweep start at `CONFIRMED`.
  9. `SUCCEEDED` is final. `EXPIRED` and `FAILED` are not.

Checkout session

Sessions of both modes, payment and subscription, use these statuses. A session starts OPEN and leaves it once.

StatusMeaningWhat you can do
OPENThe link works until expires_at: between 5 minutes and 24 hours after create, 30 minutes by default.Reissue the link, or cancel it.
COMPLETEDIts payment finalized while the session was still OPEN. Fianto sends checkout.session.completed.Nothing: the session is done.
EXPIREDNobody paid before expires_at. Fianto sends checkout.session.expired.Create a new session with the same order_id.
CANCELEDYou cancelled it through the API, or the payer cancelled it on the checkout page. Fianto sends checkout.session.canceled and no order event.Create a new session with the same order_id.

A session can be cancelled only while it is OPEN with no live payment. Fianto also cancels an OPEN subscription session itself when its link is outdated, because your receiving wallet or the treasury changed since the session took its snapshot of the plan: the payer's attempt is refused with checkout_link_outdated, and checkout.session.canceled is sent.

Order

A subscription session creates no order; only payment-mode sessions have one.

StatusMeaningWhat you can do
PENDINGWaiting for a payment. A cancelled session leaves the order here, with no end date.Wait, or create a new session for the same order_id.
PAIDA payment for it finalized. Fianto sends order.paid.Fulfil the order.
EXPIREDIts session expired before a payment finalized. Fianto sends order.expired.Create a new session for the same order_id: the order returns to PENDING.

The moves are:

  • PENDING → PAID when a payment finalizes.
  • PENDING → EXPIRED when its session expires.
  • EXPIRED → PAID when a late payment still settles.
  • EXPIRED → PENDING when your server creates a new session with the same order_id. The order takes the new request's terms. A cancelled session's order is reused the same way.

Only a PAID order refuses a new session, with 409 order_already_paid. No status is ever written to an order for a cancel: the order simply stays PENDING.

Payment

StatusMeaningWhat you can do
PROCESSINGThe payer signed and Fianto sent the transaction to Solana.Wait.
CONFIRMEDSolana confirmed the transaction; it is not finalized yet. Payments that Fianto finds on its own start here.Do not fulfil yet.
SUCCEEDEDFinalized on Solana.The order is PAID.
FAILEDFianto did not accept this payment. It can fail from PROCESSING or CONFIRMED.Check whether the USDC arrived anyway before you tell the payer anything.

The moves are:

  • PROCESSING → CONFIRMED → SUCCEEDED.
  • PROCESSING or CONFIRMED → FAILED.
  • FAILED → PROCESSING when the transfer lands after all.

A payment whose session was superseded, by a newer session for the same order or by changed order terms, is recorded FAILED at finalized, even though its funds landed. It is never shown as SUCCEEDED first: Fianto decides this in the same step that settles the payment. SUCCEEDED is final.

FAILED does not mean no money moved

A FAILED payment can have its USDC in your wallet: a transfer that finalized with the wrong amount or fee, a transfer that lands after Fianto wrote it off, or a payment whose session was superseded. Check the payment before you tell a payer anything. See Orders and payments.

Neither EXPIRED (order) nor FAILED (payment) is final, so keep your webhook route listening after each of them.

Where the three meet

  • At finalized. The payment becomes SUCCEEDED and the order PAID, and Fianto sends order.paid. When the session was still OPEN, it becomes COMPLETED and Fianto also sends checkout.session.completed. Nothing of this happens at confirmed; see Finality.
  • A payment for a closed session. A payment that finalizes after its session EXPIRED or was CANCELED still pays the order: PAID and order.paid, but no checkout.session.completed. If a newer session was opened for the order in the meantime, the payment is FAILED instead, with its funds landed.

The expiry sweep

Every 30 seconds Fianto expires OPEN sessions whose time has run out. A payment in flight keeps a session from expiring. An expired session sends checkout.session.expired, and its order becomes EXPIRED and sends order.expired.

A session can also lapse without the sweep having reached it. When your server then creates a new session for the same order_id, the lapsed one is expired and replaced: it still sends checkout.session.expired, but there is no order.expired, because the order is reopened for the new session.

The reference sweep

Every session has a reference, and the transfer of the price carries it. Every 60 seconds Fianto looks for transfers with the reference of each open session, and keeps looking for 24 hours after a session closes. This catches transfers that Fianto did not broadcast itself. A payment found this way starts at CONFIRMED.

A transfer that carries a session's reference but is not that session's payment is recorded on the order. Every such transfer is recorded as an unmatched transfer; Fianto also sends order.duplicate_payment when the order is already PAID or has another payment that has not failed. Its duplicate names that transfer's signature and amount. Otherwise no webhook is sent. Fianto records up to 20 unmatched transfers per session and ignores any beyond that.

Duplicate and unmatched transfers reached your wallet

In both cases the USDC is in your wallet. Fianto never holds your funds and has no refund API: refund the payer yourself, from your own wallet.

See also

Was this page helpful? Tell us

On this page