> ## Documentation Index
> Fetch the complete documentation index at: https://quickbutik.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing and going live

> Test the full purchase loop without paying, build inside AI app builders, and ship to production.

## Test the whole loop

The loop to prove: **add to cart → cart → checkout → hosted checkout → back to `/success/<orderNumber>` → confirmation**.

### 1. Pre-check the handoff

From a throwaway cart, check that the shop, the key's write scopes and your URLs are fine, without any UI:

```ts theme={null}
const cart = await shopkit.cart.add({ productId: "prod_27" })
const result = await shopkit.checkout.start({
  cartId: cart.id,
  successUrl: "https://minbutik.se/success", // the origin you intend to ship
})
console.log(result.checkout, result.url)     // "v2" | "legacy", and a URL
```

A `url` back means the setup works, and `checkout` tells you which checkout the shop runs. Nothing is ordered until someone pays, so the session can be left alone.

### 2. Demo mode or live

A shop that has not activated Quickbutik Payments runs the checkout in **demo mode**: demo payment methods, no money moves, **no order is created**. It proves the handoff and the checkout UI, but not the return leg. `shop.get()` reports `demo.enabled: true` on the wire. Activating payments in the admin (Settings → Payments) changes nothing in your code.

### 3. The zero-total path

Cards cannot be driven through an API. On a shop with payments activated, use the one case where the platform places an order without a payment: a total of exactly 0.

<Steps>
  <Step title="Set up in the admin">
    Create a 100 % discount code (Discounts) and a shipping method priced 0, or a free-shipping rule (Shipping).
  </Step>

  <Step title="Run the flow">
    Add a cheap product, go to checkout, enter the code, pick free shipping, place the order.
  </Step>

  <Step title="Check the return">
    You should land on `/success/<orderNumber>`, and `pollConfirmation()` / `useOrderConfirmation()` / `<qb-order-confirmation>` should report `completed`.
  </Step>

  <Step title="Clean up">
    Deactivate the code and the free shipping, and cancel the test order under Orders.
  </Step>
</Steps>

<Warning>
  Never test with a real card payment. Use the zero-total path, or abandon the checkout.
</Warning>

## Building inside an AI app builder preview

Google AI Studio, Lovable, v0, Replit, StackBlitz and similar tools render your app in a sandboxed iframe on another origin. Two things change there, and both go away once the app is deployed.

**Cookies are blocked or partitioned.** Nothing is remembered, so every `cart.add()` creates a new cart and `start()` without `cartId` creates an empty one, which the handoff refuses with `400 "Cannot hand off an empty cart to the checkout"`. Keep the cart in memory and pass its id:

<Tabs>
  <Tab title="Vanilla">
    ```ts theme={null}
    await shopkit.checkout.start({ cartId: cart.getSnapshot().cart?.id, successUrl, backUrl })
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={null}
    const { cart } = useCart()
    const { redirectToCheckout } = useCheckout()
    redirectToCheckout({ cartId: cart?.id, successUrl, backUrl })
    ```
  </Tab>

  <Tab title="Web components">
    ```html theme={null}
    <!-- In the preview, a plain button instead of <qb-checkout-button> -->
    <button id="to-checkout" type="button">Till kassan</button>
    <script type="module">
      document.querySelector("#to-checkout").addEventListener("click", async () => {
        const { url } = await Quickbutik.client.checkout.start({
          cartId: Quickbutik.cart.getSnapshot().cart?.id,
          successUrl: `${location.origin}/success`,
          backUrl: location.origin,
        })
        location.assign(url)
      })
    </script>
    ```
  </Tab>
</Tabs>

`storage: "localStorage"` keeps the cart across reloads in a preview where local storage works. Switch back to the default before deploying with a server, which reads the cart from cookies.

**Navigating to the checkout.** Plain `location.assign(url)` works: it navigates the preview frame itself. Never use `window.top.location`; the sandbox blocks it. Only if the builder blocks cross-origin navigation of its frame, open a tab synchronously in the click handler:

```ts theme={null}
button.onclick = async () => {
  const tab = window.open("", "_blank") // synchronously, inside the click
  const { url } = await shopkit.checkout.start({ cartId, successUrl, backUrl })
  if (tab) tab.location.href = url
  else location.assign(url)
}
```

**The return leg cannot be verified in a preview**, and the embedded checkout falls back to the redirect there (`no-top-navigation`). Verify both on the deployed origin.

## Production checklist

<AccordionGroup>
  <Accordion title="Keys and environment">
    * The publishable key is the one from **Custom storefront** in the admin (or one minted with the five storefront scopes).
    * No `qb_pat_` token anywhere in frontend code, a client bundle or a public repo.
    * `apiUrl` and `checkoutUrl` left unset (production defaults).
  </Accordion>

  <Accordion title="Checkout and return">
    * The storefront is served over https; `successUrl` and `backUrl` are on that origin.
    * The thank-you route answers a top-level GET at `/success/<orderNumber>`. SPA and static hosts have a rewrite (Netlify / Cloudflare Pages `_redirects`: `/success/* /success.html 200`; Vercel `rewrites`; nginx `try_files`).
    * `timeout` is rendered as "still processing", never as a payment failure.
    * A page that embeds the checkout renders it on load and keeps `qb_checkout_session` and `qb_checkout_shop` on its URL.
    * The shop has activated Quickbutik Payments, or every checkout is a demo.
  </Accordion>

  <Accordion title="Cookies and storage">
    * Cookies are not `httpOnly` when browser code reads the cart.
    * The same `storageKeyPrefix` and cookie attributes on server and browser.
    * One client per request on the server, one per app in the browser; `configure()` called once.
  </Accordion>

  <Accordion title="Catalog and money">
    * Images render `image.url` (or the kit's image components), never `image.path`.
    * Money is rendered from minor units and formatted with the currency the response states (`product.currency`, `cart.currency`), never a constant.
    * On a shop with several currencies: cached catalog data is keyed on the currency, and a `"display"` currency says the checkout charges the shop's currency. See [Currencies](/kit/concepts/currencies).
    * `stock: null` is treated as purchasable.
    * Every product the shop should sell has `visible: true`.
  </Accordion>

  <Accordion title="SEO and compliance">
    * One `<title>` owner per page; `noIndex` on cart, success, search and filtered pages.
    * `baseUrl` / `NEXT_PUBLIC_SITE_URL` fixed by config, not derived from `Host`.
    * JSON-LD has a currency, and is read in the shop's own (charged) currency.
    * The cookie banner is on (the default) with your language and a `privacyPolicyUrl`, a privacy policy page exists, and the footer has a cookie-settings button (`<ConsentSettingsButton>` / `<qb-consent-settings>`). On Next.js the layout passes `shopkit.consent.read()` as `initialState`. See [Consent and analytics](/kit/concepts/consent-and-analytics).
    * The shop's GA4, GTM or Meta ids are set in the Quickbutik admin if the merchant wants analytics; with none, the banner shows and nothing is sent.
    * CSP allows the script host, `connect-src https://commerce.quickbutik.com`, `img-src https://cdn.quickbutik.com`, and `frame-src https://pay.quickbutik.com` for the embedded checkout. With analytics on, also `script-src https://www.googletagmanager.com https://connect.facebook.net`, `connect-src https://www.google-analytics.com https://*.analytics.google.com https://www.facebook.com` and `img-src https://www.facebook.com https://www.google-analytics.com`.
    * Script-tag pages pin the CDN URL to an exact version.
  </Accordion>
</AccordionGroup>

## Building with AI

Quickbutik publishes instructions written for coding agents at **[https://quickbutik.com/agents.md](https://quickbutik.com/agents.md)**, with the kit guide as an installable agent skill under `https://quickbutik.com/agents/kit/`. It covers creating a shop when there is no key, building every page in order, a design pass and a self-check.

Add two lines to your project's `AGENTS.md` or `CLAUDE.md` so every session starts from the current instructions:

```md theme={null}
Before any work on the shop, products, cart or checkout: fetch https://quickbutik.com/agents.md and follow it.
Do not rely on memory.
```

<Card title="Build with AI" icon="sparkles" href="/build-with-ai">
  More on using AI tools with Quickbutik.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.