在控制台设计每个页面,再通过流程画布连接按钮、条件、认证、购买与完成节点。返回沿实际访问路径运行;默认选项不等于用户作答。发布版本固定页面文档与流程结构。
预览会模拟认证与购买。真实 Funnel 的购买成功路径只有在付款和权益均确认后才进入。
连接独立页面、条件分支、认证和购买节点。
在控制台设计每个页面,再通过流程画布连接按钮、条件、认证、购买与完成节点。返回沿实际访问路径运行;默认选项不等于用户作答。发布版本固定页面文档与流程结构。
在自己的后端使用 createFunnelStartClient().start() 启动运行,必须带客户会话:已登录用户用客户会话;未登录访客先 createGuestCustomer,再用 resumeGuestSession 取得游客会话。24 小时 runToken 用于浏览器运行。游客登录认领后,运行通过 link 继续,分组不变。
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().destroy() 只清 UI,close() 另外提交关闭事件。此页描述当前源码协议,SDK、Indiana 部署与数据库迁移须匹配;npm 已发布 0.0.1 beta,不代表后续工作区版本已发布。宿主负责真实会话与持久化 start 输入,切换账号须关闭并丢弃旧运行。
相关:服务端回执、任务与恢复
import { createFunnelStartClient, createGrooveServer, createGuestToken,} from "@tinkerlandai/indiana-sdk/server"; const options = { baseUrl: process.env.GROOVE_BASE_URL!, apiKey: process.env.GROOVE_SECRET_KEY!, scope: { projectId: process.env.GROOVE_PROJECT_ID!, environment: "sandbox" },} as const;const groove = createGrooveServer(options);const funnels = createFunnelStartClient(options); export async function POST(request: Request) { if (request.headers.get("origin") !== process.env.APP_ORIGIN) return new Response("Forbidden", { status: 403 }); const session = await requireHostSession(request); // Every run has a customer: the signed-in customer, or a guest kept in this session. let customerToken: string; if (session.grooveCustomerId) { ({ customerToken } = await groove.createCustomerSession(session.grooveCustomerId)); } else { session.grooveGuestToken ??= createGuestToken(); await session.save(); await groove.createGuestCustomer({ guestToken: session.grooveGuestToken }); ({ customerToken } = await groove.resumeGuestSession({ guestToken: session.grooveGuestToken })); } // Persist before the first request. Retries reuse the whole original input. if (!session.funnelStart) { session.funnelStart = { source: { type: "funnel", id: process.env.GROOVE_FUNNEL_ID! }, requestId: crypto.randomUUID(), locale: session.locale, commerce: { provider: "stripe", currency: "USD" }, }; await session.save(); } const result = await funnels.start({ ...session.funnelStart, customerToken }); session.grooveRunToken = result.runToken; await session.save(); return Response.json({ runToken: result.runToken }, { headers: { "Cache-Control": "private, no-store" }, });}obtainCurrentUserCustomerToken 先完成宿主登录,再向自己的后端获取当前用户 Token;取消返回 null。requireHostSession、session.save 与 reportError 是宿主需要提供的接入点。保留原启动输入,付款返回后重新展示相同运行。
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().