> ## 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.

# 创建收银台会话

> 锁定商品版本、定价和货币用于收银台

创建收银台会话，锁定商品版本、定价和货币。这是一次性商品和订阅商品收银台流程的第一步。

典型流程为：

1. 商户在服务端创建收银台会话
2. 将 `checkoutUrl` 返回给前端
3. Customer 点击链接进入托管收银台页面
4. Customer 填写账单信息、预览税费并完成订单

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

**认证方式：** API Key 或 Store Slug

## 请求体（API Key）

| 字段                        | 类型      | 必需 | 说明                                                                                                                                                                                                                             |
| ------------------------- | ------- | -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `productId`               | string  | 是  | 商品 ID（Short ID 格式 `PROD_xxx`）                                                                                                                                                                                                  |
| `currency`                | string  | 是  | ISO 4217 货币代码（例如 `USD`、`EUR`、`JPY`）                                                                                                                                                                                            |
| `priceSnapshot`           | object  | 否  | 在会话创建时覆盖定价（仅 API Key，见下方）                                                                                                                                                                                                      |
| `withTrial`               | boolean | 否  | 启用试用期（仅订阅商品）                                                                                                                                                                                                                   |
| `buyerEmail`              | string  | 否  | 在收银台页面预填 customer 邮箱                                                                                                                                                                                                           |
| `billingDetail`           | object  | 否  | 预填账单详情（见下方）                                                                                                                                                                                                                    |
| `successUrl`              | string  | 否  | 覆盖支付成功后的跳转 URL                                                                                                                                                                                                                 |
| `expiresInSeconds`        | number  | 否  | 会话 TTL（秒）（默认 `2700` = 45 分钟，仅 API Key）                                                                                                                                                                                         |
| `darkMode`                | boolean | 否  | 启用收银台页面暗色模式                                                                                                                                                                                                                    |
| `metadata`                | object  | 否  | 附加到会话的自定义键值对                                                                                                                                                                                                                   |
| `orderMerchantExternalId` | string  | 否  | 商户业务侧订单编号（最大 128 字符）                                                                                                                                                                                                           |
| `language`                | string  | 否  | 收银台语言（IETF BCP 47），必须是受支持的枚举值之一：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。设置收银台页面默认语言，消费者可在页面自行切换 |

## 请求体（Store Slug）

| 字段              | 类型      | 必需 | 说明                                                                                                                                                                                                                             |
| --------------- | ------- | -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `productId`     | string  | 是  | 商品 ID（Short ID 格式 `PROD_xxx`）                                                                                                                                                                                                  |
| `currency`      | string  | 是  | ISO 4217 货币代码                                                                                                                                                                                                                  |
| `buyerEmail`    | string  | 否  | 预填 customer 邮箱                                                                                                                                                                                                                 |
| `billingDetail` | object  | 否  | 预填账单详情（见下方）                                                                                                                                                                                                                    |
| `successUrl`    | string  | 否  | 覆盖支付成功后的跳转 URL                                                                                                                                                                                                                 |
| `darkMode`      | boolean | 否  | 启用收银台页面暗色模式                                                                                                                                                                                                                    |
| `language`      | string  | 否  | 收银台语言（IETF BCP 47），必须是受支持的枚举值之一：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。设置收银台页面默认语言，消费者可在页面自行切换 |

<Note>
  Store Slug 认证不支持 `priceSnapshot`、`expiresInSeconds`、`metadata`、`orderMerchantExternalId` 和 `withTrial`。这些字段会被静默忽略，以防止客户端篡改价格和操纵会话。
</Note>

<Note>
  **ID 命名约定**：同一组扁平双 key 名（checkout 端的 `orderMerchantExternalId`、退款工单端的 `refundTicketMerchantExternalId`）贯穿请求体、webhook 载荷以及承载这些值的每个 GraphQL 类型 — 在 checkout 创建时写入的值可在 `Order`、`Payment`、`Refund` 及 webhook 载荷中以相同的字段名读回。
</Note>

## 价格快照对象

| 字段            | 类型      | 必需 | 说明                                     |
| ------------- | ------- | -- | -------------------------------------- |
| `amount`      | string  | 是  | 显示格式的价格字符串（例如 "29.00" = \$29.00）       |
| `taxIncluded` | boolean | 是  | 金额是否已包含税费                              |
| `taxCategory` | string  | 是  | 用于税率计算的税务类别（例如 `saas`、`digital_goods`） |

## 账单详情对象

| 字段             | 类型      | 必需  | 说明                                         |
| -------------- | ------- | --- | ------------------------------------------ |
| `country`      | string  | 是   | ISO 3166-1 alpha-2 国家代码（例如 `US`、`JP`、`DE`） |
| `isBusiness`   | boolean | 是   | 是否为企业购买                                    |
| `postcode`     | string  | 否   | 邮政编码                                       |
| `state`        | string  | 条件性 | `US` 和 `CA` 必填（例如 `CA`、`NY`、`ON`）          |
| `businessName` | string  | 条件性 | `isBusiness` 为 `true` 时必填                  |
| `taxId`        | string  | 条件性 | `isBusiness` 为 `true` 时欧盟国家必填（VAT 号码）      |

## 会话锁定

创建收银台会话时，以下值被锁定，在会话有效期内无法更改：

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

**邮箱规范化**：所有邮箱字段（`email` / `buyerEmail` / `contactEmail`）在服务端会被规范化（`trim().toLowerCase()`）后再用于存储、cache、下游调用。`Foo@Bar.COM` 与 `foo@bar.com` 视为同一账户。

## 请求示例

<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>

## 成功响应 (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"
  }
}
```

## 响应字段

| 字段            | 类型     | 说明                                    |
| ------------- | ------ | ------------------------------------- |
| `sessionId`   | string | 收银台会话 ID（`cs_` + UUID 格式，不是 Short ID） |
| `checkoutUrl` | string | 将 customer 重定向到托管收银台页面的完整 URL         |
| `expiresAt`   | string | 会话过期的 ISO 8601 时间戳                    |

## 错误响应

> **重试策略**：4xx 一律不要重试 — 修正请求后重发。5xx 指数退避重试（起步 5s，最多 3 次）。

| 状态码 | `errors[0].message`                                                                     | 含义                                   | 推荐处理                     |
| --- | --------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------ |
| 400 | `Missing or invalid header: x-context-environment`                                      | environment 请求头缺失或不是 `test` / `prod` | 修正请求头后重发                 |
| 400 | `Invalid JSON body`                                                                     | 请求体不是合法 JSON                         | 修正请求体后重发                 |
| 400 | `Missing required fields: productId, currency`                                          | `productId` 或 `currency` 缺失          | 修正请求体后重发                 |
| 400 | `Invalid currency format: "X". Must be 3 uppercase letters (e.g., "USD", "EUR", "JPY")` | 货币代码不是 3 位大写字母（ISO 4217）             | 使用合法 ISO 4217 代码后重发      |
| 400 | `Invalid billingDetail`                                                                 | 账单详情验证失败（例如 US 缺少 `state`）           | 修正 billingDetail 后重发     |
| 400 | `Invalid language, must be one of: en, pt-BR, … ms-MY`                                  | `language` 不在受支持的收银台语言枚举内            | 使用受支持的 `language`，或省略该字段 |
| 400 | `Expected format: PROD_xxx, got "..."`                                                  | `productId` Short ID 解码失败            | 修正 `productId` 格式后重发     |
| 400 | `orderMerchantExternalId must not be empty`                                             | 字段已传但 trim 后为空                       | 不传该字段或传入非空值              |
| 400 | `orderMerchantExternalId must be at most 128 characters`                                | 值超过 128 字符                           | 缩短到 128 字符以内             |
| 400 | `Currency X is not supported for this product`                                          | 产品在请求的货币下无 price snapshot            | 使用产品支持的货币                |
| 401 | `Unauthorized`                                                                          | API Key 签名无效或 Store Slug 无法识别        | 检查认证请求头                  |
| 403 | `Store is not active`                                                                   | 门店状态非 `active`                       | 先激活门店                    |
| 403 | `Store is not approved for production payments`                                         | 门店未获 `prod` 环境审批                     | 使用 `test` 环境或申请门店审批      |
| 403 | `Product does not belong to this store`                                                 | 产品不属于当前访问的门店                         | 验证产品/门店配对                |
| 404 | `Store not found`                                                                       | 没有匹配的 slug                           | 验证 slug 和 environment    |
| 404 | `Product not found`                                                                     | 产品不存在                                | 验证 `productId`           |
| 404 | `Product not found or not active for this environment`                                  | 该 environment 下产品无 active 版本         | 为此环境激活产品版本               |
| 500 | `Internal server error`                                                                 | 服务端意外失败                              | 指数退避重试（起步 5s，最多 3 次）     |

<Tip>
  默认会话 TTL 为 45 分钟（2700 秒）。使用 API Key 认证时，可通过 `expiresInSeconds` 自定义。会话在创建时锁定商品版本和定价，因此价格变更不会影响已有会话。
</Tip>
