Cloudflare D1(SQLite) · BILLING_DB(apps/server/wrangler.local.jsonc;远端待手动创建) · contexts/billing

BILLING_DB

Berth 自己的营收账本:档位与权益目录、付费订阅、账本、加密预付订单、通道事件 inbox、运维授予与权益投影。

用户身份归 USER_DB;额度的计数与拦截归各执行方(bot / strategy / social DO);用户在 HL 的金融状态归 HL L1(ADR-0083)。法币侧的订阅与扣款权威在 Stripe / Apple,本库是投影 + 对账。

12 张表 106 个字段 5 个聚合 1 个迁移 语义层已对齐 · sha256:e091c2144f7b040c

板块颜色贯穿全文

目录

「有哪些档位、每档给多少额度、标价多少」

features
权益键
plan_features
档位额度
plans
档位
prices
价格

付费订阅

「谁在哪条通道上付了什么、访问到何时」

billing_customers
计费客户
plan_subscriptions
付费订阅

账本与加密收款

「收了多少钱、退了多少、哪笔链上转账对哪张单」

crypto_orders
加密预付订单
provider_sync_cursors
拉取断点
transactions
营收账本

通道事件

「通道发来了什么、处理了没有」

provider_events
通道事件

权益

「这个用户现在是哪一档」

entitlement_grants
运维授予
entitlements
权益

全库约定

业务流程跟着流程看表怎么变

💳 Stripe 续订扣款成功

  1. webhook invoice.paid 到达,INSERT provider_events;PK 冲突即重放,直接 200。
  2. 同 batch:INSERT transactions(renewal),UPDATE plan_subscriptions 推 current_period_end 与 access_until。
    source_updated_at 不比现状新则 outcome = ignored。
  3. resolveEntitlement 重算,UPSERT entitlements 且 version + 1;UPDATE provider_events 置 processed_at / outcome。

🍎 Apple 退款

  1. REFUND 通知按 originalTransactionId 找到 plan_subscriptions。
  2. 同 batch:status = revoked、revoked_at、ended_at;INSERT transactions(refund, 负额, refunds_transaction_id)。
  3. 重算 entitlements;若无其他来源则删行回落 free。

🪙 加密预付 3 个月

  1. 下单:INSERT crypto_orders(pending),应付额 = cryptoExpectedAmount(标价, nonce),30 分钟有效。
  2. 拉收款地址账本(provider_sync_cursors 断点),按 (收款地址, 付款地址, 金额) 命中 pending 单。
  3. 同 batch:crypto_orders → credited、INSERT transactions(purchase, tx hash)、新建或延长 plan_subscriptions(crypto)、重算 entitlements。

设计要点

为什么单独一个库营收账本要永久保留、要按 user_id 跨用户对账,且与 USER_DB 的生命周期不同(注销后账本仍在)。分库后计费迁移不影响登录路径。
为什么 status 与 revoked_at 并存status 是五态状态机;revoked_at 记撤销事实供审计。CHECK 把二者绑成同一事实,不会出现矛盾行(约定 7)。
provider_mode 必须进列生产 App 也会收到 Apple Sandbox 收据(TestFlight),同一部署环境里 live 与 sandbox 数据并存。