useCart
Behaviour worth knowing:
- One store for the whole tree. A header badge and the cart page never disagree.
- The cart is loaded on first mount and never created speculatively. Mounting a badge writes no cookie and creates no cart for a visitor who only browses.
- Mutations resolve to
nullon failure and put the error inerror. updateItem(itemId, 0)removes the line. Quantity 0 has no meaning to the API.itemIdis the cart line id (item.id, a uuid), never the product id.clear()(aliasclearCart()) deletes the remembered cart server-side and forgets it. With no cart it does nothing.- It follows the currency. A cart stores no currency of its own; switching currency (
useCurrency().setCurrency()) re-reads the same cart priced in the new one. Always format withcart.currency. See Currencies. - Mutations are tracked. With analytics on (the default), every successful add or remove is reported as
add_to_cart/remove_from_cartonce the shopper has consented.
Header badge
Cart page
useCheckout
Checkout button
successUrl: two rules. It must be https (plain http is accepted only on localhost and loopback), and only its origin is used: the shopper returns to <origin>/success/<orderNumber>. Mount your thank-you route exactly there. See Checkout flow.- A second click while a start is in flight is ignored.
- After
redirectToCheckout()orbuyNow()navigates,startingstays true until the page is restored from the back/forward cache, so a click while the checkout loads does nothing (forbuyNowit would add the item twice). A navigation that never unloads the page (a “Stay” on a leave-page prompt, a 204 or download response) releases it after 10 seconds. - Navigation uses
location.assign, notreplace, so the back button returns to the cart. theme: "light" | "dark"pins the hosted checkout’s theme. Leaving it out lets the merchant’s own setting win; it is not the same as"light".- The checkout opens in the shopper’s currency (the client’s), with nothing to pass. See Currencies.
- The shopper’s consent decision is appended to the hosted checkout URL, so the checkout’s own tags respect it. See Consent and analytics.
Buy now
buyNow goes through the shared cart store, so a badge is right if the shopper comes back with the back button.
Inside a <ProductProvider>, prefer useProductAddToCart().buyNow. It fills in the selected variant and refuses an incomplete selection:
buyNow(input, quantity = 1) is a no-op while the selection is incomplete. canAddToCart is false during a buy-now too, since both buttons add.
Starting the checkout on the server
In a server framework, starting the checkout in a server action or route handler is often better: cookies are writable there and the redirect is a real 303. See Next.js → Checkout as a server action.<Checkout>: the checkout inline
Render the hosted checkout inside your own page instead of sending the shopper away:
<div> (takes className / style) with an iframe inside, served from the checkout’s own origin.
- The session is created once, on mount. Changing props afterwards does not rebuild it; that would discard a session the shopper may be paying in.
- It follows the cart. Nothing is mounted for an empty cart. Emptying the cart from the page takes the frame down; changing the cart through
useCart()updates the checkout in place. - Redirect payment methods come back on the URL. Swish, Klarna, Vipps MobilePay, iDEAL and full-page 3-D Secure take the whole window and return with
?qb_checkout_session=and?qb_checkout_shop=. The component resumes that session. Your route must render it on load and keep those parameters. - It falls back to the full-page checkout on a legacy shop, when the shop is not enabled for embedding, inside a sandboxed preview, or when the frame never answers.
onFallbackreceives the reason. - Apple Pay and Google Pay are not available inline yet; they run in the full-page checkout.
- Errors are reported through
onErrorand logged, never thrown during render.
Thank-you page: useOrderConfirmation
After payment, the platform creates the order asynchronously (typically 6–17 s). The thank-you page at /success/[orderNumber] polls until it exists.
- With no argument it uses the checkout session the kit remembered when the checkout started. Pass a session id to override.
- The
purchaseevent is fired for you. With analytics on (the default), a completed order is reported once, built from the checkout session’s own lines and total and deduplicated across reloads. Don’t fire it yourself as well. - The order number in the URL is never proof. The hook confirms it against the API. Use the URL number only as a display fallback when
orderNumberisnull. - On
completedthe kit forgets the cart and session ids, so the next visit starts with an empty basket.
<SEO title="…" noIndex noFollow />: it is one shopper’s private page. The complete server-plus-client pattern is in Next.js → Thank-you page.