跳到正文
Indiana
控制台
文档
章节目录 / 会员与订阅

会员与订阅

直接购买、变更、取消与恢复订阅,也可通过 Funnel 执行。

SDK 0.0.1更新于

预览、确认与恢复购买

使用 npm @tinkerlandai/indiana-sdk@0.0.1 与匹配的服务端。先保存当前功能定义并发布会员商品,在当前 App 的 Sandbox 配置 Stripe 测试连接及价格绑定。Key 需要 billing:write;读取持有另需 entitlements:read。下面是可信后端的业务片段,groove 来自快速开始的 lib/groove-server.ts;customerId 必须由自己的已验证登录会话关联。

previewAttach 保存 15 分钟报价,不发起付款。attach 返回 Checkout URL 不代表已付;complete 才表示操作完成,权益再用 readHoldings 核对。响应丢失保留 operationId,不重新生成订单。未接受的报价过期或选择变化时才用新 requestId 重新报价。浏览器 SDK 的同名方法不传 customerId。

选择优惠时,先 listOffers,再传所选持券的 id 到 previewAttach.offerId;不是 Coupon 定义 ID。首次 Stripe Checkout 最多一张券,修改选择需重新报价。金额为币种的整数最小单位,自动税以渠道确认结果为准。多个套餐须满足服务端共同连接、币种、收费周期等条件;原生购买使用固定 SKU 和商店验单。

server/subscription-purchase.ts · fragmentTypeScript
import { groove } from "@/lib/groove-server"; // Persist this complete input before requesting a quote; reuse it on retry.const input = {  customerId: verifiedCustomer.id,  requestId: retainedRequestId,  plans: [{ productVersionId: selectedPublishedVersionId }],  provider: "stripe" as const,  currency: "USD" as const,};const { quote } = await groove.previewAttach(input);// Save quote.operationId. Show the quote and obtain explicit customer acceptance.// Execute only after that acceptance, using the same verified customer.const { operation } = await groove.attach({  customerId: verifiedCustomer.id,  operationId: quote.operationId,});// Return operation.checkoutUrl to the browser when present.// After payment return or a lost response, recover this original operation:const recovered = await groove.readSubscriptionOperation({  customerId: verifiedCustomer.id,  operationId: quote.operationId,});

套餐变更、席位与取消续费

Stripe 使用 immediate 或 renewal,明确操作目标会员并保留同订阅其他套餐。pending 表示等待付款或恢复,scheduled 表示已排期,effective 才表示变更生效;已验证付款决定收费权益交付。席位变更使用 change: { type: 'seats', licenseKey, quantity };预付 Feature 数量使用 { type: 'quantity', entitlementVersionId, quantity },减少数量在 renewal 生效。取消/恢复续费使用 cancel/uncancel,取消与退款是独立操作。

原生套餐替换使用 timing: store,由宿主处理商店指令和验证交易;不能把 Stripe 差额报价或可变席位套到 Apple/Google。发布包含接口不代表你的部署、SKU、试用资格或实际支付已验证。

server/subscription-update.ts · fragmentTypeScript
const { quote } = await groove.previewUpdate({  customerId: verifiedCustomer.id,  requestId: retainedChangeRequestId,  membershipId: currentMembershipId,  timing: "immediate",  change: { type: "plan", productVersionId: targetPublishedVersionId },});// Persist the quote, show its amounts and timing, and obtain customer acceptance.await groove.update({  customerId: verifiedCustomer.id,  operationId: quote.operationId,});const { operation } = await groove.readSubscriptionUpdate({  customerId: verifiedCustomer.id,  operationId: quote.operationId,});

Funnel 结账与会员管理

在商品中发布价格、权益和试用,再绑定当前环境的支付渠道。在 Funnel 中配置结账或会员动作,并通过明确的成功、取消和失败出口继续。

待付款保持等待;回跳不证明付款。购买、退款和会员变更以服务端验证事实与实际交付为准。只使用明确配置的 Sandbox 账户。
app/billing.tsTypeScript
import { createFunnelRunController, showFunnelRun } from "@tinkerlandai/indiana-sdk"; // Your backend retains the original start request and run credential.export async function openFunnel(container: HTMLElement) {const response = await fetch("/api/groove-funnel", {  method: "POST", credentials: "same-origin", cache: "no-store",});if (!response.ok) throw new Error("Funnel bootstrap failed");const { runToken } = await response.json();const client = createFunnelRunController({  baseUrl: "https://grow.your-domain.example",  scope: { projectId: "your-app-uuid", environment: "sandbox" },  runToken,});const handle = await showFunnelRun(container, {  client,  onAuthenticate: async () => obtainCurrentUserCustomerToken(),  onCheckout: ({ url }) => window.location.assign(url),  onOpenUrl: ({ url }) => window.location.assign(url),  onError: reportError,});// Keep the host's original bootstrap across navigation and payment returns.// Destroy removes UI; close is an explicit server command.return { handle, client, destroy() { handle.destroy(); client.reset(); } };}// On route leave: flow.destroy(). On explicit dismissal: await flow.handle.close().