Checkout
The sequence
Section titled “The sequence”- The cart holds
{ productId, variantId, quantity, options }per line and a destination country, and asksPOST /api/shipping/quotefor the rates that apply. Nothing displayable is stored; every price on the cart page is re-derived from the current catalogue on render. - Checkout posts the same lines to
POST /api/checkout. The server loads every referenced product from the live catalogue, reads each variant’s price from the database, checks stock, refuses anything not published to Stripe, sums the subtotal, resolves shipping again from the same function the cart used, and creates a Checkout Session withallow_promotion_codes, address collection if any line is physical,automatic_taxif tax is on, and the store’sPUBLIC_URLas success and cancel targets. - The shopper pays on Stripe’s page.
- Stripe redirects to
/confirm?session_id=…. The page pollsGET /api/checkout/:sessionIdand shows the order once its status ispaid. - Stripe’s webhook delivers
checkout.session.completed. The handler records the order, decrements inventory, links it to a verified customer account if one matches, sends the confirmation email, and queues anorder.paidoutbound event.
Step 5 can arrive before or after step 4. The confirmation page copes with either.
Three rules
Section titled “Three rules”- Line items are built server-side from stored price ids. The client sends
identifiers and quantities and never a price, so a tampered cart cannot change
what anything costs.
compareAtPriceCentsnever enters the calculation. - The webhook is the only thing that marks an order paid. The success redirect proves nothing: a buyer can close the tab, and the URL can be visited directly.
- Webhook delivery is at-least-once, so events are deduplicated by id, and a failed handler releases the dedup record so Stripe’s retry is processed rather than dismissed.
Checked when the session is built and again, authoritatively, when payment is
confirmed. If it ran out in between, the money has been taken, so the order is
recorded and flagged oversold for the merchant rather than dropped. The quantity
control in the storefront clamps against stock on every change, including a
re-clamp when the variant changes, so the shopper finds out before payment rather
than at it.
Shipping and address collection
Section titled “Shipping and address collection”The cart asks for the destination country because hosted Checkout collects the address after the session exists, and a zone-priced store has to know the destination before then. The session is then restricted to that country, so a buyer cannot hold a domestic rate against an international address. A cart with no physical line collects no address at all. Shipping.
Country list
Section titled “Country list”With no zones configured, Stripe is offered a small default list. With zones, the countries the zones name, plus every Stripe-shippable country if there is a catch-all zone.
Guest and signed-in
Section titled “Guest and signed-in”Guest checkout is the default and nothing forces an account. The cart offers signing in and prefills the shipping country from a signed-in buyer’s default address. A guest order under an email that belongs to a verified account is linked to that account by the webhook, never to an unverified one.
Testing
Section titled “Testing”stripe listen, the printed secret in .env, an API restart, and card
4242 4242 4242 4242. Replaying a delivered event with stripe events resend
must not move stock a second time.