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

# Checkout Flow

> Manage checkout sessions, cart, promo codes, and shipping

# Checkout Flow

The checkout module manages the full session lifecycle -- load a session, update the cart, apply promo codes, and handle shipping.

***

## Load a Checkout Session

A checkout session is typically created by the Node SDK on your server, then the token is passed to the frontend via URL parameter.

```typescript theme={null}
// Token from URL: /checkout?checkoutToken=ct_abc123&token=eyJ...
const checkoutToken = new URLSearchParams(window.location.search).get('checkoutToken');
const sessionToken = new URLSearchParams(window.location.search).get('token');

const session = await tagada.checkout.loadSession(checkoutToken, sessionToken);

// session.id          -- session ID (use for all subsequent calls)
// session.items       -- line items in the cart
// session.totals      -- subtotal, discount, shipping, tax, total
// session.customer    -- customer info if available
// session.currency    -- session currency (USD, EUR, etc.)
```

***

## Create a Checkout Session (Client-Side)

From a landing / cart page, create a session and send the shopper to **your** checkout. Pin the variant you sell — do not pick "whatever is in the catalog":

```typescript theme={null}
const { url } = await tagada.checkout.createSessionUrl({
  items: [{ variantId: 'var_xxx', quantity: 1 }],
  checkoutPath: '/checkout',
  // Page that hosts usePayment / maybeResumeFromUrl — 3DS returns here.
  returnUrl: 'https://mystore.com/checkout',
});
window.location.href = url; // your origin, not checkoutUrl
// → https://yoursite.com/checkout?checkoutToken=...&sessionToken=...
```

On `/checkout`, read both query names (`sessionToken` from `createSessionUrl`, `token` from a Tagada-hosted redirect):

```typescript theme={null}
const tokens = tagada.checkout.parseTokensFromUrl();
if (!tokens) {
  window.location.href = '/';
}
const session = await tagada.checkout.loadSession(tokens.checkoutToken, tokens.sessionToken);
```

`createSession()` is the same call without building the URL. It also returns `checkoutUrl` — that one is the **Tagada-hosted** checkout, not your page.

```typescript theme={null}
const created = await tagada.checkout.createSession({
  items: [{ variantId: 'var_xxx', quantity: 1 }],
  currency: 'USD',
  returnUrl: 'https://mystore.com/checkout',
});

// created.checkoutToken / created.sessionToken — load this session on YOUR page
// created.checkoutUrl — Tagada-hosted URL. Do NOT redirect here when self-hosting.
```

<Warning>
  Headless **cannot set** `checkoutUrl`. It is generated by Tagada (plugin domain, funnel, or `store-unavailable`). On a self-hosted checkout, stay on your page and call `loadSession()`. To hop from a cart page to your own checkout path, use `createSessionUrl()` as above.
</Warning>

***

## Discount codes + affiliate attributes at hand-off

When the cart lives on **your own site** (landing page, custom storefront, or server-rendered cart) and you send shoppers to **Tagada hosted checkout**, create the session on your side first, then redirect. Pass promo codes and attribution data **at session creation** so they land on the order as attributes and in the `order/paid` webhook — you do not need to call the raw REST endpoint yourself.

```typescript theme={null}
const { checkoutUrl } = await tagada.checkout.createSession({
  items: [{ variantId: 'var_xxx', quantity: 1 }],
  currency: 'USD',
  returnUrl: 'https://mystore.com/thank-you',
  discountCodes: ['SAVE10'],
  metadata: {
    cartCustomAttributes: [
      { name: 'affiliate_id', value: 'partner_42' },
      { name: 'campaign', value: 'spring_sale' },
    ],
  },
  customerMetadata: {
    queryParams: '?ref=partner&utm_source=fb&utm_campaign=spring',
    cookies: { _fbp: 'fb.1.1234567890' },
  },
  customerTags: ['affiliate'],
});

// Redirect the shopper to Tagada hosted checkout
window.location.href = checkoutUrl;
```

| Field                           | Purpose                                                                |
| ------------------------------- | ---------------------------------------------------------------------- |
| `discountCodes`                 | Pre-apply promotion codes before the customer lands on checkout        |
| `metadata.cartCustomAttributes` | Custom order note attributes (visible in CRM → Order → Attributes)     |
| `customerMetadata.queryParams`  | Persist `ref`, `utm_*`, and other query params on the customer/session |
| `customerMetadata.cookies`      | Snapshot affiliate cookies (e.g. Meta `_fbp`)                          |
| `customerTags`                  | Tags for funnel routing (`customer.hasTag` conditions)                 |

When you **self-host** the checkout page, pass the same fields on `createSessionUrl()` — attribution is applied at init before the shopper lands on your `/checkout`:

```typescript theme={null}
const { url } = await tagada.checkout.createSessionUrl({
  items: [{ variantId: 'var_xxx', quantity: 1 }],
  checkoutPath: '/checkout',
  returnUrl: 'https://mystore.com/checkout',
  discountCodes: ['SAVE10'],
  metadata: {
    cartCustomAttributes: [
      { name: 'affiliate_id', value: 'partner_42' },
      { name: 'campaign', value: 'spring_sale' },
    ],
  },
  customerMetadata: {
    queryParams: '?ref=partner&utm_source=fb&utm_campaign=spring',
    cookies: { _fbp: 'fb.1.1234567890' },
  },
  customerTags: ['affiliate'],
});

window.location.href = url;
// → https://yoursite.com/checkout?checkoutToken=...&sessionToken=...
```

***

## Update Cart

```typescript theme={null}
const updated = await tagada.checkout.updateCart(session.id, [
  { variantId: 'var_xxx', quantity: 2 },
  { variantId: 'var_yyy', quantity: 1 },
]);
// updated.totals reflects the new cart
```

***

## Customer & Address

The SDK uses a two-step pattern: first set customer identity, then set the address. Under the hood, both are sent in a single atomic API call when `updateAddress()` is called.

### Set Customer Info

`updateCustomer()` stores email, name, and phone **in memory**. No API call is made yet — the data is sent together with the address in the next step.

```typescript theme={null}
await tagada.checkout.updateCustomer(session.id, {
  email: 'jane@example.com',
  firstName: 'Jane',
  lastName: 'Doe',
  phone: '+33612345678',
});
```

### Set Address

`updateAddress()` sends customer info + address together in one atomic API call. The customer's `firstName`, `lastName`, and `phone` are automatically included in the address fields.

<Tabs>
  <Tab title="Shipped Product">
    ```typescript theme={null}
    // Billing defaults to shipping automatically
    await tagada.checkout.updateAddress(session.id, {
      shippingAddress: {
        line1: '123 Main St',
        city: 'San Francisco',
        state: 'CA',
        postalCode: '94102',
        country: 'US',
      },
    });
    ```
  </Tab>

  <Tab title="Digital Product">
    ```typescript theme={null}
    // No shipping needed — just billing country for tax calculation
    await tagada.checkout.updateAddress(session.id, {
      billingAddress: {
        country: 'US',
      },
    });
    ```
  </Tab>

  <Tab title="Separate Billing">
    ```typescript theme={null}
    // Different billing and shipping addresses
    await tagada.checkout.updateAddress(session.id, {
      shippingAddress: {
        line1: '123 Main St',
        city: 'San Francisco',
        postalCode: '94102',
        country: 'US',
      },
      billingAddress: {
        line1: '456 Oak Ave',
        city: 'New York',
        postalCode: '10001',
        country: 'US',
      },
    });
    ```
  </Tab>
</Tabs>

### Combined Method

If you prefer a single explicit call:

```typescript theme={null}
await tagada.checkout.updateCustomerAndAddress(session.id, {
  customer: {
    email: 'jane@example.com',
    firstName: 'Jane',
    lastName: 'Doe',
  },
  shippingAddress: {
    line1: '123 Main St',
    city: 'San Francisco',
    postalCode: '94102',
    country: 'US',
  },
});
```

<Note>
  **Address fields:** `firstName`, `lastName`, `phone` can be passed directly in the address object too — they override the values from `updateCustomer()`.
</Note>

***

## Promo Codes

```typescript theme={null}
// Apply
await tagada.checkout.applyPromoCode(session.id, 'SAVE20');

// Remove
await tagada.checkout.removePromoCode(session.id);

// Reload session to see updated totals
const updated = await tagada.checkout.loadSession(checkoutToken, sessionToken);
console.log(updated.totals.discount); // discount amount in cents
```

### What each total means

All amounts are in minor units (cents) in `totals.currency`.

| Field      | Meaning                                                                                                                 |
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
| `subtotal` | Line items before any promotion. An order-level promotion (e.g. "\$5 off") does **not** change it — look at `discount`. |
| `discount` | Total promotion amount applied to the order, as a positive number.                                                      |
| `shipping` | Selected shipping rate (`0` when none is selected or it is free).                                                       |
| `tax`      | Tax, computed on the discounted base.                                                                                   |
| `total`    | What the customer is charged: `subtotal − discount + shipping + tax`.                                                   |

`total` is the same number the hosted checkout shows and the order is charged for — do not recompute it client-side.

***

## Shipping

```typescript theme={null}
// Get available rates (requires address to be set first)
const rates = await tagada.checkout.getShippingRates(session.id);
// [{ id: 'rate_xxx', name: 'Standard', amount: 499, estimatedDays: 5 }]

// Select a rate
await tagada.checkout.selectShippingRate(session.id, rates[0].id);
```

<Tip>
  For **digital products**, skip shipping entirely — go straight from `updateAddress()` to payment.
</Tip>

***

## React Hook

```tsx theme={null}
import { useCheckout } from '@tagadapay/headless-sdk/react';

function CheckoutPage() {
  const {
    session,
    isLoading,
    error,
    refresh,
    createSession,
    updateCart,
    updateCustomer,
    updateAddress,
    applyPromo,
    removePromo,
    getShippingRates,
    selectShippingRate,
  } = useCheckout(checkoutToken, sessionToken);

  // All methods auto-refresh the session after completion
}
```

On a **landing page** there is no token yet — pass `null` and use
`createSessionUrl` on the CTA:

```tsx theme={null}
import { useCheckout, parseTokensFromUrl } from '@tagadapay/headless-sdk/react';

function LandingPage() {
  const { createSessionUrl } = useCheckout(null);

  async function buyNow() {
    const { url } = await createSessionUrl({
      items: [{ variantId: 'variant_xxx', quantity: 1 }],
      checkoutPath: '/checkout',
      discountCodes: ['SAVE10'],
      metadata: {
        cartCustomAttributes: [{ name: 'affiliate_id', value: 'partner_42' }],
      },
      customerMetadata: { queryParams: '?ref=partner&utm_source=fb' },
      customerTags: ['affiliate'],
    });
    window.location.href = url;
  }

  return <button onClick={buyNow}>Buy now — $29.99</button>;
}

function CheckoutPage() {
  // The tokens arrive in the URL from the landing redirect
  const tokens = parseTokensFromUrl();
  const { session } = useCheckout(tokens?.checkoutToken, tokens?.sessionToken);
  // ...
}
```

### Full Hook API

| Method                                                                                                                | Description                                                                                                                  |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `session`                                                                                                             | Current checkout session (null until loaded)                                                                                 |
| `isLoading`                                                                                                           | True during any async operation                                                                                              |
| `error`                                                                                                               | Last error, if any                                                                                                           |
| `refresh()`                                                                                                           | Reload the session                                                                                                           |
| `createSession({ items, currency, returnUrl?, discountCodes?, metadata?, customerMetadata?, customerTags? })`         | Create a session and load it in place (same page)                                                                            |
| `createSessionUrl({ items, checkoutPath?, returnUrl?, discountCodes?, metadata?, customerMetadata?, customerTags? })` | Create a session and get a redirect URL to your `/checkout` page — use on the landing CTA, then `window.location.href = url` |
| `updateCart(items)`                                                                                                   | Replace cart items                                                                                                           |
| `updateCustomer({ email, firstName, lastName, phone })`                                                               | Store customer identity (in memory)                                                                                          |
| `updateAddress({ shippingAddress?, billingAddress? })`                                                                | Set addresses + persist customer data (single API call)                                                                      |
| `applyPromo(code)`                                                                                                    | Apply a promo code                                                                                                           |
| `removePromo(promotionId?)`                                                                                           | Remove a promo code                                                                                                          |
| `getShippingRates()`                                                                                                  | Fetch available shipping rates                                                                                               |
| `selectShippingRate(rateId)`                                                                                          | Select a shipping rate                                                                                                       |
