计费

受开关控制的 Creem 结账、权益门控与许可证激活。

计费是一个默认关闭的 受开关控制 功能。保持 VITE_BILLING_ENABLED=false 时,结账操作、权益读取、功能门控和完整的 计费界面都会保持关闭,因此此模板同样可用于不销售订阅的桌面应用。

只有在 Supabase、计费迁移、Creem 产品和 Edge Function 密钥均已配置后, 才应启用它。服务商侧的配置见 Creem 配置

开关

开关默认值作用
VITE_BILLING_ENABLEDfalse结账、权益门控和 /billing 的总开关。
VITE_BILLING_LICENSE_MODEfalse显示可选的许可证密钥激活卡片。

以下内容均假定 VITE_BILLING_ENABLED=true

价格配置

shared/price-config.ts 是渲染器侧的唯一价格映射,定义了三个层级:

层级产品形态产品环境变量
Free无 Creem 产品
Pro月付和年付订阅VITE_CREEM_PRODUCT_PRO_MONTHLYVITE_CREEM_PRODUCT_PRO_YEARLY
Lifetime一次性购买VITE_CREEM_PRODUCT_LIFETIME

产品 ID 是公开的构建配置,不是凭据;Creem API 密钥只能存放在 Supabase Edge secrets 中。planTierForProductId() 将已知产品 ID 映射为 prolifetime;缺失或未知 ID 一律映射为 free,所以未配置或过期的产品不会 错误解锁付费功能。其结构与 soar-next 的价格配置一致,方便同时维护 Web 和桌面产品目录的团队。

结账与权益流程

购买在系统浏览器中完成:

  1. 已登录用户在 /billing 选择付费方案。
  2. startCheckout(productId) 调用经过 JWT 验证的 create-checkout Edge Function。
  3. 该函数创建 Creem Checkout,并新增一条 status = 'paying'、带生成 order_nopayments 台账记录。
  4. 渲染器只接受 https://*.creem.io 的结账 URL,再通过 Tauri 外部打开器 启动系统浏览器。Capabilities ACL 将该打开器限定为 https://**,而不是 授予任意原生能力。
  5. Creem 调用公开的 creem-webhook。它用 service-role 更新付款记录并 upsert 用户的 subscriptions 权益读取模型。
  6. 浏览器可通过 soar-tauri://billing?status=success&orderId=… 返回,通常 会经过一个可选的托管跳转页。Billing 路由在打开时刷新;应用窗口重新获得 焦点时,store 也会刷新。

返回 URL 只是便利路径,不是付款凭据。应用只根据已验证 webhook 写入、受 RLS 保护的 subscriptions 行授予访问权限。

BILLING_SUCCESS_URL 是可选的 Edge secret。设置后,create-checkout 会附加 status=success&orderId=<orderNo>,并把 Creem 导向该托管页。模板 附带的页面会转发到 soar-tauri://billing;重新品牌化后请同步修改协议名。

权益与功能门控

src/stores/subscription-store.ts 计算由 src/lib/feature-gate.ts 使用的账号权益状态:

状态含义
disabled计费已关闭;所有功能均允许,且不读取 Supabase。
unknownAuth、网络或 RLS 查询失败;显示中性的等待状态。
free已登录用户没有订阅记录。
active权益处于 active、trialing,或已取消但仍在已付费周期内。一次性购买不会到期。
expired有记录,但已不再授予权限。

随模板提供的功能映射是可替换示例:

功能键解锁层级
export_pdfPro、Lifetime
sync_cloudPro、Lifetime
unlimited_projectsPro、Lifetime
priority_supportPro、Lifetime
custom_brandingLifetime

使用 <FeatureGate> 包裹付费 UI。它接收 featurechildren,以及可选的 fallbackpending

<FeatureGate
  feature="custom_branding"
  pending={<MutedNotice />}
  fallback={<UpgradePrompt />}
>
  <CustomBrandingPanel />
</FeatureGate>

unknown 会显示 pending,而不是付费墙。这样在临时离线或 RLS 查询失败时, 已付费用户不会短暂看到升级提示。

客户门户

管理订阅操作会调用经过 JWT 验证的 customer-portal Function。它从 Supabase token 中确定用户,只读取该用户受 RLS 限制的订阅行内 customer_id,请求 Creem portal 会话,并仅返回已验证的 https://*.creem.io URL。渲染器会在 系统浏览器打开前再次验证该 URL。

可选许可证模式

许可证模式是面向 Creem license key 产品的替代激活路径,例如人工发放或一次性 许可证:

VITE_BILLING_LICENSE_MODE=true

LicenseKeyCard 将密钥发送至经过 JWT 验证的 activate-license Function。 该函数向 Creem 验证密钥,并将同一用户的 subscriptions 行 upsert 为 active 的 one_time 权益,且写入一个很远的到期时间。之后应用只通过 OS keychain 的白名单 license-key secure slot(Rust 中的 SecureKey::LicenseKey)保存该密钥。

本地密钥本身不授予权益;Supabase 订阅行仍是门控的唯一事实来源。停用时会将该行 标为 canceled,并且即使尽力执行的服务端释放请求不可达,也会清除本地密钥。当前模板中, 取消的订阅在 period_end 之前仍拥有权益;许可证激活将该日期写为 2999,因此停用 不会立即撤销权益。如需立即撤销,请修改 Function 以结束周期(或调整权益策略), 并测试该行为。结账和许可证激活共用每个用户唯一的订阅行,因此应将许可证模式作为 明确设计的替代购买路径,而非未经产品设计就同时打开。

验证计费

  • 启用计费,并填入真实的测试环境 VITE_CREEM_PRODUCT_* ID。
  • 部署 Edge Functions 并配置 Creem secrets。
  • 登录,打开 /billing,开始 sandbox 结账。
  • 确认系统浏览器打开 creem.io 结账 URL。
  • 确认 payments.status 变为 success,且对应的 subscriptions 行变为 active
  • 通过 soar-tauri://billing 返回或重新聚焦应用,确认当前方案会刷新。
  • 若启用许可证模式,激活一个测试密钥,并在依赖本地 keychain 副本前确认订阅行已写入。

相关页面

On this page