# 第九阶段：SaaS 模块商城与租户交付闭环

## 目标

- 套餐决定租户购买的业务 Feature。
- 角色决定员工可执行的 Permission，菜单只用于导航展示。
- 平台 SaaS 收费与租户学生商城完全分离。
- 平台创建租户和 Owner 后，租户可自助购买、开通并完成学生端初始化。

## 已完成

- `SaasFeature`、`PermissionModule`、`SaasOffering` 与不可变 `SaasOfferingVersion`。
- 基础套餐、附加包、模块清单、额度定义、租户覆盖和原子用量记录。
- `IFeatureAccessService` 统一读写状态、Feature、权限过滤和额度判断。
- 题库、词汇、手册、视频、分数线和站点内容使用独立 `PermissionModule` 与后台权限点，单独购买、授权和生成菜单。
- `/api/platform-admin/saas/**` 商品、订单、支付、订阅、发票、催缴和人工收款管理。
- `/api/tenant-billing/**` 目录、幂等报价、幂等下单、支付、续费、变更、取消、用量和发票。
- `/api/platform-billing/callbacks/{provider}` 独立平台收款回调。
- 平台创建租户与 Owner 的事务化开户，以及 `/api/tenant-onboarding/status`。
- runtime/UI bootstrap 返回有效 Feature、登录方式、权限、菜单、订阅和额度摘要。
- 员工、学生、私有题、存储、导入、导出和短信额度已接入真实写路径；当前量由 Worker 定期按业务事实校准。
- Worker 自动应用周期末降级、取消、Trial 到期、PastDue 和 Expired 状态转换，多 Worker 通过乐观并发保证幂等。
- 旧 `ProductModule`、可变套餐、旧订阅和重复平台发票模型已删除。

## 数据库边界

- 当前仓库只有一个 greenfield `InitialSchema`。
- 发布套餐版本、Feature 清单和额度清单不可修改。
- 订阅基础项必须引用基础套餐；订阅项类型必须匹配 Offering 类型。
- 订阅项可绑定产生它的 `PlatformBillingOrderItem` 价格快照。
- 平台支付事件按租户、Provider 和 Provider Event ID 幂等。
- 每个订阅最多一个 Active 基础套餐项和一个 Scheduled 基础套餐项，周期末原子切换。
- 报价、订单和支付均保存租户内唯一幂等键，客户端不能提交最终价格。

## 验收

```bash
dotnet build TIKU-BACKEND.slnx --no-restore
dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-build
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-build
dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
dotnet ef migrations has-pending-model-changes --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
git diff --check
```

真实 PostgreSQL 测试覆盖套餐发布不可变、报价与订单幂等、人工/微信/支付宝收款、签名与金额拒绝、重复回调、跨租户订单隔离、未购模块权限拒绝、额度写路径与事实校准、订阅周期转换和 onboarding ready。

## 后续

第十阶段实现教师作业、考试、批阅和教学报告。教师 AI 对话与题目反馈审核继续独立排期。
