Skip to main content
The embedded checkout is the same hosted checkout, rendered inside your page in an iframe instead of sending the shopper away. It needs kit 1.1.0 or newer.
The document in the frame is served from the checkout’s own origin, so the payment providers, 3-D Secure and wallet domain registrations work exactly as on the redirect path. mount() is start() plus a frame: one POST /checkout/handoff, with an embed block added.
The full-page redirect is still the default and the fallback. Use the embedded checkout on a route of its own; keep <qb-checkout-button> or redirectToCheckout() on the cart page. Apple Pay and Google Pay are not available inline yet; they run in the full-page checkout.

Who may frame a checkout

The session decides. mount() records your page’s origin on the session it creates, and only a session that carries one is served a framable document. A hosted checkout URL you build yourself records nothing, so a hand-built iframe around a hosted checkout URL is refused, and always will be. The recorded origin is a permission to frame, not a restriction on who frames: the document’s frame-ancestors names https, so the checkout still renders when your page is itself inside something else (a CMS preview, a site builder’s editor). Your origin decides which page the checkout exchanges messages with. mount() records the origin every time, so a cart that start() opened earlier still embeds when mount() picks it up.
Attaching an origin to a session that never had one can take up to a minute to be honoured everywhere. A shopper who mounts in that window sees the frame refused, waits out the 15-second handshake and is redirected to the hosted checkout.

Redirect payment methods

Klarna, Swish, Vipps MobilePay, iDEAL, Trustly and full-page 3-D Secure take the whole window to the provider. A frame cannot do that on its own, so the kit does:
The bounce carries the session to resume and the shop’s numeric id. The id is on the URL because the platform drops the cart the moment the order exists, normally before the shopper is back, so the browser cannot be relied on to have it. A second copy is also kept beside the remembered session (<prefix>_checkout_store). Your page owes this flow two things:
  1. Render the checkout on load, and tolerate both parameters. <qb-checkout> and <Checkout> pick them up, resume that session instead of creating a new one, and strip both from the address bar once the resumed frame has answered. A resume that fails before then leaves them, so a reload resumes again.
  2. Do not redirect that URL away. A router that drops unknown query parameters, or a marketing redirect, breaks the return leg. The order is still created, but the shopper sees nothing.

Routing the return leg yourself

readReturnedCheckout(url?) accepts a URL (for example request.url), so a server component can decide before rendering whether this is a return leg. storeId is null when the URL carried nothing usable; resume() then falls back to the id remembered with the session, then the cart’s.
checkout.parseReturnUrl() is a different thing: it parses the post-payment landing at /success/<orderNumber>, not this bounce.

It follows the cart

<qb-checkout> and <Checkout> watch the shared cart store. You write no code for this:
  • Nothing is mounted for an empty cart. While the cart loads or is empty, no session is created. <qb-checkout> emits qb:checkout-empty and <Checkout> calls onEmpty, once per empty phase. The frame goes in by itself when an item exists.
  • Emptying the cart takes the frame down, because the session names a cart that no longer exists. The next item starts a fresh checkout. A frame resuming a return leg, or showing a completed order, is left alone.
  • Changing the cart updates the checkout in place. Add a line or change a quantity through the kit’s cart store and the checkout re-reads the cart: lines, totals, shipping and the amount to pay follow, without losing what the shopper typed. If you change the cart some other way, or drive mount() by hand, call checkout.cartUpdated() after the change is saved.

When it cannot embed

Four expected situations, none of them a thrown error. Each resolves to a handle with state: "fallback", emits fallback / qb:checkout-fallback with the hosted checkout url, and (unless fallbackRedirect: false) sends the shopper to the full-page checkout: On resume() the fallback URL is built for that session, so the shopper lands on its confirmation, never on a new checkout.

The handle

One checkout per container: mounting again into the same element destroys the earlier handle first. Last mount wins.

Events

On the element these are qb:checkout-ready, qb:checkout-step, qb:checkout-event, qb:checkout-complete, qb:checkout-error, qb:checkout-fallback and qb:checkout-empty, all bubbling; element.checkout is the handle. In React they are onReady, onStep, onEvent, onComplete, onError, onFallback and onEmpty. Every navigation is performed for you: navigate and a GET redirect with location.assign, a POST redirect with a hidden target="_top" form, and complete with a jump to the confirmed order. That landing is <successUrl origin>/success/<orderNumber>?hash=…&t=…, the same as the redirect checkout, so one thank-you route serves both.

Options

Everything start() takes (successUrl, backUrl, cancelUrl, language, theme, prefill, cartId) is taken too. Set theme when the page is dark: the frame inherits nothing from your CSS. The element and component read their attributes and props once, when the session is created. Changing them later is ignored rather than discarding a checkout the shopper may be paying in.

Not in the embed yet

  • Wallets. Apple Pay and Google Pay are off inside the frame; cards and redirect methods work.
  • Merchant pixels inside the frame. GA4, GTM and Meta are not injected into the frame: in a third-party context they would pollute attribution. Every commerce event is forwarded to your page as an event instead (qb:checkout-event on <qb-checkout>, onEvent on <Checkout>), and with the kit’s analytics on (the default) it is tracked there automatically, the purchase included, gated on the shopper’s consent. See Consent and analytics.
  • A hand-built iframe. Use mount(), <qb-checkout> or <Checkout>.

Content Security Policy

The embedded checkout needs frame-src https://pay.quickbutik.com in addition to the kit’s usual connect-src https://commerce.quickbutik.com and img-src https://cdn.quickbutik.com.