Documentation Index
Fetch the complete documentation index at: https://tech.illasoft.com/llms.txt
Use this file to discover all available pages before exploring further.
kira-sg-billing 是独立 Hono 服务,集中处理所有 Stripe 交互:订阅、Booster 购买、Portal、Webhook。2026-02 从 kira-be 拆分,支持 US/SG 双区域 Stripe 账户,按用户 billing_region 路由。
kira-be 侧保留的只有「使用时的 credit 扣减」(src/ai/libs/billing.ts)和「用户删除时的 Stripe customer 清理」,所有订阅和支付流程都走本服务。
部署形态
| 项 | 值 |
|---|
| 平台 | Fly.io |
| 区域 | sjc + fra 双机 |
| 每机资源 | 512 MB / 1 CPU |
| 端口 | 8081 |
| 入口 | bun run dev(实际:src/bootstrap.ts → src/index.ts) |
| 公网域名 | billing.kira.art |
路由清单
| Method + Path | 用途 | 认证 |
|---|
POST /billing/checkout | 创建订阅 Checkout Session | JWT |
POST /billing/checkout/booster | 一次性 Booster 购买 | JWT |
POST /billing/portal | 客户自助 Portal | JWT |
POST /billing/webhook/us | US Stripe Webhook | Stripe 签名 |
POST /billing/webhook/sg | SG Stripe Webhook | Stripe 签名 |
POST /billing/daily | 日常任务(预留) | 密码保护 |
GET /health | 健康检查 | 无 |
双区域路由
resolveRegion(user) — 结账/Booster 时的区域选择
if (user.plan !== 'free' && user.billing_region) {
→ 用已有的 billing_region(US 或 SG)
} else {
→ 默认 SG(2026-02 后新订阅默认走 SG)
}
resolvePortalRegion(user) — Portal 时的区域选择
基于活跃订阅历史:如果用户曾在 US 订阅过,Portal 回到 US;否则 SG。避免用户的历史发票被隐藏。
Plans 与 Credits
| Plan | Credits / 月 | 变体 |
|---|
free | 0 | — |
basic | 1000 | basic_year |
pro | 2000 | pro_year |
max | 6000 | max_year |
年付变体按月度 credits 等比折算(pro_year = 24000/年)。
Boosters(一次性)
| SKU | Credits |
|---|
booster_small | 100 |
booster_medium | 300 |
booster_large | 1000 |
Booster 充到 user_profiles.addon_credits,与月度 credit 分开结算;月度 credit 每月重置,addon_credits 持续累积直到用完。
Stripe Webhook 处理
关键事件
| 事件 | 处理 |
|---|
customer.subscription.created | 记录 customer_id / customer_id_sg,设 billing_region |
customer.subscription.updated | 升/降级处理;月度 credit 按新 plan 补发/保留 |
customer.subscription.deleted | 降级到 free,保留 customer_id 便于重订阅 |
invoice.payment_succeeded | 发放月度 credit(覆盖上月余额) |
invoice.payment_failed | PostHog 事件 + 降级流程 |
checkout.session.completed (mode=payment) | Booster 充值到 addon_credits |
Webhook URL 配置
在 Stripe Dashboard 为两个账户分别配置:
- US:
https://billing.kira.art/billing/webhook/us,签名 secret 存 STRIPE_WEBHOOK_SECRET
- SG:
https://billing.kira.art/billing/webhook/sg,签名 secret 存 STRIPE_WEBHOOK_SECRET_SG
数据模型(user_profiles)
| 字段 | 说明 |
|---|
customer_id | US Stripe customer ID |
customer_id_sg | SG Stripe customer ID |
billing_region | "us" 或 "sg",未订阅为 null |
plan | 当前 plan slug |
credit | 月度 credits(下月重置) |
addon_credits | Booster credits(持续) |
last_credit_issued_at | 最近一次月度 credit 发放时间(防重复) |
环境变量
# Stripe(US)
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
# Stripe(SG)
STRIPE_SECRET_KEY_SG
STRIPE_WEBHOOK_SECRET_SG
# 共用
SUPABASE_URL
SUPABASE_KEY # service_role
POSTHOG_API_KEY
SENTRY_DSN
NODE_ENV
与 kira-be 的协作
职责分工
| 动作 | 发生位置 |
|---|
| 创建订阅 / Booster | kira-sg-billing(Stripe Checkout) |
| 发放 credits(webhook 触发) | kira-sg-billing(UPDATE user_profiles) |
| 使用时扣减 credits | kira-be src/ai/libs/billing.ts |
| Plan 准入检查 | kira-be checkVideoEligibility / checkMusicEligibility |
| 删除用户时清 Stripe customer | kira-be src/hono/user/index.ts:33-88 |
kira-be 的 credit 扣减
// kira-be/src/ai/libs/billing.ts
minusCredit(userId, cost, taskId?)
→ Supabase RPC: deduct_credits(p_user_id, p_cost, p_task_id?)
// Postgres 原子扣减,先扣 credit 再扣 addon_credits
RPC 函数 deduct_credits 用行锁保证并发安全,且写入 credit_deductions 表留审计。
迁移对照(从 kira-be 搬走的)
| 原 kira-be 路由 | 现 kira-sg-billing 路由 | 状态 |
|---|
POST /billing/checkout | POST /billing/checkout | 已迁 |
POST /billing/checkout/booster | POST /billing/checkout/booster | 已迁 |
POST /billing/portal | POST /billing/portal | 已迁 |
| Stripe webhook(原统一端点) | /billing/webhook/us + /webhook/sg | 分区改造 |
kira-be 的 src/hono/billing/ 目录如还存在,应标为 deprecated 或删除。
最近重要变更
c716e47 端口从 8080 → 8081(避免与 kira-be 冲突)
8c9fd92 SG 区域配置完善
111f29a 用户 profile not found 时的 500 → 404 修正
- 新增 GitHub Actions Fly deploy workflow
Caveat
- webhook 端点必须区分 US / SG;用错签名 secret 会导致 Stripe 401
- 升级 plan 时 credit 立即补足到新 plan 月度上限,但不会重置
addon_credits
billing_region 一旦设置,除非用户完全退订后重新订阅到另一侧,否则不再切换