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

# Checkout

> Convert a cart into a paid order - via hosted checkout or headless purchase

## Two paths to checkout

Henry supports two checkout modes. Pick the one that fits your needs:

<CardGroup cols={2}>
  <Card title="Hosted Checkout" icon="window" href="#hosted-checkout">
    The `checkoutUrl` returned by `cart.create` is a fully hosted checkout page. Redirect, iframe, or popup. Henry collects shipping, payment, and tax.
  </Card>

  <Card title="Headless Checkout" icon="terminal" href="#headless-checkout">
    Call `checkout.purchase` from your server with card token and shipping info. Henry processes payment and places the order with the merchant. You own the UI completely.
  </Card>
</CardGroup>

***

# Hosted Checkout

### Overview

Henry's Hosted Checkout is the fastest way to add commerce capabilities to your application. With our hosted checkout solution, you can accept payments without handling sensitive payment data or building complex UI components. Simply embed our checkout experience via iframe, webview, or redirect.

### Why Choose Hosted Checkout?

<CardGroup cols={3}>
  <Card title="Launch in Minutes" icon="rocket">
    Pre-built UI components and hosted pages get you live fast
  </Card>

  <Card title="Zero PCI Burden" icon="shield-check">
    We handle all payment security and compliance requirements
  </Card>

  <Card title="Mobile Ready" icon="mobile">
    Responsive design works seamlessly across all devices
  </Card>
</CardGroup>

<Tip>**Perfect for**: Startups and businesses that want to move fast and launch quickly.</Tip>

### How It Works

Hosted Checkout uses a simple redirect or embedded iframe approach:

```mermaid theme={null}
sequenceDiagram
    participant User
    participant YourApp
    participant Henry
    participant Merchant

    User->>YourApp: Select products
    YourApp->>Henry: Create checkout session
    Henry-->>YourApp: Return checkout URL
    YourApp->>User: Redirect/Show iframe
    User->>Henry: Complete payment
    Henry->>Merchant: Place order
    Henry->>YourApp: Order confirmation
    YourApp->>User: Show confirmation
```

### Embed options

<Tabs>
  <Tab title="Full page redirect">
    The simplest integration - just redirect the user to the checkout URL.

    ```typescript theme={null}
    // Server-side (e.g. Next.js API route)
    const cart = await henry.cart.create({ items });
    res.redirect(cart.data.checkoutUrl);

    // Client-side
    window.location.href = checkoutUrl;
    ```
  </Tab>

  <Tab title="Iframe embed">
    Keep users on your site with an embedded checkout experience.

    ```html theme={null}
    <div id="checkout-container">
      <iframe
        id="henry-checkout"
        src="..."
        width="100%"
        height="700"
        frameborder="0"
        allow="payment"
        style="border-radius: 12px;"
      ></iframe>
    </div>
    ```

    ```typescript theme={null}
    const cart = await henry.cart.create({ items });
    document.getElementById('henry-checkout').src = cart.data.checkoutUrl;

    // Listen for completion
    window.addEventListener('message', event => {
      if (!event.origin.endsWith('.henrylabs.ai')) return;

      const { action, orderId } = event.data ?? {};

      switch (action) {
        case 'orderCompleted':
          console.log('Order placed:', orderId);
          document.getElementById('henry-checkout').style.display = 'none';
          showOrderConfirmation(orderId);
          break;

        case 'checkoutClosed':
          document.getElementById('checkout-container').style.display = 'none';
          break;
      }
    });
    ```
  </Tab>

  <Tab title="Modal popup">
    Balance between full-page and inline with a popup window.

    ```typescript theme={null}
    const cart = await henry.cart.create({ items });

    const modal = window.open(
      cart.data.checkoutUrl,
      'henry-checkout',
      'width=520,height=750,left=200,top=100',
    );

    window.addEventListener('message', event => {
      if (!event.origin.endsWith('.henrylabs.ai')) return;

      const { action, orderId } = event.data ?? {};
      if (action === 'orderCompleted') {
        modal?.close();
        handleSuccess(orderId);
      } else if (action === 'checkoutClosed') {
        modal?.close();
      }
    });
    ```
  </Tab>

  <Tab title="React Native">
    Use `react-native-webview` to embed Henry checkout in a mobile app.

    ```tsx theme={null}
    import { WebView } from 'react-native-webview';

    function CheckoutScreen({ checkoutUrl }: { checkoutUrl: string }) {
      return (
        <WebView
          source={{ uri: checkoutUrl }}
          onMessage={event => {
            const data = JSON.parse(event.nativeEvent.data);
            if (data.action === 'orderCompleted') {
              navigation.navigate('OrderConfirmation', { orderId: data.orderId });
            }
          }}
        />
      );
    }
    ```
  </Tab>
</Tabs>

<Info>Want to match your color scheme? Append `?theme=<light or dark>` to the checkout URL.</Info>

***

# Headless Checkout

### Overview

Headless Checkout gives you full control over the buyer experience. You collect shipping and payment info in your own UI, then call Henry's API server-side to process payment and place the order.

To securely collect card details in your UI, use the [Card Element](/v1/sdk/client/elements/card-element). It tokenizes the card and returns a `cardToken` to pass to `cart.checkout.purchase`.

<Warning>
  **Keep card tokenization on the client to stay out of PCI scope.** The Card
  Element collects and tokenizes card data inside an isolated iframe, so raw
  card numbers never touch your servers. A server-side `POST /card/tokenize`
  REST endpoint also exists (it is not exposed through the SDK), but sending
  the raw PAN, CVV, and expiry through your own backend brings your systems
  into PCI DSS scope (typically SAQ D). Only reach for it if you already
  operate a PCI-compliant environment.
</Warning>

### Why Choose Headless Checkout?

<CardGroup cols={3}>
  <Card title="Full UI Control" icon="paintbrush">
    Own every pixel of the checkout experience - no iframes, no redirects
  </Card>

  <Card title="Your Brand" icon="badge-check">
    Seamlessly match your existing design system and user flows
  </Card>

  <Card title="Server-Side" icon="server">
    All payment processing happens on your server - no client-side exposure
  </Card>
</CardGroup>

<Callout icon="key" color="#FFC107" iconType="regular">
  Headless checkout requires special enablement. [Contact us](mailto:support@henrylabs.ai) to get set up.
</Callout>

### How It Works

```mermaid theme={null}
sequenceDiagram
    participant App as Your App (server)
    participant Henry as Henry API

    App->>Henry: cart.create({ items })
    Henry-->>App: { cartId, checkoutUrl }

    Note over App: Collect buyer info in your own UI

    App->>Henry: cart.checkout.purchase(cartId, { buyer })
    Henry-->>App: { refId, status: "pending" }

    loop Poll ~2s
        App->>Henry: cart.checkout.pollPurchase({ refId })
        Henry-->>App: { status: "processing" }
    end

    Henry-->>App: { status: "complete", result: { costs } }
```

<Tabs>
  <Tab title="Initiate purchase">
    Call `cart.checkout.purchase` from your server with the buyer's shipping and card info.

    ```typescript theme={null}
    const purchase = await henry.cart.checkout.purchase('crt_sa2aEsCz9PRM', {
      buyer: {
        name: { firstName: 'Jane', lastName: 'Doe' },
        email: 'jane@example.com',
        phone: '+19175551234',
        shippingAddress: {
          line1: '350 5th Ave',
          line2: 'Floor 21',
          city: 'New York',
          province: 'NY',
          postalCode: '10118',
          countryCode: 'US',
        },
        card: {
          nameOnCard: { firstName: 'Jane', lastName: 'Doe' },
          details: {
            cardToken: '...',
          },
          // billingAddress optional - defaults to shippingAddress if omitted
        },
      },
      mode: 'async', // or 'sync' to wait up to 30s for completion
    });

    console.log(purchase.refId, purchase.status);
    // → "ckp-ref_38lb3..." "pending"
    ```
  </Tab>

  <Tab title="Track completion">
    Poll every \~2 seconds until the order reaches a terminal state.

    ```typescript theme={null}
    let order = purchase;

    while (order.status === 'pending' || order.status === 'processing') {
      await new Promise((r) => setTimeout(r, 2000));
      order = await henry.cart.checkout.pollPurchase({ refId: purchase.refId });
    }

    if (order.status === 'complete') {
      const { subtotal, commissionFee, total } = order.result!.costs;
      console.log(`Order total: ${total.value} ${total.currency}`);
      console.log('Items:', order.products);
    } else {
      console.error('Order failed or cancelled:', order.error);
    }
    ```

    Alternatively, skip polling entirely by attaching a webhook when you create the cart - Henry will fire it when the order completes.

    ```typescript theme={null}
    const cart = await henry.cart.create({
      items: [...],
      settings: {
        events: [
          {
            type: 'order.purchase.complete',
            data: [
              {
                type: 'send_webhook',
                webhookUUID: 'your-webhook-uuid',
              },
            ],
          },
        ],
      },
    });
    ```

    <Info>Webhooks are registered in the [Henry Dashboard](https://app.henrylabs.ai). See [Universal Cart - Cart events](/v1/sdk/server/guides/universal-cart#cart-events) for the full event and action type reference.</Info>
  </Tab>

  <Tab title="Override quantities">
    Pass `overrideProducts` to adjust quantities at purchase time without modifying the cart.

    ```typescript theme={null}
    await henry.cart.checkout.purchase(cartId, {
      buyer: {
        /* ... */
      },
      overrideProducts: {
        'https://www.nike.com/t/air-max-270-shoes/AH8050-002': 2, // bump to 2
        'https://www.adidas.com/us/ultraboost-22/GX5591.html': null, // exclude
      },
    });
    ```
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="Order Management" icon="receipt" href="/v1/sdk/server/guides/order-management">
    List and track orders after checkout - filter by status, cartId, and more
  </Card>

  <Card title="Universal Cart" icon="cart-shopping" href="/v1/sdk/server/guides/universal-cart">
    Learn how to build and manage the cart before checkout
  </Card>
</CardGroup>
