关于 freemius-core
freemius-core 是一项将 Freemius 计费接入 JavaScript 和 TypeScript 后端的技能,指导开发者实现服务端结账、权益检查与 Webhook 同步。
主要功能
- 01检测现有配置及缺失项
- 02构建服务端结账路由
- 03通过 Webhook 同步购买与许可状态
- 04保存并检查用户权益
- 05将计费服务与路由处理分离
适用场景
- 01添加订阅或一次性购买结账
- 02根据有效权益限制功能访问
- 03同步本地权益记录
- 04接续已有的 Freemius 集成
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ freemius/ freemius-ai --skill freemius-core安装后在 Claude Code 中输入/freemius-core确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/AGENTS.sample.mdreferences/checkout.mddashboard-and-secrets.mdentitlement-logic.mdentitlement-table.mdpurchase-processing.mdvite-hono-notes.mdwebhooks.md详细介绍
Freemius Core
将 Freemius 计费接入 JS/TS 后端,让已登录的用户能够购买套餐, 并让你的应用能够可靠地回答一个问题:“这个用户现在拥有有效 权益吗?”
此 Skill 遵循 Freemius 官方 JS SDK 集成指南: https://freemius.com/help/documentation/saas-sdk/js-sdk/integration/
框架说明。 此 Skill 中的示例使用 Hono 框架编写,但 JS SDK 支持任何支持基于 Web Standard Request/Response 的路由的框架。请根据应用使用的实际 HTTP 框架调整示例。
需要上游文档时,请根据应用的技术栈选择:
- Next.js 应用 → https://freemius.com/help/documentation/saas-sdk/framework/nextjs.md
- 其他任何 JS/TS 运行时 → https://freemius.com/help/documentation/saas-sdk/js-sdk/integration.md
黄金准则(不得违反)
- 所有 Freemius SDK/API 调用都必须在服务端进行。 产品的
secretKey和apiKey绝不能传到浏览器。只有公开的 Checkout 链接/选项可以传到前端。 - 集成逻辑应放在纯 TS 服务中,并与路由/控制器分离。 将其放在类似
src/services/freemius/*.ts的文件中。路由只负责解析请求、调用服务并组织响应。 - 本地数据库是镜像,而非事实来源。 Freemius 管理计费状态。
user_fs_entitlement表是一个缓存,通过购买重定向和 webhook 保持同步。如有疑问,请使用freemius.purchase.retrievePurchase(licenseId)从 Freemius 重新获取数据。 - Webhook 是必需的。 没有它们,取消、续订和到期信息就无法同步到数据库,导致用户错误地保留或失去访问权限。
- 密钥只能来自环境变量。 绝不要硬编码密钥、API 令牌或 URL。
步骤 0 — 信息收集(首先执行)
在构建任何内容之前,根据制作者的产品推断 Freemius 的配置 ,并且只询问真正无法推断的信息。这是先检测,而非问卷调查——你仍需自行判断。
首先提供两种模式(制作者可随时切换到引导模式):
- 自主模式 — “我会尽可能自动检测,只在需要你协助时才停下来。”
- 引导模式 — “一步步带我完成,过程中随时提问。”
还要询问它应该在哪里运行——这会影响安装内容以及数据库的位置。 不要想当然;制作者的环境各不相同,有些人还希望保持机器整洁:
- 本地优先 — 安装/运行应用和本地 Postgres,在
localhost上验证,然后可选择部署。只有制作者确认可以在本地安装数据库时,才选择此项。 - 仅远程 — 不要在本地安装数据库、对本地数据库执行架构迁移,也不要启动本地开发服务器。本地构建/类型检查没有问题(不会触及数据库);应用及其数据库只在 部署目标(例如 Railway/Render/Fly)上运行,所有验证都针对 已部署的 URL 进行。
询问:“你希望我采用本地优先(安装本地 Postgres + 在 localhost 上运行),还是仅远程(不在本地安装——在你的主机上配置并运行所有内容,例如 Railway)?” 每个安装/运行/迁移步骤都必须遵照对方的回答。
还要一开始就询问使用沙盒还是正式环境——“先在沙盒中构建(默认模式——结账页面会以沙盒模式打开并接受测试信用卡,因此端到端运行免费),还是直接使用正式环境?” 验证时几乎总是应该使用沙盒。沙盒是结账模式,不是折扣:不需要也不会涉及优惠券。请将此选项连接到专用的 FREEMIUS_SANDBOX 环境变量,绝不要连接到
NODE_ENV——你会在 NODE_ENV=production 的情况下仅远程部署,同时仍使用沙盒;将两者耦合可能会误开正式环境的结账页面。只有正式上线时才将
FREEMIUS_SANDBOX=false。
a. 检测(提问前先扫描)
检查目标代码库,并将每项所需输入归类为 KNOWN / MISSING /
UNVERIFIED。对于已知的信息,绝不要再次询问。先读取现有配置
(.env、.env.example、README、现有的
services/freemius/client.ts 或环境变量模块),并复用其中已有的 ID/密钥,不要要求开发者重新粘贴。
| 查找内容 | 可了解的信息 |
|---|---|
.env / .env.example 中的 FREEMIUS_PRODUCT_ID、FREEMIUS_PUBLIC_KEY、FREEMIUS_SECRET_KEY、FREEMIUS_API_KEY、沙盒标志 | 哪些密钥已设置,哪些缺失 |
user_fs_entitlement 表 / Prisma 模型 / 迁移 | 构建模块 1 是否已存在 |
src/services/freemius/*、/api/checkout、/api/webhooks/freemius 路由 | 已搭建哪些构建模块 |
package.json 依赖 / 服务端入口(Hono、Express、Next、Fastify) | 所用框架和路由风格 |
PUBLIC_APP_URL / 部署配置 / 在线域名 | 是否已有公开 URL |
当配置中同时存在 FREEMIUS_API_KEY 和 FREEMIUS_PRODUCT_ID 时,
产品身份已确定——不要再次询问产品 ID。(SDK 客户端在构造时会传入
productId,因此它位于配置中,并非从 API 密钥推导得出。)针对应用需要的每个 套餐 ID 角色(例如
PRO_PLAN_ID、LIFETIME_PLAN_ID、CREDITS_PLAN_ID),按以下顺序解析——不要直接让开发者粘贴 ID:
- 通过 API 按名称查询 — 应用 README 会声明每个套餐的 名称 → 角色 (采用“已配置套餐”表格惯例,参见 references/dashboard-and-secrets.md → “套餐角色”)。使用 API 密钥列出产品的套餐,将每个已声明的名称与其角色匹配,并读取实际套餐 ID。
- 回退到 README 中的 ID — 如果名称有歧义、无法匹配,或无法进行 API
查询,则使用该表格“套餐 ID(回退)”列中固定的 ID(或现有
.env中的 ID)。 - 询问 — 仅针对无法通过以上两种方式解析的角色。
开始构建前,确认已解析的角色 → ID 映射。只询问确实 MISSING 的输入,并指引用户前往 Dashboard → API & Keys 获取 .env 代码片段。
b. 根据产品推导
Freemius 的设置应依据开发者的变现意图,而非固定流程。结合代码库和少量有针对性的问题进行判断:
- 要设置门槛的套餐以及其类型:订阅(月付/年付)、一次性购买 / 充值,或积分;
- 付费墙后面的功能/端点;
- Webhook URL = 已部署的源站 +
/api/webhooks/freemius; - 结账重定向 URL = 已部署的源站 + 你的重定向处理程序路径。
c. 只询问缺少的信息(一个可复制粘贴的区块)
对于仍然缺失的信息,请提供一个简洁的区块,让开发者一次性填写(他们通常已经有这些值)。对于未验证的项目,询问“这仍然正确吗?”,而不是不加确认地重新询问。按依赖顺序提出:
Run mode: local-first (install local Postgres) | remote-only (no local install; run on host)
Freemius Product ID: e.g. <your_product_id>
Plan ID(s) to sell/gate: e.g. <your_plan_id>
Public key (pk_...): SDK public key
Secret key (sk_...): SERVER-ONLY — goes in .env, never committed
API key (token): SERVER-ONLY — goes in .env, never committed
Sandbox or live?: sandbox while validating (sets FREEMIUS_SANDBOX, independent of NODE_ENV)
Monetization shape: subscription monthly/annual | one-off/top-ups | credits
Feature/endpoint to gate: e.g. POST /api/ocr
Deploy target + access: e.g. Railway project token — for remote-only, this is where it runs
Public/deployed URL: for webhook + redirect (the deployed origin; only localhost if local-first)
对于 Freemius ID/密钥,请告知开发者:前往 Freemius Developer Dashboard → Products → Settings → API & Keys,复制现成的 JS SDK .env 代码片段——其中包含产品 ID 和所有密钥,一次粘贴即可。
截图和分步说明:
从 Developer Dashboard 获取密钥。
机密/API 密钥应放在被 git 忽略的 .env 文件中,绝不提交,也绝不放在客户端(黄金规则 #1 + #5)。完整的环境变量列表及 Dashboard 中各值的位置,请参阅
references/dashboard-and-secrets.md——此处不要重复。
d. 确认计划,然后构建
重述:检测到的状态 + 刚提供的值 + 构建顺序(以下四个区块,跳过已有的部分)。暂停,等待开发者确认,然后继续。
e. 便于恢复进度
如果区块已存在(例如已有权益表、服务/路由),请扩展或跳过,不要重建。从中断处继续构建。
f. 检查点(暂停确认,不要猜测)
- 应用架构前先确认 schema——使用技术栈自带的迁移工具应用(例如,使用 Prisma 时:
prisma db push/prisma migrate;使用原始 SQL 时:运行提供的 DDL;使用其他 ORM 时:使用对应工具); - 接入购买处理和 Webhook 前,先确认 公开 URL;
- 将 Dashboard 操作步骤(Webhook URL、结账重定向)交给用户,明确暂停并要求“现在执行此操作,完成后告诉我”——请参阅下方的 Dashboard 配置(交由用户操作)。
四个构建模块
按以下顺序构建。每个模块都有对应的参考文件——按需阅读:
| 步骤 | 构建内容 | 参考文档 |
|---|---|---|
| 1 | user_fs_entitlement 表(Prisma/SQL/Drizzle) | references/entitlement-table.md |
| 2 | 服务端结账创建(POST /api/checkout) | references/checkout.md |
| 3 | 购买处理(重定向处理程序 + overlay 端点) | references/purchase-processing.md |
| 4 | 用于许可证生命周期同步的 Webhook 监听器(POST /api/webhooks/freemius) | references/webhooks.md |
快速入门
1. 安装并构建 SDK(仅限服务端)
npm install @freemius/sdk
// src/services/freemius/client.ts
import { Freemius } from '@freemius/sdk';
export const freemius = new Freemius({
productId: process.env.FREEMIUS_PRODUCT_ID!,
apiKey: process.env.FREEMIUS_API_KEY!,
secretKey: process.env.FREEMIUS_SECRET_KEY!,
publicKey: process.env.FREEMIUS_PUBLIC_KEY!,
});
// Sandbox is its OWN flag — decouple it from NODE_ENV. You will deploy to a
// remote host with NODE_ENV=production while STILL validating in sandbox, so
// `NODE_ENV !== 'production'` would wrongly send checkout to the LIVE URL.
// Default to sandbox unless FREEMIUS_SANDBOX is explicitly "false".
export const IS_SANDBOX = process.env.FREEMIUS_SANDBOX !== 'false';
⚠️ 不要将 sandbox 与
NODE_ENV绑定。 常见的初次错误:在已部署的主机上验证(此时NODE_ENV=production),会导致 sandbox 关闭,结账页面打开的是正式商店,而非 sandbox。请使用专用环境变量FREEMIUS_SANDBOX:测试时设为true/不设置,仅在正式上线时设为false。
2. 为已登录用户创建结账
const checkout = await freemius.checkout.create({
user: { email: session.user.email, firstName, lastName },
planId,
isSandbox: IS_SANDBOX,
});
const { options, link } = checkout.serialize();
// Send `link` (hosted) or `options` (overlay) to the frontend.
3. 在购买和 Webhook 时同步权益
freemius.purchase.retrievePurchase(licenseId) →
purchase.toEntitlementRecord() →
db.userFsEntitlement.upsert({ where: { fsLicenseId }, ... })。结账重定向、overlay 的 success 回调以及每个 Webhook 事件都使用相同的代码路径。请参阅
references/purchase-processing.md。
4. 限制付费功能的访问
const entitlement = await getUserEntitlement(userId); // active subscription OR valid one-off (Lifetime)
if (!entitlement) return new Response('Payment required', { status: 402 });
// ...run the premium feature...
getUserEntitlement()不只是freemius.entitlement.getActive(rows): 这个 SDK 辅助函数只返回type: 'subscription'的记录,因此会遗漏一次性 Lifetime 许可证。请自行解析一次性购买的许可证,参见 references/entitlement-logic.md。
使用 references/entitlement-logic.md 中的权益辅助函数 限制功能的访问。
心智模型
Frontend (pricing page) Backend services (plain TS) Freemius
│ POST /api/checkout ───────────▶ freemius.checkout.create() ──────────▶ checkout
│ ◀── { link, options } ────────── checkout.serialize()
│ user pays on Freemius checkout ───────────────────────────────────────▶ license created
│ redirect ?… ──────────────────▶ checkout.processRedirect()
│ purchase.retrievePurchase(licenseId)
│ upsert user_fs_entitlement ◀────────── PurchaseInfo
│ later: cancel/renew/expire ───────────────────────────────────────────▶ webhook event
│ POST /api/webhooks/freemius
│ re-sync user_fs_entitlement
│ GET premium feature ───────────▶ getUserEntitlement() → 402 or run
数据映射注意事项
SDK 使用 camelCase(fsLicenseId、fsPlanId、isCanceled);SQL
列通常使用 snake_case(fs_license_id、……)。请使用你的技术栈提供的方式处理映射:如果使用 Prisma,优先使用 @map/@@map(这样 purchase.toEntitlementRecord() 可以直接用于模型);如果使用其他 ORM/查询层,请使用其等效的列映射功能;如果使用原始 SQL,请直接使用 snake_case 命名列,并显式转换两个方向。参见
references/entitlement-table.md。
运行时与预期接口
Freemius 组件对应用技术栈的要求:
- JS SDK(
@freemius/sdk) — 可在 Node.js、Deno 或 Bun 上运行。其 API 依赖基于 Web 标准Request/Response的路由,因此任何支持该标准的框架都可以直接使用(Hono、Next.js 路由处理器、带有 Fetch 适配器的 Fastify、无服务器 Fetch 处理器,……)。如果框架不支持该标准,请编写一个小型适配器,在Request/Response之间进行转换。 - React Starter Kit — 使用 React 19+,搭配 shadcn UI + Tailwind。通过官方 shadcn CLI 安装:
npx shadcn@latest add https://shadcn.freemius.com/all.json。如果应用 不使用 shadcn/React,请将 Starter Kit 作为参考,用应用自己的框架实现 UI。
如需了解如何将 Next.js 风格的文档移植到其他技术栈(Vite + Hono)的完整示例,请参见 references/vite-hono-notes.md。
Dashboard 配置(交由人工处理)
这是 Step 0 — Intake 中标记为检查点的 Dashboard 交接事项的具体清单。部署到公开 URL 后,请在 Freemius Developer Dashboard (https://dashboard.freemius.com/)中进行配置:
- Product → Webhooks → 添加
POST /api/webhooks/freemius,订阅以下事件:license.created、license.updated、license.extended、license.shortened、license.cancelled、license.expired、license.plan.changed、license.quota.changed、license.deleted。 - Product → Plans → Customization → 将结账重定向 URL 设为你的购买重定向处理器。
完整的环境变量列表和 Dashboard 检查清单,请参见 references/dashboard-and-secrets.md。
生成 / 更新应用的 AGENTS.md
接入付费墙后,请在代码仓库根目录放置一份简短的 AGENTS.md,方便下一个 Agent(或人工开发者)找到高级功能,了解它们如何通过 Freemius 实现付费墙,并快速定位付费墙库文件。控制在约一屏内容,并保持厂商中立——不要写应用特定的叙述,只提供导航。
**请在构建结束时完成此操作:**如果 AGENTS.md 不存在,就创建它;如果已存在,则只在其中插入 / 更新带围栏的 ## Freemius paywall 部分(不要重写原作者的文件)。根据刚刚完成的实际集成填写所有方括号内容,包括实际受限路由和真实服务文件路径,不要保留占位符。
此 Skill 附带可直接复制的起始模板: assets/AGENTS.sample.md。复制后替换方括号内容。以下三个部分对应原作者的要求:
- 高级功能 — 如何识别 — 用表格列出受限功能 → 入口点(路由)→ 所使用的守卫(
requireEntitlement/requireCredits)。规则:经过付费墙中间件包装的路由属于高级功能;免费路由不带守卫。 - 通过 Freemius 实现付费墙 — 用三行说明销售 → 同步 → 限制访问的流程,另需说明“无权益时返回 402”以及“所有 SDK 调用均在服务端进行,密钥存储在环境变量中”。
- 付费墙库函数的位置 — 用表格指向实际文件:SDK 客户端、结账、权益同步与限制访问辅助函数、付费墙中间件,以及(如有使用)额度计量文件;还要列出仅限服务端使用的环境变量。
可复制粘贴的模板(放入 AGENTS.md,替换每个 [...]):
## Freemius paywall
### 1. Premium features — how to identify them
Premium features are the routes/actions gated behind a Freemius entitlement —
find them by the paywall guard they carry (§3), not by guesswork.
| Premium feature | Entry point | Gated by |
| --------------- | ----------------- | ---------------------- |
| `[feature]` | `[METHOD /route]` | `[requireEntitlement]` |
| `[feature]` | `[METHOD /route]` | `[requireCredits(n)]` |
### 2. Paywalling premium features with Freemius
1. **Sell** — the frontend opens the Freemius Checkout for a plan (sandbox mode
accepts test cards while validating).
2. **Sync** — the purchase redirect + Freemius webhooks upsert the local
entitlement mirror (Freemius stays the source of truth).
3. **Gate** — before a premium feature runs, resolve the active entitlement (or
credit balance for top-up/metered features). No entitlement → **HTTP 402**;
the frontend branches on 402 to show the upgrade UI.
All Freemius SDK calls are **server-side**; `secretKey`/`apiKey` never reach the
browser; secrets come from env vars only.
### 3. Where the paywall library functions live
| Concern | File |
| ---------------------------------------- | --------------------------------- |
| Freemius SDK client (keys, sandbox flag) | `[src/…/freemius/client.ts]` |
| Checkout creation + pricing | `[src/…/freemius/checkout.ts]` |
| Entitlement sync (redirect + webhooks) | `[src/…/freemius/entitlement.ts]` |
| Entitlement resolve + gating helpers | `[src/…/freemius/entitlement.ts]` |
| Paywall guard (402 middleware) | `[src/…/middleware/paywall.ts]` |
| Credit balance / metering (if used) | `[src/…/credits.ts]` |
Env (server-only, git-ignored `.env`): `FREEMIUS_PRODUCT_ID`,
`FREEMIUS_PUBLIC_KEY`, `FREEMIUS_SECRET_KEY`, `FREEMIUS_API_KEY`,
`FREEMIUS_SANDBOX`.