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

# Create Checkout Session

> Lock product version, pricing, and currency for checkout

Create a checkout session that locks product version, pricing, and currency. This is the first step in the checkout flow for both one-time and subscription products.

The typical flow is:

1. Merchant creates a checkout session (server-side)
2. Returns `checkoutUrl` to the frontend
3. Customer clicks the link and lands on the hosted checkout page
4. Customer fills in billing details, previews tax, and completes the order

```
POST /v1/actions/checkout/create-session
```

**Authentication:** API Key or Store Slug

## Request Body (API Key)

| Field                     | Type    | Required | Description                                                                                                                                                                                                                                                                                                                             |
| ------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productId`               | string  | Yes      | Product ID in Short ID format (`PROD_xxx`)                                                                                                                                                                                                                                                                                              |
| `currency`                | string  | Yes      | ISO 4217 currency code (e.g. `USD`, `EUR`, `JPY`)                                                                                                                                                                                                                                                                                       |
| `priceSnapshot`           | object  | No       | Override pricing at session creation (API Key only, see below)                                                                                                                                                                                                                                                                          |
| `withTrial`               | boolean | No       | Enable trial period (subscription products only)                                                                                                                                                                                                                                                                                        |
| `buyerEmail`              | string  | No       | Pre-fill customer's email on the checkout page                                                                                                                                                                                                                                                                                          |
| `billingDetail`           | object  | No       | Pre-fill billing details (see below)                                                                                                                                                                                                                                                                                                    |
| `successUrl`              | string  | No       | Override redirect URL after successful payment                                                                                                                                                                                                                                                                                          |
| `expiresInSeconds`        | number  | No       | Session TTL in seconds (default: `2700` = 45 minutes, API Key only)                                                                                                                                                                                                                                                                     |
| `darkMode`                | boolean | No       | Enable dark mode for the checkout page                                                                                                                                                                                                                                                                                                  |
| `metadata`                | object  | No       | Custom key-value pairs attached to the session                                                                                                                                                                                                                                                                                          |
| `orderMerchantExternalId` | string  | No       | Your business-side order reference (max 128 chars).                                                                                                                                                                                                                                                                                     |
| `language`                | string  | No       | Cashier UI language (IETF BCP 47). Must be one of the supported enum values: en, pt-BR, es-MX, id-ID, vi-VN, ru-RU, en-KE, es-PE, es-CO, es-CL, zh-Hant-TW, zh-Hant-HK, th-TH, ja-JP, en-NG, ko-KR, en-HK, zh-Hans-HK, pl-PL, tr-TR, zh-Hans, ms-MY. Sets the checkout page's default language; the customer can switch it on the page. |

## Request Body (Store Slug)

| Field           | Type    | Required | Description                                                                                                                                                                                                                                                                                                                             |
| --------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productId`     | string  | Yes      | Product ID in Short ID format (`PROD_xxx`)                                                                                                                                                                                                                                                                                              |
| `currency`      | string  | Yes      | ISO 4217 currency code                                                                                                                                                                                                                                                                                                                  |
| `buyerEmail`    | string  | No       | Pre-fill customer's email                                                                                                                                                                                                                                                                                                               |
| `billingDetail` | object  | No       | Pre-fill billing details (see below)                                                                                                                                                                                                                                                                                                    |
| `successUrl`    | string  | No       | Override redirect URL after successful payment                                                                                                                                                                                                                                                                                          |
| `darkMode`      | boolean | No       | Enable dark mode for the checkout page                                                                                                                                                                                                                                                                                                  |
| `language`      | string  | No       | Cashier UI language (IETF BCP 47). Must be one of the supported enum values: en, pt-BR, es-MX, id-ID, vi-VN, ru-RU, en-KE, es-PE, es-CO, es-CL, zh-Hant-TW, zh-Hant-HK, th-TH, ja-JP, en-NG, ko-KR, en-HK, zh-Hans-HK, pl-PL, tr-TR, zh-Hans, ms-MY. Sets the checkout page's default language; the customer can switch it on the page. |

<Note>
  Store Slug authentication does not support `priceSnapshot`, `expiresInSeconds`, `metadata`, `orderMerchantExternalId`, or `withTrial`. These fields are silently ignored to prevent price tampering and session manipulation from the client side.
</Note>

<Note>
  **ID-naming convention**: the same flat dual-key names (`orderMerchantExternalId` on checkout-side, `refundTicketMerchantExternalId` on refund-ticket-side) are used across request bodies, webhook payloads, and every GraphQL type that carries the value — so a value written at checkout can be read back from `Order`, `Payment`, `Refund`, or a webhook payload under the same field name.
</Note>

## Price Snapshot Object

| Field         | Type    | Required | Description                                                      |
| ------------- | ------- | -------- | ---------------------------------------------------------------- |
| `amount`      | string  | Yes      | Price as a display format string (e.g., "29.00" = \$29.00)       |
| `taxIncluded` | boolean | Yes      | Whether the amount already includes tax                          |
| `taxCategory` | string  | Yes      | Tax category for tax calculation (e.g., `saas`, `digital_goods`) |

## Billing Detail Object

| Field          | Type    | Required    | Description                                                        |
| -------------- | ------- | ----------- | ------------------------------------------------------------------ |
| `country`      | string  | Yes         | ISO 3166-1 alpha-2 country code (e.g. `US`, `JP`, `DE`)            |
| `isBusiness`   | boolean | Yes         | Whether this is a business purchase                                |
| `postcode`     | string  | No          | Postal or ZIP code                                                 |
| `state`        | string  | Conditional | Required for `US` and `CA` (e.g. `CA`, `NY`, `ON`)                 |
| `businessName` | string  | Conditional | Required when `isBusiness` is `true`                               |
| `taxId`        | string  | Conditional | Required for EU countries when `isBusiness` is `true` (VAT number) |

## Session Locks

When a checkout session is created, the following values are locked and cannot change during the session lifetime:

`productVersionId`, `productName`, `priceInfo`, `storeName`, `billingPeriod`, `withTrial`, `theme`, `buyerEmail`, `billingDetail`

**Email normalization**: All email fields (`email`, `buyerEmail`, `contactEmail`) are normalized server-side via `trim().toLowerCase()` before storage, cache writes, and downstream calls. `Foo@Bar.COM` and `foo@bar.com` are treated as the same account.

## Example Request

<CodeGroup>
  ```typescript SDK theme={"system"}
  import { WaffoPancake } from "@waffo/pancake-ts";

  const client = new WaffoPancake({
    merchantId: process.env.WAFFO_MERCHANT_ID!,
    privateKey: process.env.WAFFO_PRIVATE_KEY!,
  });

  const session = await client.checkout.createSession({
    productId: "PROD_7J3K5L8M2N4P6Q9R",
    currency: "USD",
    buyerEmail: "customer@example.com",
    language: "en",
    successUrl: "https://example.com/thank-you",
  });

  console.log(session.sessionId);   // "cs_550e8400-e29b-41d4-a716-446655440000"
  console.log(session.checkoutUrl); // "https://checkout.waffo.ai/..."
  console.log(session.expiresAt);   // "2026-01-22T10:30:00.000Z"
  ```

  ```bash cURL (API Key) theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
    -H "Content-Type: application/json" \
    -H "X-Merchant-Id: MER_2D5F8G3H1K4M6N9P" \
    -H "X-Timestamp: 1711800000" \
    -H "X-Signature: BASE64_RSA_SIGNATURE" \
    -d '{
      "productId": "PROD_7J3K5L8M2N4P6Q9R",
      "currency": "USD",
      "buyerEmail": "customer@example.com",
      "language": "en",
      "successUrl": "https://example.com/thank-you"
    }'
  ```

  ```bash cURL (Store Slug) theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
    -H "Content-Type: application/json" \
    -H "X-Store-Slug: my-store-abc123" \
    -H "X-Environment: test" \
    -d '{
      "productId": "PROD_7J3K5L8M2N4P6Q9R",
      "currency": "USD"
    }'
  ```

  ```python Python theme={"system"}
  import requests

  response = requests.post(
      "https://api.waffo.ai/v1/actions/checkout/create-session",
      headers={
          "Content-Type": "application/json",
          "X-Merchant-Id": "MER_2D5F8G3H1K4M6N9P",
          "X-Timestamp": "1711800000",
          "X-Signature": "BASE64_RSA_SIGNATURE",
      },
      json={
          "productId": "PROD_7J3K5L8M2N4P6Q9R",
          "currency": "USD",
          "buyerEmail": "customer@example.com",
          "language": "en",
      },
  )

  data = response.json()["data"]
  print(data["checkoutUrl"])
  ```
</CodeGroup>

## Success Response (200)

```json theme={"system"}
{
  "data": {
    "sessionId": "cs_550e8400-e29b-41d4-a716-446655440000",
    "checkoutUrl": "https://checkout.waffo.ai/my-store-abc123/checkout/cs_550e8400-e29b-41d4-a716-446655440000",
    "expiresAt": "2026-01-22T10:30:00.000Z"
  }
}
```

## Response Fields

| Field         | Type   | Description                                                   |
| ------------- | ------ | ------------------------------------------------------------- |
| `sessionId`   | string | Checkout session ID (`cs_` + UUID format, not a Short ID)     |
| `checkoutUrl` | string | Full URL to redirect the customer to the hosted checkout page |
| `expiresAt`   | string | ISO 8601 timestamp when the session expires                   |

## Errors

> **Retry policy:** Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).

| Status | `errors[0].message`                                                                     | What it means                                                  | Recommended handling                                      |
| ------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------- |
| 400    | `Missing or invalid header: x-context-environment`                                      | Environment header missing or not `test` / `prod`              | Fix the header, resubmit                                  |
| 400    | `Invalid JSON body`                                                                     | Request body is not valid JSON                                 | Fix the body, resubmit                                    |
| 400    | `Missing required fields: productId, currency`                                          | `productId` or `currency` is missing                           | Fix the body, resubmit                                    |
| 400    | `Invalid currency format: "X". Must be 3 uppercase letters (e.g., "USD", "EUR", "JPY")` | Currency is not 3 uppercase letters (ISO 4217)                 | Use a valid ISO 4217 code, resubmit                       |
| 400    | `Invalid billingDetail`                                                                 | Billing detail validation failed (e.g. missing `state` for US) | Fix billing detail, resubmit                              |
| 400    | `Invalid language, must be one of: en, pt-BR, … ms-MY`                                  | `language` is not one of the supported cashier languages       | Use a supported `language`, or omit it                    |
| 400    | `Expected format: PROD_xxx, got "..."`                                                  | `productId` Short ID could not be decoded                      | Fix `productId` format, resubmit                          |
| 400    | `orderMerchantExternalId must not be empty`                                             | Field was provided but is empty after trim                     | Omit the field or pass a non-empty value                  |
| 400    | `orderMerchantExternalId must be at most 128 characters`                                | Value exceeds 128 chars                                        | Shorten to 128 chars or less                              |
| 400    | `Currency X is not supported for this product`                                          | Product has no price snapshot for the requested currency       | Use a currency supported by the product                   |
| 401    | `Unauthorized`                                                                          | Invalid API Key signature or Store Slug                        | Verify auth headers                                       |
| 403    | `Store is not active`                                                                   | Store status is not `active`                                   | Activate the store first                                  |
| 403    | `Store is not approved for production payments`                                         | Store is not enabled for `prod` environment                    | Use `test` environment, or get the store approved         |
| 403    | `Product does not belong to this store`                                                 | Product is not owned by the visiting store                     | Verify the product/store pair                             |
| 404    | `Store not found`                                                                       | No store matches the given slug                                | Verify slug and environment                               |
| 404    | `Product not found`                                                                     | Product does not exist                                         | Verify the `productId`                                    |
| 404    | `Product not found or not active for this environment`                                  | Product has no active version for the given environment        | Activate a product version for this environment           |
| 500    | `Internal server error`                                                                 | Unexpected server-side failure                                 | Retry with exponential backoff (start 5s, max 3 attempts) |

<Tip>
  The default session TTL is 45 minutes (2700 seconds). When using API Key auth, you can customize this with `expiresInSeconds`. Sessions lock the product version and pricing at creation time, so price changes will not affect existing sessions.
</Tip>
