跳转到主要内容
创建收银台会话,锁定商品版本、定价和货币。这是一次性商品和订阅商品收银台流程的第一步。 典型流程为:
  1. 商户在服务端创建收银台会话
  2. 将 checkoutUrl 返回给前端
  3. Customer 点击链接进入托管收银台页面
  4. Customer 填写账单信息、预览税费并完成订单
认证方式: API Key 或 Store Slug

请求体(API Key)

请求体(Store Slug)

Store Slug 认证不支持 priceSnapshot、expiresInSeconds、metadata、orderMerchantExternalId、withTrial、changeAmount、changeCreditAmount、includePaymentMethods 和 excludePaymentMethods。这些字段会被静默忽略,以防止客户端篡改价格和操纵会话。支付方式属于商户侧的商业决策(渠道费率、结算周期),浏览器不得收窄或放宽——Store Slug 创建的 session 一律开放该货币支持的全部方式。couponId 与 promotionCode 例外:它们不会被忽略,而是返回 403,买家永远无法自己挑选优惠券。
ID 命名约定:同一组扁平双 key 名(checkout 端的 orderMerchantExternalId、退款工单端的 refundTicketMerchantExternalId)贯穿请求体、webhook 载荷以及承载这些值的每个 GraphQL 类型 — 在 checkout 创建时写入的值可在 Order、Payment、Refund 及 webhook 载荷中以相同的字段名读回。
本接口没有 cancelUrl 参数。 买家关闭收银台或放弃支付时,不会跳回你的站点。会话到期(默认 24 小时)后订单以 canceled 结束,不产生扣款,也不发 webhook —— 你这边无需处理。传入 cancelUrl、returnUrl 之类的字段会被静默忽略,不报错。请在打开收银台的那个页面上保留自己的「返回商店」入口。

价格快照对象

对一次性商品,amount 直接替换商品价格。对订阅商品,它只替换常规周期价格 —— 试用期价格取自 session 锁定的商品版本,此处无法覆盖。

账单详情对象

billingDetail 只做预填。你传的国家会成为收银台国家下拉框的初始值(商家预填优先于 IP 探测),买家可以改,订单最终记录的是买家自己选定的国家。没有任何会话参数或 URL 参数能锁定或隐藏账单字段。支付方式列表不由国家驱动:它按产品类型 × 币种再加 includePaymentMethods / excludePaymentMethods 解析;国家只用于税费预览和决定展示哪些地址字段,不影响买家的付款币种 —— 币种由买家打开付款页时的 IP 所在地决定,见本地币种定价。

换方案模式

传入 originOrderId 即把本端点切换到换方案模式:返回的 checkoutUrl 不再指向新购链接,而是指向一笔已有订阅的变更确认页 —— 门店 slug 之后的路径段是 change 而不是 checkout(…/store/{storeSlug}/change/{sessionId})。换方案分两步 —— 本端点签发链接,customer 在该页面确认。 换方案被建模为一次取消加一次新建:旧订单进入取消终态,新订单走正常的创建生命周期。它不是对现有订阅的原地修改。 谁能签发链接:API Key(你的服务端)或 customer 会话令牌。Store Slug 是匿名身份,没有可归属的订阅 —— 用 Store Slug 认证传 originOrderId 一律返回 403。

用 SDK 签发链接

每个 SDK 都给换方案单独一个方法,originOrderId 必填,因此上面那些「必须与 originOrderId 同时出现」的拒绝在 SDK 里根本表达不出来:
上面这三段都用你的 API Key 签名,因此两个定价字段与 withTrial 都会生效。用 checkout.authenticated.createPlanChange()(TypeScript / Next.js 的 authenticatedPlanChange)或 Checkout.Authenticated.CreatePlanChange(Go)可以让 SDK 替你把 customer 会话令牌拼到链接上 —— 那正是确认页需要的令牌,见下一节。 买家自行签发的那一半用 customer.createPlanChangeSession(),在 customer 会话上调用(TypeScript 的 client.customer(token)、Go 的 client.Customer(token)、Next.js 的 customerAction(token, "createPlanChangeSession", …))。它以买家自己的凭证发起,因此上面那三条 403 判据全部适用,且仅 API Key 可用的字段不在其入参里 —— 后端会静默丢弃它们。

构造给买家打开的链接

返回的 checkoutUrl 既不带买家凭证、也不带环境参数 —— 会话是用你的服务端凭证创建的,平台此时无法代表买家授权。直接打开裸链接能看到方案对比页,但确认那一步会报「缺少授权信息」。两部分都要你自己拼:
查询参数在前、fragment 在后。生产环境去掉 ?test=true。 不带令牌时页面回退到买家邮箱验证 —— 可用但更慢。环境拼错的链接过去会在确认那一步报 Environment mismatch between request and session。
changeAmount、changeCreditAmount 与 withTrial 仅 API Key 可用。用其他凭证传入会被静默忽略,与新购时的 priceSnapshot 同一口径 —— 不会有错误告诉你它们被丢弃了。

哪些值取自原订阅

换方案模式下,这些值来自你要变更的那笔订阅,而非请求体: 货币也必须与原订阅一致 —— 跨币种换方案会被拒绝。

为这次变更定价

两个字段,两种为同一次变更定价的方式,二者互斥 —— 同时传入返回 400。 两者都是主单位小数串、同量纲同口径(含税),且都要求结果不为负:实收高于目标方案本期应收会被拒绝,抵扣高于它同样会被拒绝。两条拒绝是不同的文案,因此你能看出该调低哪个字段。 换方案带了优惠时,优惠在此之上计算:立即生效的变更,你指定的金额就是第 1 期的优惠前金额,买家实付为它减去优惠。优惠额本身按目标方案全价计算,并以你指定的金额为上限:目标方案 20.00、changeAmount: "12.00"、减 20% 的码 → 优惠 4.00,买家实付 8.00。 同一个数字在两个字段下含义相反,所以平台不会替你选一个。按你定价的方式挑:知道最终金额用 changeAmount,知道优惠额度用 changeCreditAmount。对下面的生效档位规则而言,传了任意一个都算「商户已为这次变更定价」。 会话内部只存一个口径 —— 本期实收,因此你指定的抵扣会在签发时刻按目标方案当时的应收折算成实收。

新方案何时生效

changeTiming 接受 immediate 或 next_period。不传则由平台按变更方向推导,方向按税前定价比较得出:升级默认 immediate,降级与同价切换默认 next_period。
升级在最后 24 小时内不会自动降到下周期。 升级的默认档位是 immediate,因此距下次扣款不足 24 小时时,不传 changeTiming 会直接拿到 400 —— 需显式传 changeTiming: "next_period"。降级的默认档位本就是 next_period,同一窗口不受影响。

同时只能有一笔已确认的变更

customer 还在犹豫时,同一笔订阅可以连续签发并提交多笔候选变更。只有当某笔变更被支付渠道确认之后,该订阅才被锁定到这一笔;此后再发起会返回 409。

响应

响应体与新购模式完全一致 —— sessionId、checkoutUrl、expiresAt,不新增任何字段。变更方向、推导出的生效档位与折抵预览只写进会话供确认页读取,不回显。两个后果:
  • 不传 changeTiming 时你不会得知平台推导成了哪一档。需要确定就显式传。
  • 折抵预览是签发时刻的估算,提交时会按当时实际剩余天数重算。不要把它当作最终金额呈现给 customer。
  • next_period 换方案会产生一笔 $0 验卡记录。Dashboard 交易列表里这一行显示方案标价,下方标出 Charged $0.00,且不计入订阅的扣款次数。
变更落地后会收到什么,见 subscription.plan_changed 与另两个换方案事件。升降级对 customer 的行为口径,见 订阅。

优惠

一次性商品、新购订阅与换方案的 Checkout Session 都可以用以下方式带上优惠。couponId 不能与 promotionCode 或 allowPromotionCodes 同传 —— 同时传返回 400。 输码框默认值。 请求不传 allowPromotionCodes 时 —— 不论凭证:商户凭证(API Key 或 JWT)、Store Slug(商品链接或店铺首页),或签发换方案的 customer 或 buyer 会话令牌 ——只有本店本环境至少存在一张可用于该 session 商品(换方案为新方案的商品)的优惠券,才显示输码框。该优惠券须为 active、60 秒内不会到期(redeemBy)、总使用次数未达 maxRedemptions,并适用于该商品(productIds 为空或包含它);且它自身的码或至少一个促销码为 active、60 秒内不会到期、自身使用次数未达 maxRedemptions。与买家相关的规则(限定买家、单买家次数、最低消费)以及固定减额券的币种不参与判断,在买家输码时校验。刚到期或刚达到上限的优惠券或码,最多 5 分钟内仍可能显示输码框。传了 allowPromotionCodes 时按传入值生效;带 couponId、商品链接 discount_code,或传 promotionCode 而未传 allowPromotionCodes 的 session 不显示输码框。 创建 session 时校验。 传 couponId 或 promotionCode 时立即按与买家无关的规则校验:须属于本店本环境、状态为 active、60 秒内不会到期、适用于该商品(换方案为新方案的商品),固定金额优惠券须有本次币种的金额。promotionCode 在本店有效的码中按大小写不敏感匹配。不满足时返回 409,errors[0].message 为原因码(见错误响应)。 下单时校验。 与买家相关的规则(限定买家、单买家次数)、优惠券的总使用次数,以及促销码自身的总使用次数(maxRedemptions)与最低消费(minimumAmount)在买家提交订单时再次校验。不满足时不会创建订单、不会扣款,Checkout Session 仍可继续使用。买家接下来能做什么取决于优惠的来源:自己在输码框输入的码可以移除后按原价付款,或换一个码;商品链接带入的码已锁定,买家需重新打开不带 discount_code 的商品链接(链接中的码在打开页面时即不可用的,该页面会提供「不使用优惠继续」);你设置的 couponId 同样已锁定,需要不带它重新创建 Checkout Session;未同时传 allowPromotionCodes: true 的 promotionCode 也是如此。全部原因码见优惠与促销。 订阅。 新购订阅按优惠券的 duration 决定优惠几个计费周期;零元试用期不享受优惠,优惠从首个付费周期开始,付费试用期则享受优惠。见优惠与促销。 换方案。 换方案 session(带 originOrderId)同样接受 couponId、promotionCode 与 allowPromotionCodes。随换方案带上的码 —— 你设置的或买家输入的 —— 替换订阅当前的优惠,是一次新的兑换:买家确认换方案时重新校验全部规则,促销码最低消费按新方案正价期的优惠前、不含税金额判断。不带码时,当前优惠在可延续的情况下延续到新方案,已用周期不重置。第 1 期的优惠前金额为新方案价减去按天折抵,或你用 changeAmount / changeCreditAmount 指定的金额。优惠按新方案全价计算,再扣除原方案剩余抵扣,买家实付为该金额减去优惠,不低于 0;为 0 时优惠从第 2 期开始。见优惠与促销。
两个 SDK 都会在发请求前拒绝 couponId 与 allowPromotionCodes 或 promotionCode 同传,以及不带 CPN_ 前缀的 couponId。Next.js SDK 的 server action 会剥离 couponId 与 promotionCode —— 见 Next.js 集成。

会话锁定

创建收银台会话时,以下值被锁定,在会话有效期内无法更改: productVersionId、productName、priceInfo、storeName、billingPeriod、withTrial、theme、buyerEmail、billingDetail 换方案模式下,会话还会额外锁定变更上下文:原订阅的快照、变更方向、推导出的生效档位与生效时刻、折抵预览、你指定的金额(传 changeCreditAmount 时存的是它折算出的实收),以及 session 上设置的优惠。确认页全部从会话读取,因此这些值在提交时都无法被覆盖 —— 目标方案、生效档位与金额在链接签发的那一刻就已定下。 邮箱规范化:所有邮箱字段(email / buyerEmail / contactEmail)在服务端会被规范化(trim().toLowerCase())后再用于存储、cache、下游调用。Foo@Bar.COM 与 foo@bar.com 视为同一账户。

请求示例

成功响应 (200)

响应字段

错误响应

重试策略:4xx 一律不要重试 — 修正请求后重发。下方的 409 是种类上的例外而非策略上的例外:它们报告的是订阅当前的状态,不是请求体写错了,因此原样重发只会一直失败,直到该状态发生变化。5xx 指数退避重试(起步 5s,最多 3 次)。
收款开关与生产环境审批是两个相互独立的条件:前者是平台侧控制的收款开关(可暂停整个门店,也可只暂停单个支付方式),后者是生产环境审批(KYB)闸门。在 prod 环境下,任一条件单独不满足即返回 403。两者在 test 环境均不适用。
默认会话 TTL 为 24 小时(86400 秒)。使用 API Key 认证时,可通过 expiresInSeconds 自定义(建议 1 分钟 ~ 7 天)。会话在创建时锁定商品版本和定价,因此价格变更不会影响已有会话。