关于 freemius-checkout
freemius-checkout 是面向 React 前端的 Freemius 结账集成技能,用于呈现订阅与一次性购买选项并打开结账覆盖层。价格和结账上下文由后端提供,购买结果也由后端验证与同步。
主要功能
- 01订阅月付与年付价格表
- 02一次性与额度充值价格表
- 03结账覆盖层与自定义按钮
- 04付费墙展示对应购买选项
- 05购买后向后端同步权益
适用场景
- 01在 React 应用中呈现订阅方案
- 02为用户提供一次性或额度充值购买
- 03按功能门控状态显示付费墙
- 04购买后由后端核验并更新权益
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ freemius/ freemius-ai --skill freemius-checkout安装后在 Claude Code 中输入/freemius-checkout确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/checkout-provider.mdcustom-buttons.mdpaywall.mdpricing-tables.mdvite-hono-porting.md详细介绍
Freemius Checkout
在 React 前端渲染定价信息并打开 Freemius Checkout 覆盖层,让用户购买任意类型的商品——定期订阅或**一次性/充值(积分)**购买,并在服务端同步结果。
来源:React Starter Kit Checkout 组件 https://freemius.com/help/documentation/saas-sdk/react-starter/components/#checkout-components
框架说明。 本 Skill 中的示例基于 Vite/Hono,但并不要求使用该技术栈:服务端(由
freemius-core管理)适用于任何支持 Web 标准Request/Response路由的框架;React Starter Kit 组件适用于 React 19+、shadcn UI 和 Tailwind,可通过npx shadcn@latest add https://shadcn.freemius.com/all.json安装。如果应用未使用 shadcn/React,请下载 Starter Kit,并将其作为参考,在应用自己的框架中实现相同的 UI。优先复用应用现有组件(按钮、输入框、卡片、对话框),而不是重新创建基础组件——将 Starter Kit 的结构和行为移植到应用自己的设计系统中,而非引入新的基础组件。
沙盒。 请在沙盒模式下构建和验证——这种结账模式接受测试信用卡(不是折扣;无需使用或涉及优惠券)。该模式通过专用的
FREEMIUS_SANDBOX环境变量接入(绝不使用NODE_ENV),由freemius-core管理;服务端创建的结账已包含沙盒标志,本 Skill 会继承该标志。
黄金规则(不得违反)
- 浏览器中运行的只有覆盖层。 它使用由服务器创建的序列化结账对象进行初始化(来自
freemius-core的checkout.serialize())。secretKey/apiKey绝不会传到客户端。 - 浏览器永远不能信任自身。 覆盖层购买成功后会触发回调,将载荷 POST 到后端;后端会从 Freemius 重新获取权威购买记录,并更新或插入权益。付费墙在服务器端强制执行(HTTP 402);UI 门控仅用于改善用户体验。
- ⛔️ 覆盖层同步与托管重定向是两个独立端点,使用不同的载荷契约——绝不要将它们合并。 覆盖层的
purchaseCompleted回调会将 JSON 请求体 POST 到专用的POST /api/purchase;该处理程序从覆盖层载荷中读取license_id,并调用syncEntitlementByLicenseId(licenseId)。托管返回使用另一个独立端点GET /api/checkout/redirect,在已签名的查询字符串上运行freemius.checkout.processRedirect(url, PUBLIC_URL)(参见freemius-core→ purchase-processing)。不要通过freemius.checkout.request.createProcessor(...)/processRedirect(...)处理覆盖层 POST,也不要将syncEndpoint指向/api/checkout/redirect?action=process_purchase。这些 SDK 路径要求提供已签名的重定向,或使用覆盖层载荷不包含的格式提供license_id,因此 POST 会返回400 "License ID is required in the request body";前端的.catch()会吞掉此错误,而user_fs_entitlement对所有用户都将保持空值,尽管 UI 看起来正常。(createProcessor属于客户门户的无头端点,而非覆盖层购买同步——不要在这里借用它。)
- ⛔️ 覆盖层同步与托管重定向是两个独立端点,使用不同的载荷契约——绝不要将它们合并。 覆盖层的
- 定价数据来自一个服务器端点。 前端从后端获取方案、价格和每个方案的结账上下文(
GET /api/checkout/pricing);绝不会直接调用 Freemius API。 - 覆盖所有购买模式。 订阅使用计费周期切换开关(
monthly/annual);一次性/充值方案通过billing_cycle: 'lifetime'打开覆盖层。两种模式都要渲染——不要将演示版中仅支持订阅的路径写死。 - 绝不要要求开发者重新说明订阅模式。 定价表会从
GET /api/checkout/pricing自动获取方案和价格,其数据来源为 Freemius Dashboard。Dashboard 是方案和定价的唯一可信来源——根据该数据检测购买模式(仅有 lifetime 的方案 →Topup;monthly/annual 方案 →Subscribe);不要在构建提示词或收集信息的问题中将其硬编码。 - 购买后的确认体验由应用负责。 打开覆盖层时设置
show_confirmation_dialog: false(跳过 Freemius 的确认弹窗),改为显示应用内成功状态——参见应用内成功消息。在同一个onPurchase处理程序中,隐藏付费墙,并清除受门控操作因 402 产生的任何过时门控错误/结果,然后重新检查权益——否则受门控界面会一直显示“需要订阅”/“升级”消息,直到重新加载。 - 购买界面必须识别用户权益。 绝不要向已订阅用户显示普通的 Subscribe 按钮(普通订阅结账会创建第二个并行订阅,导致重复扣费);他们应使用服务器构建的许可证升级结账路径(获取许可证 +
setLicenseUpgradeByKey——参见freemius-customer-portal;不要使用checkout.create({ licenseId }),它会对续订中的许可证返回 500)。对拥有有效无限量订阅的用户隐藏积分充值选项;标记已拥有的方案;对订阅用户注明或隐藏 Lifetime。参见 references/pricing-tables.md →“Entitlement-aware rendering”。
组件
将这些组件移植为可编辑的 shadcn/ui 风格 React 源码(而非不透明的包)。 每个组件都有专门的参考文档——阅读所需的文档:
| 组件 | 功能 | 参考 |
|---|---|---|
CheckoutProvider | 持有一个覆盖层实例;提供 useCheckout() | references/checkout-provider.md |
Subscribe | 订阅定价表(月度/年度切换) | references/pricing-tables.md |
Topup | 一次性/点数定价表(lifetime 计费) | references/pricing-tables.md |
Paywall | 在功能受限时显示相应定价表的覆盖层 | references/paywall.md |
| 自定义按钮 | 调用 useCheckout().open(...) 的任意元素 | references/custom-buttons.md |
Next.js→Vite/Hono 的移植范围(环境变量、@/@shared 别名、两个服务端端点)见
references/vite-hono-porting.md。
快速开始
1. 安装前端 SDK
npm install @freemius/checkout
2. 使用服务端创建的结账信息初始化提供程序
// The base checkout is created server-side (freemius-core) and exposed at
// GET /api/checkout → { options, link, baseUrl }. Load it, then wrap the UI.
const checkout = await getBaseCheckout(); // CheckoutSerialized
<CheckoutProvider
checkout={checkout}
syncEndpoint="/api/purchase" // POST target for the success callback
fetcher={portalFetch} // attaches auth headers
onPurchase={refreshEntitlement} // re-fetch entitlement after success
>
<Subscribe />
<Topup />
</CheckoutProvider>;
3. 为选定方案打开覆盖层
const checkout = useCheckout();
// Subscription (with billing cycle):
checkout.open({
plan_id: planId,
billing_cycle: 'annual',
show_confirmation_dialog: false, // default — the app shows its own success state
});
// One-off / top-up (consumable credits):
checkout.open({
plan_id: planId,
billing_cycle: 'lifetime',
show_confirmation_dialog: false,
});
open() 会将你的参数与基础选项合并,并接入 purchaseCompleted 回调;该回调会向 syncEndpoint 发送 POST 请求。
在每次调用 open() 时,将 show_confirmation_dialog: false 设为默认值(或将其写入提供程序):这样会跳过 Freemius 自带的购买后确认弹窗,改由应用显示成功状态——体验更好,也只需一个弹窗。由于 Freemius 弹窗已关闭,应用必须自行显示确认信息(见下一节)。
应用内成功提示(购买后)
当 show_confirmation_dialog: false 时,应用需在两种购买流程中自行提供确认体验:
- 覆盖层流程 —
purchaseCompleted→ 提供程序向/api/purchase发送 POST 请求(同步权益)→ 触发onPurchase。使用此回调来(1)重新获取权益/余额;(2)显示可见的成功状态——例如 Toast/横幅,订阅时显示**“购买完成——已解锁高级功能”,充值时显示“点数已添加到账户”**。绝不要在不提示用户的情况下完成购买。 - 托管重定向流程 — Freemius 会将用户重定向回你的购买重定向处理程序(
freemius-core→ purchase-processing);其结果页面必须显示相同的成功提示。
接线方式见 references/checkout-provider.md → “应用内成功提示”。
工作原理
Server (freemius-core) Frontend (this Skill) Freemius
GET /api/checkout ───────────────▶ getBaseCheckout()
{ options, link, baseUrl } ──────▶ new Checkout(options,…) (one instance)
GET /api/checkout/pricing ───────▶ <Subscribe/> + <Topup/> tables
user clicks "Subscribe"/"Buy"
useCheckout().open({plan_id, billing_cycle}) ─▶ overlay
user pays ───────────────────────────────────▶ license/payment
POST /api/purchase ◀────────────── purchaseCompleted(data) callback
(re-fetch + upsert entitlement)
402 on gated feature ────────────▶ <Paywall/> shows the right table
托管结账回退方案
如果无法加载 @freemius/checkout(或想要最简单的方案),定价响应中的每个方案也都包含托管的 checkoutLink。将浏览器导航到该链接;Freemius 会将用户重定向回你的购买重定向处理程序(由 freemius-core → purchase-processing 处理)。为了获得更好的体验,优先使用覆盖层;同时保留该链接作为平稳降级方案。
仪表板配置(交由人工处理)
在 Freemius Developer Dashboard(https://dashboard.freemius.com/)中:
- Product → Plans → 定义各项计划及其定价。仅设有
lifetime 价格(无月付/年付)的计划会显示在
Topup表格中;设有月付/年付价格的计划会显示在Subscribe中。 - 充值前提条件: 只有当产品中存在仅限 lifetime 的计划时,一次性/充值模式才会显示。如果制作方希望提供充值(或你需要演示/验证该模式),请让他们先创建一个计划——将此视为受理/前提条件问题,并在
oneoff返回空内容时妥善隐藏Topup表格。 - Checkout → appearance / behaviour → 可选的叠加层品牌设置。
- Webhook 和重定向 URL 的设置由
freemius-core负责。
购买所授予用户的权益(功能解锁、额度余额)由服务端处理,并由 freemius-core 负责——充值额度请参阅
freemius-core → entitlement-logic.md →“基于用量/额度计量
(充值模式)”(余额存放在单独的、以用户为键的表中,绝不能扩展 user_fs_entitlement)。