Skip to main content
FIM One ships a complete Stripe billing pipeline behind an instance access model. Private deployments leave it on No subscriptions and never see payment UI. Operators who want a catalogue pick Included + paid or Paid only and get hosted Checkout, Customer Portal, webhook-driven subscription lifecycle, and quota enforcement.
The software default is No subscriptions (access_model = off, default_token_quota = 0 meaning unlimited). Fresh installs and existing self-hosts start there. No billing UI surfaces until an admin picks a Stripe posture.

What you get

  • Three access postures: no subscriptions, included + paid, or paid only
  • Stripe-hosted Checkout — users upgrade without your code ever touching card data
  • Customer Portal — users update payment methods, download invoices, cancel — all on Stripe’s UI
  • Webhook-driven lifecycle — subscriptions provision and renew automatically; canceled paid users return to the included tier (or become unentitled in paid-only) at period end
  • Quota enforcement — token usage tracked per period; mid-stream cutoff with a structured upgrade prompt
  • Admin pages for plan CRUD and subscription monitoring

前置条件

  1. Stripe账户已启用实时模式。新加坡注册公司必须完成KYC(营业执照、董事身份证、银行账户)。审批通常需要1-3天。
  2. Stripe实时API密钥,类型为受限制(推荐使用,相比标准sk_live_***更易撤销、权限范围更小)。
  3. Webhook端点可公开访问,地址为<your-domain>/api/webhooks/stripe
  4. 银行账户用于支付。建议多币种结算(例如USD支付到USD账户),以避免非USD默认Stripe账户每笔交易产生1.5-2%的汇兑损失。

设置

1. Stripe 仪表板

创建Pro产品

  1. Catalog → Products → + Add product
  2. Name: Pro, description: 5M tokens / month, priority support
  3. Pricing: Recurring, monthly, $20.00 USD (根据您的定价策略调整)
  4. Save → 复制生成的 price_*** ID(激活后,您将使用此值更新本地 billing_plans 表)

创建受限API密钥

  1. Developers → API keys → + Create restricted key
  2. Name: fim-one production
  3. Permissions (minimum):
    • Customers: Write
    • Subscriptions: Write
    • Checkout Sessions: Write
    • Customer portal: Write
    • Prices: Read
    • Products: Read
  4. Save → copy rk_live_***

注册webhook端点

  1. Developers → Webhooks → + Add endpoint
  2. URL: https://<your-domain>/api/webhooks/stripe
  3. 要接收的事件:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  4. 保存后,点击”Reveal signing secret”→ 复制 whsec_***

配置多币种结算(推荐)

如果您的Stripe账户默认币种与您收费的价格币种不同(常见情况:SGD账户收取USD费用):
  1. Settings → Bank accounts and currencies → Add a settlement currency
  2. 选择价格币种(例如USD)
  3. 关联匹配的银行账户(例如Aspire USD虚拟账户)
  4. 保存——Stripe将USD费用直接路由到USD支付,无需外汇转换

2. 后端 .env

在生产环境的 .env 中设置这三个密钥:
完整参考请见环境变量 编辑 .env 后重启后端以使密钥生效:

3. Activate in Admin

  1. Log in as an admin
  2. Admin → System Settings → Access and billing
  3. Pick one posture (the instance is always in exactly one):
    • No subscriptions (software default) — Stripe unused. Token access is the default monthly quota (0 = unlimited).
    • Included + paid — Stripe required. New users land on the included tier; they can upgrade. Canceled paid users return to the included tier at period end.
    • Paid only — Stripe required. No included tier. Users must subscribe before they can call models. Existing included-tier users keep their quota if you switch from Included + paid.
  4. The backend validates that both STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are present when you pick a Stripe posture — if either is missing it returns 400
  5. On first activation of a Stripe posture the backend runs an idempotent setup:
    • Included + paid: seeds the included plan (slug=free) + a Pro template with an empty Price ID; sets default_plan_id; backfills users without a plan
    • Paid only: seeds the Pro template only; does not backfill
    • Copies default_token_quota onto the included plan when that plan is seeded (0 stays 0, unlimited)
  6. Switching back to No subscriptions is a pure flag flip with no data side effects. The included default plan cannot be deleted while Included + paid is active.

4. 使用您的实时价格更新Pro套餐

激活后,更新预设的Pro套餐以指向您的实时Stripe价格:
  • Admin → Billing → Plans → Pro → Edit
  • 将您的price_1***(来自第1步)粘贴到Stripe Price ID
  • 保存
或通过SQL(如果您更喜欢直接数据库访问):

5. 烟雾测试

  1. 以普通用户身份打开 /settings?tab=billing
  2. 点击升级到 Pro
  3. Stripe Checkout 打开;使用低额真实卡片完成支付(之后退款)
  4. Webhook 应该触发——在 Stripe Dashboard → Webhooks → 最近事件中验证显示 2xx 响应
  5. 订阅行出现在 subscriptions 表中;users.plan_id 切换为 pro
  6. UI 现在显示 Pro 计划 + “管理订阅”按钮

禁用账单

在管理员 → 系统设置 → 账单中,将启用 Stripe 账单开关切换为关闭。 禁用后:
  • 所有 /api/billing/* 端点返回 503
  • Webhook 端点返回 503(Stripe 将重试,然后在仪表板中显示为失败——这是正常的,如果账单永久关闭,你可以改为在 Stripe 仪表板中禁用 Webhook)
  • Plan & Billing 用户界面标签页消失
  • 管理员 → 账单导航组被隐藏
  • 配额链跳过计划层级,直接回退到 default_token_quota
数据被保留:现有的 subscriptionsbilling_plansusers.plan_id 行保持不变。重新启用将从相同状态恢复,无需迁移。

计算参考——配额和令牌数学

这是权威参考,用于决定用户被允许消费的内容、计数器何时重置以及解析链如何组合的每条数值规则。在更改定价、调整配额、构建使用情况仪表板或规划v2/v3工作之前,请阅读本文档。尚未发布的未来规则记录在其保留位置,以便贡献者知道新逻辑在何处插入。

词汇表

什么算作一个令牌

令牌消耗在LLM调用层进行计算,来自LiteLLM在每次完成时的usage对象。
  • 计算:每次模型调用的提示令牌 + 完成令牌
  • 计算:多步骤/工具使用智能体流中的每个往返(每次模型调用都是单独的扣费)
  • 计算:嵌入请求(知识库摄取、检索评分)
  • 不计算:输入已暂存但从未发送到模型的内容(例如用户丢弃的已上传文件)
  • 不计算:在到达提供商之前失败的请求(身份验证错误、速率限制预检查)
  • 缓存输入:在v1中按全价计算(不显示提供商缓存折扣)。v2可能会单独计入缓存提示令牌。

三态覆盖语义

users.token_quota 是按用户的管理覆盖。它在一列中承载三种含义: 覆盖始终优先于套餐和默认值。它的存在使管理员能够将单个用户固定在其套餐层级之上或之下,而无需接触Stripe。

Quota resolution chain — v1 (current)

For any authenticated request, the cap is computed top-down — first match wins:
Step 4 is the instance-wide cap for No subscriptions. Under Paid only, a user with no plan is unentitled (chat returns 402) even if default_token_quota is 0.

周期重置

  • 对于付费用户,quota_reset_at 镜像 Subscription.current_period_endinvoice.payment_succeeded webhook 处理程序在每次成功续期时将 tokens_used_this_period 设置为 0,并将 quota_reset_at 推进到新周期结束。
  • 对于免费用户(无 Stripe 订阅),每小时 cron 任务在以计划分配日期为锚点的日历月边界处将 tokens_used_this_period 重置为 0。
  • 周期中途的计划变更不会重置计数器——仅续期会重置。这可防止配额循环利用漏洞(“订阅 → 使用专业版配额 → 取消 → 重新订阅”)。

中流强制执行

  • 聊天调用入口的预检查:最便宜的路径,阻止用户无法承担的请求。
  • 流式传输期间,运行中的令牌计数在每个分块上重新评估。超过上限会关闭流,返回结构化终止帧,而不是网络错误。
  • 前端解释终止帧并显示 <QuotaExceededDialog>,其中包含指向 /settings?tab=billing 的深层链接。
  • 非流式响应返回 HTTP 402,响应体为 { code: "QUOTA_EXCEEDED", reset_at, upgrade_url }

账单禁用回退

system_settings.billing_enabled = FALSE 时:
  • 跳过链的第 2 步——链折叠为 override → default → unlimited
  • /api/billing/*/api/webhooks/stripe 返回 503
  • Plan & Billing 用户标签页和 Admin → Billing 导航组被隐藏。
  • 所有账单数据(订阅、套餐、users.plan_id)被保留——重新启用时从相同状态恢复,无需迁移。

保留:配额链 v2 — 团队座位

尚未发布。此处记录以便 v2 工作有已知的落地点。
当团队计划发布时:
  • Subscription.quantity 携带座位数(Stripe 原生)。
  • 用户的有效计划通过团队成员身份解析,然后回退到其个人计划:
  • 配额是按座位(每个团队成员获得完整的 monthly_token_quota),而不是共享池。共享池会导致先到先得的耗尽,对客户不利。
  • 覆盖语义保持不变——团队管理员仍然可以通过 users.token_quota = N 对单个成员进行硬限制,该限制在链中位于团队计划之上。

Reserved: quota chain v3 — native Org allocation (no Stripe)

Not yet shipped. Reserved for on-prem / enterprise deployments that allocate quota internally without paying Stripe per user.
  • New table org_quota_allocations(user_id, monthly_token_quota, org_id) distributes a parent budget across members.
  • Allocations are per user, not a shared pool — every member has a clear individual SLA.
  • Updated chain:
  • max(),而非 sum()。付费Pro用户永远不会获得少于他们已支付的配额,即使他们的组织管理员设置了较低的分配。Stripe支付的配额是神圣的。

预留:按使用量付费的信用额度余额(v3 独立维度)

尚未发布。与上述链独立的轴——信用额度是一次性充值,而非订阅层级。
  • 新表 user_credits(user_id, balance_cents, currency) ——通过 Stripe Checkout mode='payment' 充值。
  • 消费顺序:订阅配额优先,然后是信用额度余额(仅在订阅耗尽后才开始扣除信用额度)。
  • 信用额度余额不可退款(预付款行业标准)。
  • UI 同时展示两个进度条:订阅配额:已使用 4.2M / 5M + 信用额度:剩余 $7.40

Default values

Ship-time defaults — all tunable post-install except where noted.

定价模型

V1 是一个统一订阅。免费 + 专业版,按月计费,仅支持美元。 V1 范围外(有意推迟至路线图):
  • 团队计划(Stripe 座位 / subscription.quantity
  • 年度计费
  • 多币种展示
  • 优惠券 / 促销代码
  • 税务处理(Stripe Tax 集成——需要单独的合规审查)
  • 基于使用量的计量 / 超额费用
  • 按次付费信用余额(一次性充值)
查看路线图了解后续计划。

故障排除

激活端点需要同时设置 STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRET。确认它们存在于 .env 中,并且编辑后后端已重启。
要么是账单被禁用(切换开关为OFF),要么是请求签名验证失败(STRIPE_WEBHOOK_SECRET 不匹配)。检查Stripe仪表板→Webhooks→最近事件以查看实际错误正文。
checkout.session.completed webhook未到达你的后端。验证Stripe仪表板中的端点URL与 <your-domain>/api/webhooks/stripe 完全匹配,包括末尾路径。检查Webhook最近交付记录中的失败。
种子迁移写入了测试模式价格ID。激活生产账单后,通过管理员→账单→套餐→Pro→编辑,或通过直接SQL UPDATE,将Pro计划更新为使用你的实时 price_1***
在Stripe仪表板→设置→品牌中配置你的业务品牌。添加你的徽标、业务名称(例如”FIM Labs Pte. Ltd.”)和地址。Stripe会将这些应用于所有自动生成的收据和发票。
如果你的Stripe账户默认货币与收费货币不同,Stripe会在每次支付时进行转换(1.5-2%的差价)。在设置→银行账户和货币下添加匹配的结算货币,附加相同货币的银行账户,Stripe将路由相同货币的付款而无需转换。