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.
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.
Scroll sideways to see the whole diagram →
Show as text
- Order: `PENDING` → `PAID` at finalized, or `PENDING` → `EXPIRED` when its session expires.
- `EXPIRED` → `PAID` when a late payment still settles.
- `EXPIRED` → `PENDING` when you create a new session with the same `order_id`.
- A cancelled session leaves the order `PENDING`. An order is never `CANCELED`.
- Payment: `PROCESSING` → `CONFIRMED` → `SUCCEEDED` at finalized.
- A payment can fail from `PROCESSING` or `CONFIRMED`, and `FAILED` → `PROCESSING` if the transfer lands after all.
- 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.
- Payments found by the reference sweep start at `CONFIRMED`.
- `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.
| Status | Meaning | What you can do |
|---|---|---|
OPEN | The link works until expires_at: between 5 minutes and 24 hours after create, 30 minutes by default. | Reissue the link, or cancel it. |
COMPLETED | Its payment finalized while the session was still OPEN. Fianto sends checkout.session.completed. | Nothing: the session is done. |
EXPIRED | Nobody paid before expires_at. Fianto sends checkout.session.expired. | Create a new session with the same order_id. |
CANCELED | You 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.
| Status | Meaning | What you can do |
|---|---|---|
PENDING | Waiting 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. |
PAID | A payment for it finalized. Fianto sends order.paid. | Fulfil the order. |
EXPIRED | Its 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→PAIDwhen a payment finalizes.PENDING→EXPIREDwhen its session expires.EXPIRED→PAIDwhen a late payment still settles.EXPIRED→PENDINGwhen your server creates a new session with the sameorder_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
| Status | Meaning | What you can do |
|---|---|---|
PROCESSING | The payer signed and Fianto sent the transaction to Solana. | Wait. |
CONFIRMED | Solana confirmed the transaction; it is not finalized yet. Payments that Fianto finds on its own start here. | Do not fulfil yet. |
SUCCEEDED | Finalized on Solana. | The order is PAID. |
FAILED | Fianto 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.PROCESSINGorCONFIRMED→FAILED.FAILED→PROCESSINGwhen 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
SUCCEEDEDand the orderPAID, and Fianto sendsorder.paid. When the session was stillOPEN, it becomesCOMPLETEDand Fianto also sendscheckout.session.completed. Nothing of this happens at confirmed; see Finality. - A payment for a closed session. A payment that finalizes after its session
EXPIREDor wasCANCELEDstill pays the order:PAIDandorder.paid, but nocheckout.session.completed. If a newer session was opened for the order in the meantime, the payment isFAILEDinstead, 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