跳到正文
Indiana
控制台
文档
章节目录 / 代码示例

代码示例

完整接入文件与常见业务场景,按你的项目替换登录校验和配置。

下载接入示例
SDK 0.0.1更新于

Next.js:身份与客户会话

按快速开始的顺序使用这些文件。唯一需要接入的宿主函数是你已有的 getSignedInUser;必须验证真实登录会话。

.env.localShell
# 复制你的 App → Sandbox → SDK 接入中的配置GROOVE_BASE_URL=https://your-grow-host.exampleGROOVE_PROJECT_ID=your-project-idGROOVE_SECRET_KEY=your-sandbox-server-key # 浏览器可见;不包含 Secret KeyNEXT_PUBLIC_GROOVE_BASE_URL=https://your-grow-host.exampleNEXT_PUBLIC_GROOVE_PROJECT_ID=your-project-id

刷新会话与退出登录

在已有 grow 客户端旁增加这个模块。只在 UNAUTHENTICATED 时刷新一次;失败向上抛出。退出登录或切换账号调用 disconnectSession,中止宿主会话获取和 SDK 旧请求;再调用自己的登出接口。写入重试沿用原 requestId / 幂等键。

lib/groove-session.tsTypeScript
import { GrooveError } from "@tinkerlandai/indiana-sdk";import { grow } from "./groove-client"; // 向你自己的服务端取新 Token(会话接口需要你的登录态)let generation = 0;let pending: { controller: AbortController; promise: Promise<void> } | undefined; function refreshSession(): Promise<void> {  if (pending) return pending.promise;  const expected = generation;  const controller = new AbortController();  const promise = (async () => {    const response = await fetch("/api/groove-session", {      signal: controller.signal, cache: "no-store", credentials: "same-origin",    });    if (!response.ok) throw new Error("用户未登录");    const { customerToken } = await response.json();    if (expected !== generation)      throw new GrooveError("IDENTITY_CHANGED", "Identity changed during refresh");    if (typeof customerToken !== "string") throw new Error("Invalid session");    grow.identify(customerToken);  })();  pending = { controller, promise };  const clear = () => { if (pending?.promise === promise) pending = undefined; };  void promise.then(clear, clear);  return promise;} // Token 过期(默认 10 分钟)时重新签发一次再重试export async function withSession<T>(call: () => Promise<T>) {  const expected = generation;  try {    return await call();  } catch (error) {    if (!(error instanceof GrooveError) || error.code !== "UNAUTHENTICATED")      throw error;    if (expected !== generation) throw new GrooveError("IDENTITY_CHANGED", "Identity changed");    await refreshSession();    if (expected !== generation) throw new GrooveError("IDENTITY_CHANGED", "Identity changed");    return call();  }} // 你的用户退出登录时:先丢弃 Token,再走你自己的登出export function disconnectSession() {  generation++;  pending?.controller.abort();  pending = undefined;  grow.reset(); // 清除客户 Token,中止进行中的请求} export async function signOut() {  disconnectSession();  await fetch("/api/logout", { method: "POST" });}

从已发布套餐创建结账

这是浏览器展示片段;先按 Funnel 接入页实现 /api/groove-funnel 后端及宿主回调。发布当前商品并配置 Sandbox 支付绑定,再发布引用固定商品版本的 Funnel 结账节点。宿主后端保留原启动输入;返回支付页后恢复同一运行。超时重试原命令,不直接创建另一笔结账。

lib/show-checkout.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().

服务端:执行任务前消费积分

消费函数接受后端已经验证并关联的 customerId。jobId 是你的稳定任务 ID,重试沿用同一个幂等 Key。在服务端环境变量 GROOVE_GENERATIONS_ENTITLEMENT_ID 填写generations 功能的稳定 ID(不是 key 或商品版本 ID)。只有 accepted 为 true 才执行受保护的任务;失败退款或长任务预占见积分指南。

lib/authorize-generation.tsTypeScript
import { groove } from "./groove-server"; const configuredEntitlementId = process.env.GROOVE_GENERATIONS_ENTITLEMENT_ID;if (!configuredEntitlementId) throw new Error("Missing GROOVE_GENERATIONS_ENTITLEMENT_ID"); export async function authorizeGeneration(customerId: string, jobId: string) {  const { receipt } = await groove.mutateBalance({    customerId,    command: {      operation: "consume", entitlementId: configuredEntitlementId,      quantity: "1", properties: {},      idempotencyKey: `job:${jobId}:consume`,    },  });  if (!receipt.accepted) throw new Error(receipt.reason ?? "Credits unavailable");  return receipt;}

浏览器:展示已发布的 Funnel

先发布 Funnel 及入口配置。宿主后端调用 start,浏览器通过运行凭据展示。reportError 处理失败;页面离开或身份切换前销毁并丢弃旧运行。

相关:查看编辑器与客户预览

show-upgrade.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().