feat: add software subscription orders
This commit is contained in:
+1
-1
@@ -107,7 +107,7 @@
|
||||
| T-626 | 软件套餐、权益与设备席位基础模型 | T-624, T-401 | 已完成阶段 2 基础模型:新增 `SoftwarePlan`、`SoftwareEntitlement`、`LicenseSeat`、`LicenseEvent` 和 `licensing.0002_softwareentitlement_licenseseat_licenseevent_and_more`。授予时复制套餐名称/价格/时长/设备数/宽限期快照并预创建固定席位;`(entitlement, seat_number)` 唯一。服务层以事务锁权益和席位执行授予、续期、撤销、绑定与解绑,所有人工操作原因必填并写事件;续期从 `max(now, expires_at)` 起算,不触碰钱包。admin 提供套餐维护及权益授予/续期/撤销专用入口,权益/席位/事件只读。未接购买、支付、迁移凭证或生成授权。已通过套餐快照、续期、撤销、审计、后台权限及双连接单席位并发测试、迁移和 `check`。**评审遗留(2026-07-21,须在 T-629 强制前修复)**:① `renew_software_entitlement` 只更新权益自身字段,不刷新该权益下已签发 `DeviceCredential.expires_at`(签发时钉死为旧 `grace_expires_at`),续费用户在旧宽限期后会被判 `license_expired`;现有续期测试只断言权益未覆盖凭证。② `revoke_software_entitlement` 不吊销凭证也不释放席位,裁决层由 `is_usable_at()` 兜底不构成安全问题,但留下 `revoked_at=null` 凭证与占用席位的数据残留。 | DONE |
|
||||
| T-627 | 存量用户迁移权益、网页确认与设备凭证 | T-624, T-626, T-501 | 已完成存量迁移闭环:新增 `LegacyMigrationGrant`、短时 `MigrationRequest` 和 `DeviceCredential`,均只保存 hash/摘要;admin 通过显式用户+套餐授予迁移资格和快照,新注册用户无自动路径。客户端用旧 API Key + 当前设备会话申请迁移并一次性取得凭证明文;同账号 portal 确认后事务内绑定席位、按 hash 签发凭证,重复确认/轮询不重复占位或签发。用户可自助解绑,凭证吊销并释放席位且写审计。新增迁移 API、portal 确认/设备页;旧生成 API 完全不变。已覆盖资格、缺会话、跨账号、过期、重复确认、凭证 hash、状态轮询与解绑;迁移 / `check` / 迁移一致性通过。 | DONE |
|
||||
| T-628 | 蝦皮圈专属授权入口与影子校验 | T-625, T-626, T-627, T-613, T-614 | 已完成:新增 `/api/v1/cmshopee/` 的 title、vision、异步图片提交和已接受任务读取,均复用原 API / 计费 core。统一授权判定检查设备会话、凭证、用户、产品、席位和权益,返回 `device_not_bound` / `device_mismatch` / `license_required` / `license_expired`;当前仅写脱敏 `would_reject` 日志,不阻断专属或通用调用。通用 `/api/v1/generate/*` 未改,任务读取不追加订阅校验;`CMSHOPEE_AUTHORIZATION_SHADOW_MODE=true` 为部署口径。已验证正确 / 缺失 / 过期凭证判定、迁移流程回归、`check` 和迁移一致性。**评审说明(2026-07-21)**:`CMSHOPEE_AUTHORIZATION_SHADOW_MODE` 当前仅被写入日志事件,没有任何分支依据它决定放行或拒绝,属阶段 2 预期行为;须留档提醒——切换强制拦截不是改环境变量即可,需要在专属入口显式加入拒绝分支并同步部署说明,避免运维误判为纯配置开关。 | DONE |
|
||||
| T-629 | 软件套餐购买、续订订单与权益入账 | T-626, T-627, T-628, T-304, T-305 | **DOING。** 新增与 `RechargeOrder`、`PointsLedger` 完全分离的 `SoftwareOrder`,实现蝦皮圈套餐选择、创建待支付订单、支付回调/主动查单后的幂等权益发放与续期;不得把软件订阅金额兑换为点数,也不得修改既有充值订单、充值回调或钱包入账契约。订单必须锁定产品代码、套餐名称、金额、币种、时长、设备数、宽限期和支付通道快照,并保存明确的发放结果(目标权益、`LicenseEvent`、发放时间);支付渠道加 `payment_txn_no` 必须只归属一个软件订单,已支付订单收到不同交易号一律拒绝。微信支付能力仅复用验签、下单、查单的渠道适配,订阅回调/查单服务与路由独立于 `RechargePayment` / `apply_recharge_payment()`。回调必须验签、校验订单金额/通道、锁订单并按订单号幂等;同一订单的并发或重复回调最多发放/续期一次。portal 提供订阅状态、套餐购买/续订入口和订单只读记录;订单状态明确为 `pending -> paid`,渠道下单失败为 `failed`,过期支付票据为 `expired`,不得把未支付订单发放权益。第一版“月订阅”由用户每月主动续订,不假设自动代扣协议。**退款范围:本卡不新增退款发起或自动退款回收权益。** 已支付订单的退款及关联权益撤销走运营人工流程、必须按订单留痕;要支持自动回收时,需另建按订单权益周期/发放账本任务,不能简单缩短累计 `expires_at` 而误伤后续续费。测试覆盖订单/套餐快照、同订单重复与并发回调、不同交易号、金额/通道/验签不一致、主动查单、续期起算、未支付不发权益、充值链路回归,以及回调 HTTP 契约。真实生产支付验收依赖既有微信回调到账闭环修复;未提供商户条件时仅按 mock/SDK 契约测试。**前置修复(本卡支付上线验收前完成)**:先修 T-626 遗留 ①,在 `renew_software_entitlement` 事务内同步刷新该权益下所有未吊销 `DeviceCredential.expires_at`,并测试续期后凭证仍可通过授权判定;再修遗留 ②,撤销权益时吊销未吊销凭证并释放其占用席位,逐项写审计事件。 | DOING |
|
||||
| T-629 | 软件套餐购买、续订订单与权益入账 | T-626, T-627, T-628, T-304, T-305 | **已完成。** 新增与 `RechargeOrder`、`PointsLedger` 完全分离的 `SoftwareOrder` 和 `licensing.0004_alter_licenseevent_action_softwareorder`。订单锁定产品、套餐、金额、币种、时长、设备数、宽限期和通道快照,保存关联权益、唯一 `order_fulfilled` 事件和发放时间;`order_no` 与 `(pay_method, payment_txn_no)` 受数据库唯一约束,已支付订单收到不同交易号一律拒绝。微信支付仅复用通用验签、下单、查单适配,新增独立 `SOFTWARE_WECHAT_PAY_NOTIFY_URL`、`POST /api/v1/software-orders/callback/wechat` 与 `GET /api/v1/software-orders/status`,未调用 `apply_recharge_payment()`、未写 `UserWallet` / `PointsLedger`。portal 新增 `/subscription` 套餐购买/续订、状态和最近订单,admin 新增只读软件套餐订单检索。成功回调/查单锁订单后首次创建权益或续订同套餐权益;同订单重复/并发回调最多发放一次,金额/通道/验签不一致不发权益。支付票据到期转 `expired`,下单失败转 `failed`。第一版仅微信主动月度续订,不做自动代扣、退款发起或自动退款回收;已支付退款与权益撤销由运营按订单人工处理。**同时修复 T-626 遗留**:续期同步刷新未吊销凭证到期时间,撤销权益逐项吊销凭证、释放席位并写审计。已通过 12 条 licensing/API 目标测试(含双连接并发回调)和 31 条既有充值回归;`check`、`makemigrations --check --dry-run`、编译检查通过。全量测试在远端 MySQL 测试库运行 10 分钟未返回且无失败输出,未将其记为通过。 | DONE |
|
||||
|
||||
## 里程碑
|
||||
|
||||
|
||||
Reference in New Issue
Block a user