---
title: Checkout
description: Embedded (slide-out iframe) vs hosted-redirect checkout, return detection, and the shopping beacon.
---

Bolder's checkout can be integrated in two modes. Choose based on the UX you want and the payment methods your shop uses.

| Mode | UX | Gateway redirects | Setup |
|------|-----|------------------|-------|
| **Embedded** | Customer stays on your page; checkout opens in a slide-out iframe | Handled transparently — SDK navigates the top-level window when a gateway (PayPal, Webpay, Khipu) redirects | Import `bolder-checkout` or include `checkout.umd.cjs` |
| **Hosted redirect** | Customer navigates to Bolder's checkout page, then returns to your page | Native — no iframes involved | Pass `return_url` in the checkout link |

Both modes share the same return-detection API so your page always knows when a customer comes back after payment.

## Embedded mode

The checkout opens in a closeable slide-out panel on top of your page. When an inline payment succeeds the SDK fires a `success` event directly. When a gateway redirect is required (PayPal, Webpay, Khipu), the SDK breaks out of the iframe and navigates the top-level window — on return, `handleReturn()` detects the result.

**Install:**

```bash
npm install bolder-checkout
# or use the script tag: https://assets.bolder.run/checkout.umd.cjs
```

**Usage with `bolder-storefront` cart:**

```js
import { createStorefront, openCheckout } from 'bolder-storefront'
// or: import { openCheckout } from 'bolder-checkout'

createStorefront({ shop: 'acme' }).then(function(storefront) {
  // Cart's checkout() returns a URL — open it in the embedded slider.
  document.querySelector('#checkout-btn').addEventListener('click', function() {
    storefront.cart.checkout(function(err, url) {
      if (err) return
      openCheckout({ checkoutUrl: url })
    })
  })
})
```

The `ShoppingBeacon` component wires this automatically — you don't need to
handle it manually if you're using the beacon.

**Usage with your own order API (no storefront cart):**

```js
import { openCheckout } from 'bolder-checkout'

// Your backend creates the order and returns a checkout_url.
const { checkout_url } = await fetch('/api/orders', { method: 'POST' }).then(r => r.json())

const session = openCheckout({ checkoutUrl: checkout_url })
session.on('success', () => showConfirmation())
session.on('closed',  () => console.log('checkout closed'))
```

## Hosted redirect mode

The customer navigates to Bolder's checkout UI (same as for a regular hosted shop). When they complete (or cancel) their order, Bolder redirects them back to the URL you specified as `return_url`.

This is the simpler mode — no iframes, no postMessage, no SDK required on the checkout page itself.

**Navigating to the checkout:**

Use `redirectToCheckout()` — it accepts the same URL options as `openCheckout()` and appends `return_url` automatically:

```js
import { redirectToCheckout } from 'bolder-checkout'
// or: import { redirectToCheckout } from 'bolder-storefront'

redirectToCheckout({ checkoutUrl: cartData.checkout_url })

// Optional: override the return page (defaults to the current page)
redirectToCheckout({ checkoutUrl: cartData.checkout_url, returnUrl: 'https://mysite.com/thanks' })
```

Script-tag: `Bolder.checkout.redirect({ checkoutUrl: cartData.checkout_url })`

Bolder stores `return_url` on the checkout session when the checkout page first loads and uses it to redirect back after any gateway round-trip. The `return_url` must be a URL whose hostname matches the shop's configured domain or an allowed embed origin — Bolder rejects unknown hosts to prevent open redirects.

**What the customer receives on return:**

Bolder appends two parameters before redirecting back:

```
https://your-site.com/products/hat?checkout_session=ABC123&checkout_status=success
```

| Parameter | Values |
|-----------|--------|
| `checkout_session` | The order permalink — identifies which order was placed |
| `checkout_status` | `success` or `cancelled` |

## Return detection

Whether you use embedded or hosted redirect mode, load `bolder-checkout` on the
page the customer returns to. It runs `handleReturn()` automatically at module
load — before any framework router can consume the URL — cleans the params, and
fires a `bolder:checkout:return` event.

**Listening for the event (registers before module load completes):**

```js
import { handleReturn } from 'bolder-checkout'
// handleReturn() already ran by the time the next line executes.
// Use document.addEventListener *before* the import for early listeners,
// or use getLastReturn() for components that mount later.

document.addEventListener('bolder:checkout:return', function(e) {
  const { sessionCode, status } = e.detail  // status: 'success' | 'cancelled'
  if (status === 'success') showConfirmationBanner(sessionCode)
})
```

**Checking in a late-mounting component (misses the event):**

```js
import { getLastReturn } from 'bolder-checkout'
// or: import { getLastCheckoutReturn } from 'bolder-storefront'

// Call this inside your component's mount/init callback:
const ret = getLastReturn()
if (ret?.status === 'success') showConfirmationBanner(ret.sessionCode)
if (ret?.status === 'cancelled') showRetryPrompt()
```

**`handleReturn()` return value** — `{ sessionCode, status }` if checkout params
were present, `undefined` otherwise. Also available via `Bolder.checkout.handleReturn()` in script-tag usage.

**URL cleanup** — `handleReturn()` removes `checkout_session` and `checkout_status`
from the URL via `history.replaceState` so a page refresh doesn't re-trigger the
handler.

## Shopping beacon integration

The `ShoppingBeacon` component integrates both modes automatically:

- **Embedded mode:** the "Proceed to checkout" button in the cart slide-out calls
  `Bolder.checkout.open({ checkoutUrl })`. The beacon passes `return_url` (the current
  page URL) and `origin` to the checkout iframe automatically.

- **Return detection:** on every page load, if `getLastReturn()` reports
  `status === 'success'`, the beacon displays a success toast notification. In
  hosted themes the toast uses the styled `Toasts` module; in headless mode a
  self-contained fallback renders instead.

No additional wiring is needed — mount the beacon and both flows work out of the box.

```js
import { createStorefront, ShoppingBeacon } from 'bolder-storefront'

createStorefront({ shop: 'acme' }).then(function(storefront) {
  storefront.mount(ShoppingBeacon, '#beacon-container')
})
```

To handle the return yourself and suppress the beacon's built-in toast, listen to
the event before the beacon mounts and call `e.stopImmediatePropagation()`, or
simply don't use the beacon and wire your own UI around `getLastReturn()`.

## Agentic checkout (UCP)

Every storefront also exposes an agent-facing checkout API — the [Universal Commerce
Protocol](/frontend/sdk/ucp) — discoverable at `/.well-known/ucp`, for shopping agents
that complete a purchase without a human driving the browser.
