docs(计费): 设计文档与决策记录更新为已落地
- 【技术设计】泥点三池与会员计费后端设计:余额投影改为扁平三池标量、报价请求不收周期、会员订单 product_id 编码档位与周期、充值中心改 membershipPlans,新增后台接口与 ts-rs 契约生成小节 - §10.2 实施状态由「待落地清单」改写为「已落地 + 明确不做」,注明永久余数仍是推导实现 - docs/README 索引标注两份文档已落地;decision-log 追加 2026-10-02 状态与实现期补充决断 Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
+2
-2
@@ -100,8 +100,8 @@
|
||||
|
||||
## 钱包与会员计费
|
||||
|
||||
- [双余额泥点与会员补差升级实施计划](./technical/【实施计划】双余额泥点与会员补差升级-2026-10-02.md):每日免费 / 月度 / 永久三池并列投影、按开通日自然月计期、年付 12 期与补差升级的规则细则、里程碑与验收判据;方案定稿,未实施。
|
||||
- [泥点三池与会员计费后端设计](./technical/【技术设计】泥点三池与会员计费后端设计-2026-10-02.md):可直接评审的后端 DB schema、账本与幂等、领域数据结构与 HTTP 契约;会员档位改为枚举、目录表以枚举为主键,含破坏性迁移计划与遗留清理清单;待评审,未实施。
|
||||
- [双余额泥点与会员补差升级实施计划](./technical/【实施计划】双余额泥点与会员补差升级-2026-10-02.md):每日免费 / 月度 / 永久三池并列投影、按开通日自然月计期、年付 12 期与补差升级的规则细则、里程碑与验收判据;已落地(会员购买与升级的 C 端 UI 未做)。
|
||||
- [泥点三池与会员计费后端设计](./technical/【技术设计】泥点三池与会员计费后端设计-2026-10-02.md):后端 DB schema、账本与幂等、领域数据结构与 HTTP 契约;会员档位改为枚举、目录表以枚举为主键,含破坏性迁移计划、遗留清理清单与落地状态;已落地。
|
||||
- [泥点三池以单一总额为权威](./adr/【ADR】泥点三池以单一总额为权威-2026-10-02.md):存储层保留 `wallet_balance` 单一权威总额,对外并列三池;永久泥点暂为推导余数,另记独立存储的 TODO。
|
||||
- [会员账期按开通日自然月](./adr/【ADR】会员账期按开通日自然月-2026-10-02.md):北京时间开通日锚点、自然月推进与月末夹取、年付 12 期的期数表达与取整口径。
|
||||
- [会员订单复用充值订单与补差升级幂等锚点](./adr/【ADR】会员订单复用充值订单与补差升级幂等锚点-2026-10-02.md):会员订单复用 `profile_recharge_order`、`MembershipUpgradeGrant` 账本幂等、后端只读报价与退款人工复核边界。
|
||||
|
||||
@@ -11,7 +11,13 @@
|
||||
- 决策(会员档位枚举化):会员档位身份改为 Rust 枚举 `RuntimeProfileMembershipPlan { Normal, Starter, Plus, Pro, Max }`,绝不字符串;新增 `profile_membership_plan` 目录表并**以该枚举为主键**(后台按档位改价 / 改权益);`profile_membership.tier` 与 `profile_recharge_product_config.tier` 两列删除、旧 `RuntimeProfileMembershipTier` 整体退役。会员侧无存量行,属经用户授权的破坏性变更;账本来源枚举仍只允许末尾追加 `MembershipUpgradeGrant`。
|
||||
- 决策(目录权益):目录一行一档,含月价、**独立可配置年价** `year_price_cents`、每期额度、模型权限 `Basic` / `Full`(本期只落字段、不执行拦截)、并发上限 `u32`(**哨兵 128 = 不设上限**,不用 `Option`);`Normal` = 0 点 / `Basic` / 并发 1 / rank 0。
|
||||
- 决策(目录数值):`Starter` ¥39 / 400 / ¥390 · `Plus` ¥99 / 1150 / ¥990 · `Pro` ¥299 / 3650 / ¥2990 · `Max` ¥699 / 8650 / ¥6990。
|
||||
- 权威入口:[实施计划](../../technical/【实施计划】双余额泥点与会员补差升级-2026-10-02.md);决策见 [`【ADR】泥点三池以单一总额为权威`](../../adr/【ADR】泥点三池以单一总额为权威-2026-10-02.md)、[`【ADR】会员账期按开通日自然月`](../../adr/【ADR】会员账期按开通日自然月-2026-10-02.md)、[`【ADR】会员订单复用充值订单与补差升级幂等锚点`](../../adr/【ADR】会员订单复用充值订单与补差升级幂等锚点-2026-10-02.md)、[`【ADR】会员档位以枚举为权威`](../../adr/【ADR】会员档位以枚举为权威-2026-10-02.md)。状态:方案定稿,未实施。
|
||||
- 权威入口:[实施计划](../../technical/【实施计划】双余额泥点与会员补差升级-2026-10-02.md);设计见 [`【技术设计】泥点三池与会员计费后端设计`](../../technical/【技术设计】泥点三池与会员计费后端设计-2026-10-02.md);决策见 [`【ADR】泥点三池以单一总额为权威`](../../adr/【ADR】泥点三池以单一总额为权威-2026-10-02.md)、[`【ADR】会员账期按开通日自然月`](../../adr/【ADR】会员账期按开通日自然月-2026-10-02.md)、[`【ADR】会员订单复用充值订单与补差升级幂等锚点`](../../adr/【ADR】会员订单复用充值订单与补差升级幂等锚点-2026-10-02.md)、[`【ADR】会员档位以枚举为权威`](../../adr/【ADR】会员档位以枚举为权威-2026-10-02.md)。
|
||||
- 状态:**已落地**。后端(领域模块、`profile_membership_plan` 表与播种、两处 `tier` 列删除、订单变更快照、自然月账期、6 档充点、三池余额投影、升级报价接口、后台档位接口)、契约生成(ts-rs → `packages/shared/src/contracts/generated/`)、共享组件与平台入口三池展示、AGC 壳、后台「会员档位」页与充值商品页收敛均已实现并通过门禁。明确不做:模型权限与并发上限只落字段、不提供 `availableTotalPoints`、会员购买 / 升级的 C 端 UI。
|
||||
- 实现期补充决断(未改变上述范围):
|
||||
- 余额 DTO 由 Rust 单一真源经 ts-rs 导出,前端 re-export 而非手写第二份字段列表;`u64` 标 `ts(type = "number")` 避免 `bigint` 分叉,`check:generated-bindings` 以「重新生成逐字节比对」守漂移。
|
||||
- 「合计」由前端派生(`packages/shared/src/utils/mudPoints.ts` 的 `resolveProfileMudPointBalanceView`),月度刷新时刻固定按北京时间格式化,不把第二份口径放回后端。
|
||||
- 会员订单 `product_id` 编码「档位 + 周期」(`membership-{plan}-{cycleKind}`),会员商品不进 `profile_recharge_product_config`;支付确认不做出售校验,避免付款后档位被下架导致权益丢失。
|
||||
- 后台 `rank` 不可改(升级比较基准由代码内目录决定),`normal` 档位的价格与额度由后端强制归零。
|
||||
|
||||
## 2026-10-01 游戏广场评分展示边界
|
||||
|
||||
|
||||
@@ -316,72 +316,109 @@ flowchart TD
|
||||
|
||||
## 5. HTTP 契约
|
||||
|
||||
### 5.1 余额投影 `ProfileMudPointBalanceResponse`(重做)
|
||||
### 5.1 余额投影 `ProfileMudPointBalanceResponse`(重做,已落地)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"dailyFreePoints": { "points": 20, "grantedPoints": 20, "resetsAt": "...", "dayKey": 20261002 },
|
||||
"monthlyPoints": {
|
||||
"points": 615, "grantedPoints": 3650,
|
||||
"expiresAt": "...", "resetsAt": "...",
|
||||
"cycleIndex": 3, "cycleCount": 12,
|
||||
"plan": "pro", "planTitle": "Pro 年付"
|
||||
},
|
||||
"dailyFreePoints": 20,
|
||||
"dailyFreeResetPoints": 20,
|
||||
"dailyFreeResetsAt": "2026-10-03T00:00:00+08:00",
|
||||
"monthlyPoints": 615,
|
||||
"monthlyResetsAt": "2026-11-20T09:00:00+08:00",
|
||||
"permanentPoints": 1482
|
||||
}
|
||||
```
|
||||
|
||||
- 删除:`totalPoints`(池语义的总额)、`limitedPoints`、`limitedExpiresAt`。
|
||||
- 三池并列,各自带元数据;永久泥点无到期字段。
|
||||
- 三个池子都是**扁平标量**:月度泥点的档位、期序号、期总数等元数据挂在
|
||||
`ProfileMembershipResponse`(§5.2),余额投影只负责「各池还剩多少 + 各自什么时候失效」。
|
||||
- 决策:**不提供** `availableTotalPoints`;`walletBalance`(原始总额)从用户面移除,仅后台 / 内部对账使用。
|
||||
- 需要「合计」时由前端派生(`packages/shared/src/utils/mudPoints.ts` 的
|
||||
`resolveProfileMudPointBalanceView`),不把第二份口径放回后端。
|
||||
|
||||
### 5.2 会员信息 `ProfileMembershipResponse`(改)
|
||||
### 5.2 会员信息 `ProfileMembershipResponse`(改,已落地)
|
||||
|
||||
- 新增 `plan`(枚举 token,如 `"pro"`)/ `planTitle` / `cycleKind` / `cycleIndex` / `cycleCount`。
|
||||
- 新增 `plan`(档位 token,如 `"pro"`;非会员与已到期统一为 `"normal"`)/
|
||||
`cycleKind`(`monthly` / `yearly`)/ `cycleIndex` / `cycleCount`。
|
||||
- **不提供** `planTitle`:展示标题属于目录表 `profile_membership_plan.title`,
|
||||
由充值中心的 `membershipPlans` 提供,避免同一档位在两个响应里各带一份可变标题。
|
||||
- **删除** `tier`(旧枚举整体退役,不再对外暴露)。
|
||||
- 删除 `cyclePeriodDays`(固定 30 天已被自然月账期取代)。
|
||||
- 保留 `status` / `startedAt` / `expiresAt` / `cycleStartedAt` / `cycleResetsAt` /
|
||||
`cycleGrantedPoints` / `cycleRemainingPoints`。
|
||||
|
||||
### 5.3 升级报价(新增,只读)
|
||||
### 5.3 升级报价(新增,只读,已落地)
|
||||
|
||||
`POST /api/profile/membership/upgrade-quote`
|
||||
|
||||
```jsonc
|
||||
// request
|
||||
{ "targetPlan": "pro", "targetCycleKind": "yearly" }
|
||||
// request:只给目标档位;周期沿用当前会员周期(有效期内不支持月年互换)
|
||||
{ "targetPlan": "pro" }
|
||||
// response
|
||||
{
|
||||
"currentPlan": "plus", "currentCycleKind": "yearly",
|
||||
"targetPlan": "pro", "targetCycleKind": "yearly",
|
||||
"currentPlan": "plus",
|
||||
"targetPlan": "pro",
|
||||
"cycleKind": "yearly",
|
||||
"cycleIndex": 3,
|
||||
"cycleCount": 12,
|
||||
"amountCents": 108334,
|
||||
"grantedPointsDelta": 1250,
|
||||
"monthlyBalanceAfter": 1865,
|
||||
"expiresAt": "...", "cycleResetsAt": "...", "cycleIndex": 3,
|
||||
"breakdown": { "remainingFullMonths": 6, "remainingRatioPpm": 500000 }
|
||||
"expiresAt": "...",
|
||||
"cycleResetsAt": "...",
|
||||
"remainingFullMonths": 6
|
||||
}
|
||||
```
|
||||
|
||||
- 纯读、不落库;下单时后端按同一组输入重算并落订单快照(§2.4)。
|
||||
- 周期不接受入参:`targetCycleKind` 不在契约里,月转年 / 年转月是拒绝项(到期后重新选择)。
|
||||
- `targetPlan` 严格解析进 `RuntimeProfileMembershipPlan`,未知 token 直接 4xx。
|
||||
- **唯一解析器**:档位 / 周期 / 模型权限 / 变更类型的 wire token 一律经各自的 `parse()` 解析
|
||||
(`RuntimeProfileMembershipPlan::parse` 等),返回 `None` 即拒绝;禁止在各处手写字符串匹配。
|
||||
- 日期字段是 `expiresAt` / `cycleResetsAt`(RFC3339 字符串),不带 `*Micros` 双字段。
|
||||
|
||||
### 5.4 下单(复用现有链路)
|
||||
### 5.4 下单(复用现有链路,已落地)
|
||||
|
||||
`POST /api/profile/recharge/orders { product_id, payment_channel }`。
|
||||
决策:**复用此单接口**;会员订单的 `product_id` = 目标计划 token(枚举字符串化),
|
||||
后端按当前会员状态区分购买 / 升级并重算金额。
|
||||
决策:**复用此单接口**;会员订单的 `product_id` 编码「档位 + 周期」:
|
||||
`membership-{plan}-{cycleKind}`(如 `membership-pro-yearly`),
|
||||
由 `runtime_profile_membership_product_id()` / `parse_runtime_profile_membership_product_id()` 双向解析。
|
||||
|
||||
### 5.5 充值中心 `ProfileRechargeCenterResponse`(改)
|
||||
- 会员商品**没有** `profile_recharge_product_config` 行:标题与价格都来自目录表,
|
||||
订单行按 `product_id` 现算商品快照(`build_membership_order_product`)。
|
||||
- 后端按当前会员状态区分购买(无有效会员 → 目录价)与升级(有效期内 → 补差重算),
|
||||
金额在创建订单与支付确认两处都走同一函数 `resolve_profile_membership_order_amount_cents`。
|
||||
- 支付确认时即使档位已下架,已支付订单仍按原档位结算(`resolve_profile_recharge_order_product`
|
||||
不做出售校验),避免「付款后档位被后台下架」导致权益丢失。
|
||||
|
||||
- `mudPointBalance` 用 §5.1;`membership` 用 §5.2;会员列表来自 `profile_membership_plan`(含 `plan` / 月价 / 年价 / 每期泥点 / 模型权限 / 并发上限)。
|
||||
- `walletBalance` 不在用户面暴露;会员商品行不再出现在充点商品列表里。
|
||||
### 5.5 充值中心 `ProfileRechargeCenterResponse`(改,已落地)
|
||||
|
||||
### 5.6 后台(新增 / 改)
|
||||
- `mudPointBalance` 用 §5.1;`membership` 用 §5.2。
|
||||
- 会员列表改为 `membershipPlans: RuntimeProfileMembershipPlanRecord[]`:一行一档,含 `plan` /
|
||||
`title` / `rank` / 月价 / 年价 / 每期泥点 / 模型权限 / 并发上限 / 排序 / 可购买,
|
||||
由 `profile_membership_plan` 表按 `sort_order` 升序读出。
|
||||
- 删除 `walletBalance`、`membershipProducts`、`benefits`(旧 Month/Season/Year 权益矩阵)。
|
||||
- `pointProducts` 只剩泥点商品(会员商品行不再出现在商品配置表里)。
|
||||
|
||||
### 5.6 后台(新增 / 改,已落地)
|
||||
|
||||
- 新增 `GET|POST /admin/api/profile/membership-plans`(按档位枚举 CRUD 价格与权益)。
|
||||
- `GET|POST /admin/api/profile/recharge-products` 继续管 6 档充点商品。
|
||||
- 订单列表 / 退款人工复核读取 §2.4 快照(含变更前后档位与补点)。
|
||||
- `rank` 不进请求:升级比较基准由代码内目录决定,后台不能改。
|
||||
- `normal` 是不可购买的非会员占位档位,价格与额度由后端按 0 落库、`enabled` 强制 `false`。
|
||||
- 非 `normal` 档位要求月价 / 年价 / 并发上限都大于 0,否则 4xx。
|
||||
- 后台页:新增「会员档位」页(`AdminMembershipPlanPage`);「充值商品」页收敛为纯泥点商品
|
||||
(`AdminRechargeProductPage` 不再有会员档位与 `membership_*` 字段)。
|
||||
- 订单列表 / 退款人工复核读取 §2.4 快照(`membershipChange`,含变更前后档位、周期、补点与有效期)。
|
||||
|
||||
### 5.7 契约生成(ts-rs,已落地)
|
||||
|
||||
- 余额 DTO 的形状由 Rust 单一真源导出,前端**不再手写第二份字段列表**:
|
||||
`cargo test -p shared-contracts --features ts-bindings export_bindings` 生成
|
||||
`packages/shared/src/contracts/generated/ProfileMudPointBalanceResponse.ts`,
|
||||
`packages/shared/src/contracts/runtime.ts` 只 re-export 成 `ProfileMudPointBalance`。
|
||||
- `u64` 字段加 `ts(type = "number")`:JSON 走数字,不用 `bigint`,避免序列化与前端类型分叉。
|
||||
- `npm run check:generated-bindings` 以「重新生成并与工作区逐字节比较」做门禁,防止 Rust 改了而生成文件没重跑。
|
||||
- 需要展示用派生字段(如 `totalPoints` 合计)时,在前端 `utils/mudPoints.ts` 里派生,不改 DTO。
|
||||
|
||||
---
|
||||
|
||||
@@ -453,22 +490,40 @@ flowchart TD
|
||||
|
||||
### 10.2 实施状态
|
||||
|
||||
已落地(各自独立可编译):
|
||||
已全部落地(工作区可编译、测试全绿):
|
||||
|
||||
后端:
|
||||
|
||||
- 会员领域模块 `module-runtime/src/membership/{catalog,cycle,limits,upgrade}.rs`,公历基元抽为 `civil_calendar`。
|
||||
- 账本来源枚举末尾追加 `MembershipUpgradeGrant`,并同步契约常量、后台索引映射、前端来源标签与生成绑定。
|
||||
- 新增表 `profile_membership_plan`(主键为档位枚举 `RuntimeProfileMembershipPlan`),同步 `migration.rs`、表目录与生成绑定。
|
||||
- 会员档位目录播种 `ensure_default_profile_membership_plan`:表为空时按代码内目录整表播种,表非空时保留后台改价。
|
||||
- 删除未被引用的旧副本 `runtime/profile.rs`。
|
||||
- 新增表 `profile_membership_plan`(主键为档位枚举 `RuntimeProfileMembershipPlan`),同步 `migration.rs`、表目录与生成绑定;
|
||||
播种 `ensure_default_profile_membership_plan`:表为空时按代码内目录整表播种,表非空时保留后台改价。
|
||||
- 删列与清退:`profile_membership` 去掉 `tier` / `cycle_period_days`、追加
|
||||
`plan` / `cycle_index` / `cycle_count` / `cycle_kind`;`profile_recharge_product_config` 去掉
|
||||
`tier` 与四个 `membership_*` 字段;`RuntimeProfileMembershipTier`、会员权益矩阵、
|
||||
`canonical_membership_pricing_tier`、`membership_discount_bps`、固定 30 天常量全部退役。
|
||||
- `profile_recharge_order` 末位追加会员变更快照列(档位 / 周期 / 补点 / 有效期 / 价格拆分 JSON),
|
||||
购买与升级都会落快照;退款仍走人工复核,不自动回滚。
|
||||
- 自然月账期:`advance_profile_membership_cycle_to` 每期从原始锚点重算(1/31 → 2/28 → 3/31),
|
||||
到期清月度、停发、保留永久;6 档充点商品取消首充赠送,`¥128` / `¥328` 两档由停用改为在售。
|
||||
- HTTP:三池余额投影、`ProfileMembershipResponse.plan`、`POST /api/profile/membership/upgrade-quote`、
|
||||
`GET|POST /admin/api/profile/membership-plans`;会员订单复用 `POST /api/profile/recharge/orders`。
|
||||
|
||||
待落地——**第 1、2 项必须作为同一个提交落地**,否则工作区不可编译:
|
||||
前端与契约:
|
||||
|
||||
1. `module-runtime` 领域层清退:删 `RuntimeProfileMembershipTier`、会员权益快照/记录、`cycle_period_days`、`tier` 与充点商品上的四个 `membership_*` 字段;会员快照/记录改用 `plan` + `cycle_index` / `cycle_count` / `cycle_kind`;充值中心改用 `membership_plans`。
|
||||
2. `spacetime-module`:两张表的列增删、充值中心装配、后台商品 upsert 收敛为泥点商品、会员目录价格与自然月周期逻辑改走 `plan` + 目录表。
|
||||
3. `spacetime-client` mapper / facade 与 `api-server` DTO / 后台接口同步;重新生成绑定。
|
||||
4. 三池余额投影 `ProfileMudPointBalanceResponse`、`ProfileMembershipResponse.plan`、`POST /api/profile/membership/upgrade-quote` 与下单重算。
|
||||
5. 充点 6 档落库与 `resolve_default_point_product_migration` 中 `points_1280` / `points_3280` 的历史反例修正。
|
||||
6. `packages/shared` 契约与共享组件同步。
|
||||
7. 门禁:`SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1 npm run check:spacetime-schema`、`npm run check:generated-bindings`、`npm run check:encoding`、`git diff --check`。
|
||||
- `ProfileMudPointBalanceResponse` 由 ts-rs 生成到 `packages/shared/src/contracts/generated/`,
|
||||
`packages/shared/src/contracts/runtime.ts` 只 re-export;`check:generated-bindings` 守住漂移。
|
||||
- 共享组件与平台入口改用三池:`PlatformMudPointWalletEntry` 明细面板并列「永久泥点 / 月度泥点 / 每日免费泥点」,
|
||||
充值弹层余额读三池合计,`ProfileRechargeCenterResponse` 不再有 `walletBalance`。
|
||||
- 展示用合计在 `packages/shared/src/utils/mudPoints.ts` 派生(`totalPoints`),月度刷新时刻固定按北京时间格式化。
|
||||
- 后台:新增「会员档位」页,充值商品页收敛为纯泥点商品。
|
||||
|
||||
未做(依决策保留,不是遗漏):
|
||||
|
||||
- 模型权限与并发上限**只落字段**,服务端不做拦截。
|
||||
- 不提供 `availableTotalPoints`;合计由前端派生。
|
||||
- 会员购买 / 升级的 C 端 UI 未做(会员侧未上线,充值弹层只陈列泥点商品)。
|
||||
- 永久泥点仍是「总额 − 每日免费 − 月度」的推导余数,独立存储见
|
||||
[`【ADR】泥点三池以单一总额为权威`](../../adr/【ADR】泥点三池以单一总额为权威-2026-10-02.md) 的 TODO 与触发条件。
|
||||
|
||||
> 约束来源:`scripts/check-spacetime-schema-guard.mjs` 只做源码静态比对,破坏性变更必须显式带 `SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1` 才会放行;而 `module-runtime` 与 `spacetime-module` 相互依赖,任一侧单独清退都会让另一侧无法编译,所以领域层清退必须与 schema 变更同批提交。
|
||||
|
||||
Reference in New Issue
Block a user