Checkout Features and Flows
Developer Resources Checkout Checkout Features and Flows Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for the Checkout module.
Use this page for the checkout domain: what it does for customers and admins, and how each flow behaves from start to finish.
Source Type Files or Docs What Was Extracted API apps/api/src/modules/checkout/customer/checkout-customer.controller.ts, admin/checkout-admin.controller.tsRoutes, permissions, rate limits Backend shared/checkout-session.service.ts, checkout-payment.service.ts, checkout-release.service.ts, checkout-transition.util.ts, checkout-validation.util.tsOne transaction, transitions, attempt scoping Schema packages/db/src/schema/checkout/{checkout-session,checkout-session-item,checkout-session-promotion,enums}.tsGrand-total CHECK, partial uniques, expiry Workers workers/checkout-expiry-sweep.processor.ts, checkout-maintenance.scheduler.tsThe sweep and its exemption Error registry apps/api/src/common/types/error-codes.ts (// CHECKOUT)CHECKOUT_* codes
Field Value Module checkoutSubmodule N/A Primary user value A validated, price-frozen purchase attempt that holds stock for fifteen minutes, recoverable from every failure short of a silent gateway Actors Customer (signed in), admin (read + release only), payment module (future) Main entry points /api/mobile/checkout (3 routes), /api/checkout/sessions (4 routes)Main outputs Checkout sessions with frozen pricing, reservations, and a payment contract Related docs API , Backend
Actor Can Do Cannot Do Auth Requirement Notes Customer Start checkout, fetch their active session, cancel their own pending session Call payment-side transitions (no HTTP surface), see another customer's session (404, never 403), cancel while a payment is at the gateway JWT CUSTOMER_CHECKOUT_ATTEMPT 10/min (start), CUSTOMER_READ 60/min, CUSTOMER_WRITE 20/min — account-keyedAdmin List, read, cancel (fraud/duplicate), force-expire (stuck at gateway) Edit or delete a session — a purchase contract an operator can reprice is not a contractAdmin JWT + Checkout_READ/Checkout_UPDATE Both release actions record which administrator acted Payment (future) markPaymentStarted / complete / fail— Internal Attempt-scoped contract — see backend §6
Capability Surface Actor Route/Trigger State Read State Written Linked API Section Start checkout Customer Customer POST /api/mobile/checkoutcart, address, inventory, promotion session + holds (one tx) API §4 Active session Customer Customer GET /api/mobile/checkout/activesessions — API Cancel own session Customer Customer POST /:id/cancelsession status + releases API Admin list Admin Admin GET /api/checkout/sessionssessions — API Admin detail Admin Admin GET /:idone session — API Admin cancel Admin Admin POST /:id/cancelsession releases + audit API Admin force-expire Admin Admin POST /:id/expiresession releases + audit API Payment transitions Internal Payment (future) service calls session status + attempt backend §6
A customer confirms their basket. The server validates everything (cart readiness, product availability, stock, address serviceability, coupon), reserves inventory and the promotion slot, freezes prices, and commits it all in one transaction . The customer is shown the frozen total and sent to the payment gateway.
Branch Condition Behavior Error/Result Repeat POST Live session exists Same session, 200 — double-click/retry/two tabs safe200, one live sessionEmpty cart No cart or no lines 409 CHECKOUT_CART_EMPTYStale cart version Another device changed it 409 CHECKOUT_CART_CHANGEDBlocking validation Cart not ready 409 + details.blockingReasons CHECKOUT_CART_NOT_READYProduct gone Withdrawn/deleted 409 + unavailableVariantIds CHECKOUT_PRODUCT_UNAVAILABLENot enough stock Shortfall 409 + shortfalls[] CHECKOUT_INSUFFICIENT_STOCKAddress problem Unknown/archived/not theirs 404 CHECKOUT_ADDRESS_NOT_FOUNDUnserviceable No delivery to district 409 CHECKOUT_ADDRESS_NOT_SERVICEABLECoupon inapplicable Does not apply 409 + details.reason — retry without the coupon is valid CHECKOUT_COUPON_NOT_APPLICABLE
The session holds stock for the session TTL plus a full payment window (CHECKOUT_HOLD_TTL_SECONDS) — the holds deliberately outlive the session, because inventory and promotion sweep their own holds on their own schedules and would reclaim them mid-payment. The countdown runs from server-computed expiresInSeconds; at zero the session reads expired (the read predicate, regardless of any background job) and the customer starts again.
A retryable failure returns the session to pending_payment with the holds intact — the customer reaches for a second card. A non-retryable one releases everything.
cancel — for a pending_payment session an operator judges fraudulent or duplicated.
expire — for a session stuck at a payment gateway that never called back. The only lever for that state , because the automatic sweep deliberately never touches payment_in_progress.
Both release the held stock and the coupon slot, unlock the cart, and record which administrator acted.
List (status/customer/date/total filters, offset pagination, sort by createdAt/updatedAt/expiresAt/grandTotal) and detail. Admin may read and release, never edit — no update route, no delete route: a purchase contract an operator can reprice is not a contract.
From Event/Action To Guard Condition Side Effects — start pending_paymentOne live session per customer (partial unique) Holds taken, prices frozen pending_paymentmarkPaymentStartedpayment_in_progressexpires_at > now()Attempt number assigned payment_in_progresscompletecompletedSame attempt Order may be created elsewhere payment_in_progressfail (retryable)pending_paymentSame attempt Holds intact payment_in_progressfail (non-retryable)cancelledSame attempt Holds released pending_payment / payment_in_progresscancel / force-expire cancelledNot already terminal Holds + coupon released, cart unlocked any non-terminal sweep (past expires_at) expiredpayment_in_progress exemptHolds released, audit
Every payment-side transition carries the attempt number. The retry makes the state machine cyclic, and once a cycle exists "a duplicate matches nothing" stops being true — a redelivered decline for a superseded attempt would knock a live payment back and let the real success no-op silently.
Flow DB Writes Cache Effects Jobs Realtime Analytics Notifications Start session + items + promotions + holds (one tx) — — — — — Cancel (customer/admin) status + releases + audit — — — — — Force-expire status + releases + audit — — — — — Sweep expired sessions + releases — checkout queue — — —
Checkout caches nothing. The module is synchronous apart from the sweep.
Scenario Trigger User/System Experience Recovery Source Stale cart Cart changed elsewhere 409 Reload, show changes, retry CHECKOUT_CART_CHANGEDPayment declined Card rejected Session back to pending Second card — holds intact retryable fail Session expired Customer dawdled 409 CHECKOUT_SESSION_EXPIRED Start again read predicate Not cancellable Already terminal 409 Refresh CHECKOUT_SESSION_NOT_CANCELLABLEPayment in progress Gateway live 409 CHECKOUT_PAYMENT_IN_PROGRESS Finish or contact support attempt guard Silent gateway Never calls back Session stuck Admin force-expire sweep exemption
Actor capability diagram — §3/§4.
Sequence diagram per major flow — §5.1.
State machine diagram — §5.3/§7.
Data side-effect diagram — §9.
Error branch diagram — §10.
Feature Minor Behavior Actor Trigger User/System Result Backend Side Effect Source Start Unknown property rejected Customer Extra field in body 400 — not silently stripped strict DTO Start Repeat POST Customer Double-click Same session, 200 partial unique Start cartVersion optionalCustomer No loaded cart Omitted → no conflict — Start reserved: false lineCustomer Untracked product Normal, not failure nothing held Start priceChange.changedCustomer Price moved Warning, not blocker frozen at current Start Discount from reservation System Promotion re-evaluates Session agrees with ledger own row lock Start 404 same for others/unknown Customer Wrong id No existence oracle — Cancel Own pending only Customer Live payment 409 PAYMENT_IN_PROGRESS — Admin cancel Fraud/duplicate Admin Judgment Release + audit — Admin expire Stuck gateway Admin No callback Only lever — Sweep Exempt payment_in_progress System Past expiry Never released mid-payment partial index Payment Attempt scoping System Redelivered decline Harmless for old attempt attempt number
Rule Business Reason Actor Impact Enforced In API Impact Backend Impact Tests One transaction No partial state Failed checkout leaves nothing DbExecutor threading — atomic commit int spec Holds outlive session Sweeps don't know sessions No mid-payment reclamation TTL constant expiresInSecondsCHECKOUT_HOLD_TTL_SECONDSspec Expiry as read predicate No stale pay-for-stock window Status always correct reads status: expiredsweep writes only to record spec payment_in_progress sweep-exemptNo unfulfillable charge Stuck sessions possible partial index — admin force-expire spec Grand total CHECK No wrong charge Display any part schema totals arithmetic invariant probe One live per customer Double-click story 200 on repeat partial unique — — spec Retryable fail keeps holds Declined card ≠ lost basket Second card possible transition — — spec Attempt-scoped transitions Cyclic machine safe Redelivered decline harmless transition util — attempt number spec Discount from reservation Session agrees with ledger Consistent service — — spec Everything snapshotted History immutable Receipts never join live tables schema — — probe
Product Decision User Benefit Engineering Benefit Alternative Tradeoff Risk One transaction No orphan state Small module Saga/coordinator All modules must accept DbExecutor Already true Holds TTL = session + payment window No mid-payment loss Simple Session-length holds Longer holds Released on expire immediately Expiry as predicate Always correct No flip job Stored status Compute per read Cheap Sweep exempts gateway No unfulfillable charge — Sweep everything Stuck sessions Admin force-expire Attempt scoping Safe retries — Idempotency only Cyclic machine Attempt number Admin read-only Contract integrity — Admin edit Can't fix errors Cancel/expire only Snapshots everywhere Immutable history — References Storage Accepted
Flow Edge Case Trigger Expected Behavior User/System Feedback Source Start Empty cart No lines 409 CART_EMPTY Start Stale version Cart changed 409 CART_CHANGED Start Shortfall Stock dropped 409 + shortfalls INSUFFICIENT_STOCK Start Unserviceable Bad district 409 ADDRESS_NOT_SERVICEABLE Start Coupon invalid Not applicable 409 + reason COUPON_NOT_APPLICABLE Start Repeat Live session 200 same session — Cancel Expired Lapsed 409 SESSION_EXPIRED Cancel At gateway Payment live 409 PAYMENT_IN_PROGRESS Payment Redelivered decline Old attempt No-op — attempt Payment Decline then success Attempt 2 Success wins — attempt Sweep Gateway never calls Silent Session stuck force-expire Expiry race Customer pays at boundary expires_at passesPayment transition guarded START_PAYMENT requires expires_at > now()
Flow Reads Writes Cache Jobs/Events Response Fields Start cart, address, inventory, promotion session + items + promotions + holds — — checkout, pricing, items, promotions, shipping, payment Active/cancel sessions status + releases — — session/whole shape Admin list/detail sessions — — — rows + pagination Admin cancel/expire session status + releases + audit — — message Sweep sessions expired + releases — checkout queue —