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

# 创建收银台会话

> 通过编程方式生成带有自定义参数的支付链接

<Accordion title="✨ 利用 AI 安装" icon="sparkles">
  将此提示复制到你的 AI 代码编辑器（Cursor、Copilot 等）中，即可创建收银台集成：

  ```text theme={"system"}
  Add Waffo Pancake checkout to my project using the official TypeScript SDK.

  npm install @waffo/pancake-ts

  Create a WaffoPancake client with WAFFO_MERCHANT_ID and WAFFO_PRIVATE_KEY.
  Use client.checkout.createSession({ productId, currency: "USD" })
  to get a checkoutUrl, then redirect the user with res.redirect(checkoutUrl).

  For webhooks, use verifyWebhook(rawBody, signature) from the SDK — it has embedded public keys,
  no secret needed. Handle events: order.completed, subscription.activated, subscription.canceled.

  Read https://waffo.mintlify.app/llms-full.txt for full API reference.
  ```
</Accordion>

***

## 你将构建什么

收银台会话是一个动态生成的支付页面。相比分享静态的产品链接，你可以：

* 预填客户邮箱
* 使用自定义 metadata 追踪订单
* 设置自定义成功 URL
* 为订阅产品启用试用期

***

## 前置条件

* Waffo Pancake 账户
* API 密钥（Dashboard → API 与开发）
* 至少创建一个产品

***

## 基础收银台会话

最简单的收银台会话需要产品 ID 和货币。

### 使用 TypeScript SDK（推荐）

```bash theme={"system"}
npm install @waffo/pancake-ts
```

```typescript 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_xxx",
  currency: "USD",
});

// 将用户重定向到收银台页面
res.redirect(session.checkoutUrl);
```

### 直接使用 REST API

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
    -H "Content-Type: application/json" \
    -H "X-Store-Slug: your-store-slug" \
    -H "X-Environment: test" \
    -d '{
      "productId": "your-product-uuid",
      "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-Store-Slug': 'your-store-slug',
          'X-Environment': 'test',
      },
      json={
          'productId': 'your-product-uuid',
          'currency': 'USD',
      }
  )

  checkout_url = response.json()['data']['checkoutUrl']
  # 将客户重定向到 checkout_url
  ```
</CodeGroup>

**响应：**

```json theme={"system"}
{
  "data": {
    "sessionId": "cs_xxx",
    "checkoutUrl": "https://checkout.waffo.ai/your-store/checkout/cs_xxx",
    "expiresAt": "2024-01-22T12:00:00Z"
  }
}
```

将客户重定向到 `checkoutUrl` 完成支付。

***

## 重定向最佳实践

<Warning>
  **不要使用 `window.open()` 打开收银台页面。** Safari 和许多移动端浏览器会拦截在异步回调中（例如 API 调用之后）打开的弹出窗口。这会导致客户的收银台流程静默失败。
</Warning>

**推荐方式：**

| 方式     | 使用场景           | 示例                                   |
| ------ | -------------- | ------------------------------------ |
| 服务端重定向 | API 路由 / 服务端操作 | `res.redirect(checkoutUrl)`          |
| 客户端重定向 | SPA / React    | `window.location.href = checkoutUrl` |
| 链接元素   | 静态链接           | `<a href={checkoutUrl}>`             |

```typescript theme={"system"}
// ✅ 推荐：服务端重定向
export async function POST(req: NextRequest) {
  const session = await client.checkout.createSession({ ... });
  return NextResponse.redirect(session.checkoutUrl);
}

// ✅ 也可以：客户端重定向
const { checkoutUrl } = await res.json();
window.location.href = checkoutUrl;

// ❌ 避免：window.open — 会被 Safari 和移动端浏览器拦截
window.open(checkoutUrl, "_blank"); // 会被拦截！
```

支付完成后，客户会被重定向回你的 `successUrl`。使用 `{SESSION_ID}` 占位符即可在成功页面验证付款。

***

## 预填客户邮箱

通过预填邮箱跳过邮箱输入步骤：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  buyerEmail: "user@example.com",
});
```

**使用场景：** 用户已登录你的应用，你已知道他们的邮箱。

***

## 使用 Metadata 追踪

将收银台会话与你的内部系统关联：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  metadata: {
    user_id: "usr_abc123",
    campaign: "black_friday_2024",
    referrer: "twitter",
  },
  successUrl: "https://yoursite.com/success",
});
```

Metadata 存储在收银台会话上，可通过 `checkoutSession` GraphQL 查询获取。Webhook 负载中传回 metadata 功能即将上线。

***

## 订阅试用

为订阅产品启用试用期：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "prod_subscription",
  currency: "USD",
  withTrial: true,
  successUrl: "https://yoursite.com/welcome",
});
```

<Note>
  优惠码和按座位数量功能尚未通过 API 支持。请通过 Dashboard 管理折扣。
</Note>

***

## 订阅预填账单信息

为税费计算预填消费者的账单信息：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  billingDetail: {
    country: "US",
    isBusiness: false,
    state: "CA",
    postcode: "94105",
  },
});
```

***

## 动态成功 URL

将数据传递到成功页面：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  successUrl: "https://yoursite.com/success?session_id={SESSION_ID}",
});
```

`{SESSION_ID}` 会被替换为实际的会话 ID，这样你可以在成功页面验证付款。

***

## 完整示例：SaaS 升级流程

这是一个使用 SDK 升级用户订阅的真实示例：

```typescript theme={"system"}
// app/api/upgrade/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";
import { WaffoPancake } from "@waffo/pancake-ts";

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

export async function POST(req: NextRequest) {
  const { userId, planId, email } = await req.json();

  const session = await client.checkout.createSession({
    productId: planId,
    currency: "USD",
    buyerEmail: email,
    metadata: { user_id: userId, action: "upgrade" },
    successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?upgraded=true`,
  });

  // 服务端重定向 — 兼容所有浏览器
  return NextResponse.redirect(session.checkoutUrl);
}
```

如果你需要在客户端处理收银台（例如 SPA）：

```typescript theme={"system"}
async function handleUpgrade(planId: string) {
  const res = await fetch("/api/upgrade", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, planId, email: currentUser.email }),
  });
  const { checkoutUrl } = await res.json();

  // 在当前标签页跳转 — 兼容 Safari
  window.location.href = checkoutUrl;
}
```

***

## 参数参考

| 参数           | 类型      | 必填 | 描述                             |
| ------------ | ------- | -- | ------------------------------ |
| `productId`  | string  | 是  | Dashboard 中的产品 UUID            |
| `currency`   | string  | 是  | ISO 4217 货币代码（如 `"USD"`）       |
| `buyerEmail` | string  | 否  | 预填消费者邮箱                        |
| `successUrl` | string  | 否  | 自定义成功重定向 URL                   |
| `metadata`   | object  | 否  | 自定义键值数据，存储在会话上（Webhook 传回即将上线） |
| `withTrial`  | boolean | 否  | 为订阅产品启用试用期                     |

***

## 下一步

<CardGroup cols={2}>
  <Card title="处理 Webhooks" icon="webhook" href="/zh/guides/webhooks">
    在支付完成时获得通知
  </Card>

  <Card title="验证支付" icon="shield-check" href="/zh/api-reference/webhooks">
    安全验证 Webhook 签名
  </Card>
</CardGroup>
