跳到正文
Indiana
控制台
章节目录 / 购买与 Stripe Checkout

购买与 Stripe Checkout

预览、明确确认,并通过原操作读取验证后的付款结果。

SDK 示例对应当前 API。核对匹配版本与 HTTP 接入方式

API v1更新于

开始前准备

完成 Customer 接入;连接当前 App / Sandbox 的 Stripe,准备固定 Price Product 和对应权限。首次接入先跑独立购买,不需要 Funnel。

预览并确认购买

配置当前 App/Sandbox 的 Stripe 连接和固定 Price Product。使用匹配部署的 SDK,Key 需要 billing:write,读取结果另需 billing:read/entitlements:read。下例在可信后端执行,customerId 来自自己的已验证登录身份。

previewAttach 只报价并预留 Coupon;attach 是明确确认。autumn 模式由 Indiana 使用商家的已验证 restricted Key 创建 Stripe Checkout,返回 checkoutUrl。record_only 模式不替商家调用 Stripe,返回原 execution 指令,由商家后端执行。两者都只根据验签 Webhook 写入最终购买事实。

取消未付款报价时使用 attach({ customerId, operationId, intent: 'cancel' }),释放预留持券。刷新/读取原操作不确认付款。响应丢失保留原 requestId/operationId,显示 pending,重新读取或重试原请求。

server/purchase.ts · current SDKTypeScript
// Persist the complete input and requestId before previewing.const { quote } = await indiana.previewAttach({  customerId: verifiedCustomer.id,  requestId: retainedRequestId,  plans: [{ productVersionId: selectedProductId }],  provider: "stripe", currency: "USD",  // offerId: heldCouponGrantId,});// Show quote and obtain explicit customer confirmation before attach.const { operation } = await indiana.attach({  customerId: verifiedCustomer.id, operationId: quote.operationId,});if (operation.status === "complete") {  await refreshCustomerHoldings();} else if (operation.checkoutUrl) {  return { checkoutUrl: operation.checkoutUrl };} else if (operation.executor === "merchant" && operation.execution) {  // Execute only on your backend using the original operation instructions.  return await executeMerchantCheckout(operation);}// After returning from checkout, read the original operation; do not create another.await indiana.readSubscriptionOperation({  customerId: verifiedCustomer.id, operationId: quote.operationId,});

record_only: Stripe Checkout

原样保留 execution.metadata,订阅复制到 subscription_data.metadata;一次性付款开启 invoice_creation 并复制到 invoice_data.metadata。后端核对 Stripe account、Price、Coupon 条款、试用/支付方式、税和 expiry;不要把服务端 Key 或可篡改执行参数交给浏览器。配置的 Webhook 签名密钥必须属于同一账户和环境。

record_only 不出售用量后付费的 Price(周期结束时按实际用量收费):这部分用量要在续费账单生成时由 Indiana 加到 Stripe 账单上,只有 autumn 模式能做到。含这类 Price 的 Product 报价返回 NOT_CONFIGURED,Offering/Paywall 中显示为不可购买(reason 为 commercial_runtime_pending)。预付包在购买时收费,可以在 record_only 下出售,但每个 Price 须先绑定你的 Stripe Price。

server/stripe-checkout.ts · fragmentTypeScript
// Re-read this operation on your backend for the authenticated Customer.// stripe is your server-only Stripe client for execution.accountId.const execution = operation.execution;if (operation.executor !== "merchant" || !execution) throw new Error("Wrong executor");if (execution.lineItems.some((item) => !item.price)) throw new Error("Price binding missing");const cardless = quote.lines.some((line) => line.trial && !line.trial.cardRequired);const session = await stripe.checkout.sessions.create({  mode: execution.mode,  ...(quote.automaticTax ? { automatic_tax: { enabled: true } } : {}),  line_items: execution.lineItems.map(({ price, quantity }) => ({ price: price!, quantity })),  discounts: execution.discounts.map(({ coupon }) => ({ coupon })),  metadata: execution.metadata,  client_reference_id: operation.operationId,  expires_at: Math.floor(Date.parse(execution.expiresAt) / 1000),  success_url: configuredReturnUrl,  cancel_url: configuredReturnUrl,  ...(execution.mode === "subscription"    ? { subscription_data: {        metadata: execution.metadata,        ...(execution.trialPeriodDays ? { trial_period_days: execution.trialPeriodDays } : {}),        ...(cardless ? { trial_settings: { end_behavior: { missing_payment_method: "cancel" } } } : {}),      }, ...(cardless ? { payment_method_collection: "if_required" } : {}) }    : { invoice_creation: { enabled: true, invoice_data: { metadata: execution.metadata } },        payment_intent_data: { metadata: execution.metadata } }),}, { idempotencyKey: `checkout:${operation.operationId}` });return { checkoutUrl: session.url };

支持范围与结果

当前购买入口以 Sandbox、Stripe、固定 Price 为范围。Live、原生购买、Entity/append、任意席位/预付数量、用量 Price 和已有订阅变更可能返回明确的 NOT_CONFIGURED/SERVICE_UNAVAILABLE;不要改为其他 Product 或假定成功。SDK 内存在方法不代表部署已经开放该能力。

trial 不等于 paid Invoice;一次性购买不产生自动续费 Subscription。只读 operation 的已验证状态,再刷新 readHoldings。付款回跳、onComplete 或浏览器成功消息都不能替代后端授权。

对应 HTTP 接口

可以在已完成的 HTTP 接入上调用这些操作。每页参考列出完整请求、响应和凭据要求;有匹配 SDK 时,也可使用本页 SDK 示例。

操作与权限
操作HTTP权限
previewAttachPOST /api/v1/subscriptions/preview-attachbilling:read
attachPOST /api/v1/subscriptions/attachbilling:write
readSubscriptionOperationPOST /api/v1/subscriptions/operations/readbilling:read

操作与权限

  • previewAttach

    HTTP
    POST /api/v1/subscriptions/preview-attach
    权限
    billing:read
  • attach

    HTTP
    POST /api/v1/subscriptions/attach
    权限
    billing:write
  • readSubscriptionOperation

    HTTP
    POST /api/v1/subscriptions/operations/read
    权限
    billing:read

相关:previewAttach·attach·readSubscriptionOperation

完成后检查

预期结果

用户看到服务端报价并明确确认;保留 operationId,根据验证后的操作状态刷新权益,支付回跳本身不算成功。