创建收银台会话,锁定商品版本、定价和货币。这是一次性商品和订阅商品收银台流程的第一步。
典型流程为:
- 商户在服务端创建收银台会话
- 将
checkoutUrl 返回给前端
- 消费者点击链接进入托管收银台页面
- 消费者填写账单信息、预览税费并完成订单
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 | 否 | 在收银台页面预填消费者邮箱 |
billingDetail | object | 否 | 预填账单详情(见下方) |
successUrl | string | 否 | 覆盖支付成功后的跳转 URL |
expiresInSeconds | number | 否 | 会话 TTL(秒)(默认 2700 = 45 分钟,仅 API Key) |
darkMode | boolean | 否 | 启用收银台页面暗色模式 |
metadata | object | 否 | 附加到会话的自定义键值对 |
orderMerchantExternalId | string | 否 | 商户业务侧订单编号(最大 128 字符) |
请求体(Store Slug)
| 字段 | 类型 | 必需 | 说明 |
|---|
productId | string | 是 | 商品 ID(Short ID 格式 PROD_xxx) |
currency | string | 是 | ISO 4217 货币代码 |
buyerEmail | string | 否 | 预填消费者邮箱 |
billingDetail | object | 否 | 预填账单详情(见下方) |
successUrl | string | 否 | 覆盖支付成功后的跳转 URL |
darkMode | boolean | 否 | 启用收银台页面暗色模式 |
Store Slug 认证不支持 priceSnapshot、expiresInSeconds、metadata、orderMerchantExternalId 和 withTrial。这些字段会被静默忽略,以防止客户端篡改价格和操纵会话。
ID 命名约定:同一组扁平双 key 名(checkout 端的 orderMerchantExternalId、退款工单端的 refundTicketMerchantExternalId)贯穿请求体、webhook 载荷以及承载这些值的每个 GraphQL 类型 — 在 checkout 创建时写入的值可在 Order、Payment、Refund 及 webhook 载荷中以相同的字段名读回。
价格快照对象
| 字段 | 类型 | 必需 | 说明 |
|---|
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 视为同一账户。
请求示例
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",
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"
成功响应 (200)
{
"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 | 将消费者重定向到托管收银台页面的完整 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 | 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 次) |
默认会话 TTL 为 45 分钟(2700 秒)。使用 API Key 认证时,可通过 expiresInSeconds 自定义。会话在创建时锁定商品版本和定价,因此价格变更不会影响已有会话。