Skip to Content
ConceptsSessions
View as Markdown

Sessions

A CheckoutSession is what the buyer interacts with: a time-limited, single-use object that owns the hosted checkout URL. It’s the lightest of the three core entities. Most of your logic works with Orders and Payment Intents (see below).

The three-entity data model

CheckoutSession ← 1:1 → Order ← 1:N → PaymentIntent (buyer) (catalog) (each pay attempt)
EntityPurposeLifetime
CheckoutSessionBuyer-facing — has session_key, checkout_url, TTLMinutes (default 30)
OrderYour catalog state — line items, totals, refundsPermanent record
PaymentIntentOne pay attempt on one chain/assetHours; settles or expires

You create a session and an order together (via POST /b2b/v1/checkout-sessions/quick). Each time the buyer picks an asset on the checkout page, a fresh PaymentIntent is opened against the right chain. If they switch assets mid-checkout, the previous intent goes to EXPIRED and a new one starts.

Identifier

A session is identified by its session_key:

cst_G-SO92J7HNWkwMHEHjD4oO1Z

URL-safe, about 24 characters after the prefix. The hosted checkout page is https://checkout.infraio.xyz/<session_key> — treat the session key as a bearer credential for that one checkout.

Lifecycle — CheckoutSession

StateMeans
ACTIVESession created and open. The checkout URL is usable.
COMPLETEDA PaymentIntent on this session settled. The Order is now PAID (or PARTIAL_PAID if underpaid).
EXPIREDexpires_at passed without settlement. Any open Orders are canceled.
CANCELEDExplicit cancel — either buyer hit “cancel” or you called the cancel endpoint.

The three terminal states are mutually exclusive and final. A new CheckoutSession against the same Order can be created if you want to retry (e.g., after underpayment).

TTL

  • Default: 30 minutes (configurable via the expires_in field on create, in seconds).
  • Bounds: There is no enforced minimum or maximum. Choose a value that matches your buyer’s expected decision window: under 60 seconds risks timing out legitimate buyers, and a session that stays open for more than 7 days is almost certainly abandoned.
  • Per-merchant default: You can set a default in the dashboard, but only POST /b2b/v1/checkout-sessions (two-step) honors it. POST /b2b/v1/checkout-sessions/quick uses 30 minutes when expires_in is omitted, regardless of the dashboard setting. To use a different default with /quick, send expires_in on every call.
  • Enforcement: A session whose expires_at has passed is treated as EXPIRED even if its state hasn’t updated yet, so don’t rely on the state value at the exact moment of expiry.

Underpayment

If a buyer sends less than the session amount, the PaymentIntent still settles for the partial amount and the Order transitions to PARTIAL_PAID. The CheckoutSession moves to COMPLETED (one PaymentIntent settled), so it’s no longer reusable.

To accept the shortfall as full payment, the order can be resolved to PAID. See Concepts → Orders (resolving isn’t available through the API; to stay self-serve, collect the remainder instead). To collect the remainder, create a new CheckoutSession against the same Order with the residual amount.

Overpayment

If the buyer sends more than the session amount (rare, but happens with manual transfers), InfraIO Pay detects the overpayment within 24 hours and notifies you. It isn’t refunded automatically. Issue the refund through the refund API or the dashboard.

Wrong-asset payments

The deposit address is generated for one session, network, and asset. If a buyer sends a different asset to it, the payment isn’t matched and the PaymentIntent stays open until the session expires. Support can help recover the funds, but it isn’t automatic. Tell buyers to send the exact asset shown on the checkout page.

On TRON, Solana and TON there is no deposit address, so this applies to EVM networks only: the buyer pays your wallet directly. See Direct-to-wallet networks.

Idempotency on create

POST /b2b/v1/checkout-sessions/quick accepts an idempotency_key field in the request body (note: body field, not HTTP header). Generate a UUID if you don’t have a natural key.

The key deduplicates the Order, not the CheckoutSession. On a retry with the same key, /quick returns the original order (order_id is stable) but mints a fresh CheckoutSession — a new session_key and checkout_url each time. That’s intentional: one Order can back several checkout attempts (see Orders), so a retried /quick hands the buyer a clean session without duplicating the order.

Two behaviors to be aware of:

  • The body is not hashed or compared. Reusing a key with a different body does not return 409 — the server silently returns the order already stored under that key and ignores the new body. So treat an idempotency_key as a one-shot token for a single logical order; never recycle one across different carts.
  • Only the Order is deduplicated, not the session. If you need the same checkout URL back, persist session_key / checkout_url from the first response — calling /quick again won’t return the old one. To enumerate every session minted against an order, use GET /b2b/v1/checkout-sessions/by-order/:order_id.

API endpoints

MethodPathNotes
POST/b2b/v1/checkout-sessions/quickCreate order + session in one call
POST/b2b/v1/checkout-sessionsCreate session against an existing order
GET/b2b/v1/checkout-sessions/by-order/:order_idList all sessions for an order (for retry history)

See the Quickstart for the full create request body and signing.

What’s next