# PRD:UR#62 H5 购买与实名顺序配置 Status: ready-for-agent --- ## Problem Statement 卡和设备已经保存 `realname_policy`,也已有单资产修改接口,但 C 端充值、购买、实名入口分别维护局部判断,接口仍只返回原始策略,无法稳定表达“运营商实名能力优先于资产顺序策略”。后台也缺少卡和设备的批量配置能力。 设备场景还存在两个容易混淆的概念:每张卡是否需要实名由其运营商 `realname_link_type` 决定;设备的 `realname_policy` 只决定购买和实名的先后顺序。本期设备数据前提是所有有效绑定卡都需要实名,不提前设计设备内混有无需实名卡的流程。 ## Solution 建立统一的有效实名策略解析和购买资格规则。独立卡使用卡策略;设备以及从设备入口访问的卡使用设备策略。对每张卡,`realname_link_type=none` 表示无需实名,`template/gateway` 表示需要实名。独立卡据此计算有效策略;设备本期仅允许 `before_order/after_order`,并按全部有效绑定卡的实名结果决定购买资格。 保留现有单资产修改接口,新增卡和设备批量配置接口。C 端初始化直接返回有效策略、是否需要实名和实际实名状态,前端只编排页面,不复制后端判断。 ## User Stories 1. 作为 C 端客户,我希望无需实名的独立卡直接进入购买流程。 2. 作为 C 端客户,我希望“先实名后购买”的独立卡在未实名时先完成实名。 3. 作为设备客户,我希望设备配置为先实名时,全部有效绑定卡完成实名后才能购买。 4. 作为设备客户,我希望设备配置为先购买时,可以完成购买后再逐张处理未实名卡。 5. 作为运营人员,我希望在卡或设备列表一次修改最多 500 个资产的实名顺序,并保证全成全败。 6. 作为运营人员,我希望设备下卡的页面明确提示 H5 顺序由设备策略控制。 7. 作为审计人员,我希望单条和批量变更都留下统一审计记录。 ## Implementation Decisions ### 领域语义 - 卡的 `realname_link_type` 始终决定该卡是否需要实名:`none` 为无需实名,`template/gateway` 为需要实名。 - `realname_policy` 只描述购买与实名顺序:`none`、`before_order`、`after_order`;默认值继续为 `after_order`。 - 独立卡使用卡自身 `realname_policy`。若 `realname_link_type=none`,有效策略固定为 `none`;若为 `template/gateway` 而卡策略为 `none`,这是冲突数据,初始化和购买均明确报错,不得静默放行。 - 设备以及从设备入口解析出的卡使用设备 `realname_policy`,绑定卡自身策略不参与设备 H5 顺序。 - 本期确认设备的全部有效绑定卡都需要实名,因此设备策略只允许 `before_order` 或 `after_order`;设备配置 `none` 属于非法配置。 - 本期仍按每张卡的 `realname_link_type` 生成具体实名入口,但不设计设备内出现 `realname_link_type=none` 卡时的资格、展示和批量交互;未来出现该业务后另行扩展。 - 设备 `before_order` 的购买资格是所有有效绑定、未软删除的卡均 `real_name_status=1`;无绑定卡或任一卡未实名都不允许购买。 - 设备 `after_order` 允许先购买;购买完成后返回全部尚未实名的有效绑定卡,前端逐张引导实名。 - UR#53 的设备 `real_name_status` 是列表筛选投影,语义为“任一有效绑定卡已实名”;不得用该快照代替本需求的“全部有效绑定卡已实名”购买资格判断。 - 卡的 `realname_link_type`、实名状态及有效绑定关系由数据同步公共 DDD 提供;本需求不得在 C 端 Handler 复制第二套状态规则。 ### API 契约 - 保留 `PATCH /api/admin/assets/{identifier}/realname-mode`,请求为 `{ "realname_policy": "none|before_order|after_order" }`。 - 新增 `POST /api/admin/iot-cards/batch-update-realname-policy`。 - 新增 `POST /api/admin/devices/batch-update-realname-policy`。 - 两个批量接口统一请求:`asset_ids` 为去重后的正整数数组,1~500 项;`realname_policy` 使用上述枚举。 - 卡批量配置允许三个值,但每张卡都执行运营商能力冲突校验;设备批量配置只允许 `before_order/after_order`。 - 任一资产不存在、软删除、越权、类型不符或策略冲突时整批失败,不更新任何资产;错误不得泄露越权资源是否存在。 - 单条和批量接口复用同一 Application 校验规则,不能出现单条可写、批量不可写或反向不一致。 - C 端 `GET /api/c/v1/asset/info` 增加 `effective_realname_policy`、`realname_required`,继续返回实际 `real_name_status`;原 `realname_policy` 在兼容期保留为配置原值,不再供新前端决定流程。 - 设备响应还应返回 `pending_realname_cards`,仅包含当前客户有权操作且尚未实名的有效绑定卡必要信息;不得返回身份证等敏感信息。 - `realname_required` 对独立卡按卡运营商能力返回;对设备本期固定为 `true`,但仍必须经过设备绑定完整性校验。 - 充值校验、创建充值、创建套餐订单和实名链接入口都调用统一有效策略/资格用例,禁止继续散落比较 `RealnamePolicy`。 - 参数解析和完整 DTO 校验失败统一返回参数错误;所有枚举 description 从常量原文同步。 ### 事务、权限与审计 - 批量写操作使用 Application 事务脚本:一次批量加载权限范围内资产及必要运营商能力,完成全量校验后批量更新。 - 不逐资产查询运营商、绑定或权限,最多 500 项仍必须批量读取。 - 单条和批量成功后写 Audit Event;批量事件记录目标类型、数量、目标策略和请求 ID,不在日志中写客户实名敏感数据。 - 代理和平台账号继续使用现有资产数据范围;批量更新不得绕过 GORM Callback 和业务权限检查。 - 重复设置相同策略按幂等成功处理,不制造多次业务副作用;审计中可标记实际变更数量。 ### 前端 - 卡、设备列表分别增加批量“修改实名顺序”,显示已选择数量,最多 500 项。 - 卡可选无需实名、先实名后购买、先购买后实名;设备只展示后两项。 - 修改设备下卡策略时提示“实际 H5 流程由设备策略决定”。 - C 端只读取 `effective_realname_policy`、`realname_required` 和后端返回的待实名卡,不按资产类型或运营商类型自行覆盖。 - `before_order` 未满足时引导实名;`after_order` 在购买完成后引导未实名卡;冲突数据展示后端中文错误并停止流程。 ### 发布与依赖 - 本需求依赖数据同步公共 DDD 提供统一卡事实和有效绑定读取边界;公共能力未落地前不得在旧 C 端 Handler 增加临时分支。 - 发布前扫描独立卡 `realname_link_type!=none && realname_policy=none` 以及设备 `realname_policy=none` 的冲突数据,形成修复清单;运行时仍保留拒绝保护。 - API 和对应 C 端、后台前端同一维护窗口发布;旧 `realname_policy` 响应字段只做兼容,不长期承载流程语义。 ## Testing Decisions - 领域测试覆盖独立卡的 `none/template/gateway × none/before_order/after_order × 实名0/1` 组合。 - 设备测试覆盖无绑定卡、单卡、多卡、全部已实名、部分实名、全部未实名、无效绑定和软删除卡。 - 明确验证设备资格使用“全部有效卡已实名”,而 UR#53 设备列表快照仍是“任一有效卡已实名”。 - 批量接口覆盖 1、500、501 项,重复 ID、零 ID、不存在、越权、软删除、策略冲突和整批回滚。 - HTTP 集成测试使用真实开发 PostgreSQL、Redis、JWT 和进程内 Fiber App;实名 Gateway 使用测试 Adapter,不调用真实上游。 - 回归充值校验、创建充值、套餐购买、实名链接和设备逐卡实名,证明所有入口使用同一策略。 - 验证相同策略重复提交幂等、审计实际变更数量正确,且批量查询无 N+1。 - 前端验收三种独立卡流程、两种设备流程、批量交互、冲突错误和购买后逐卡引导。 ## Out of Scope - 不设计设备内混有无需实名卡时的有效策略和 UI。 - 不修改 `realname_link_type` 的配置入口或实名链接生成协议。 - 不改变 UR#53 的设备实名筛选投影语义。 - 不新增全局实名策略默认配置。 - 不实现数据同步公共 DDD、运营商回调或实名轮询本身。 ## Further Notes - 当前代码已经具备字段、默认值、单条接口和部分前置校验,但这些属于迁移输入;目标是统一资格用例,而不是继续复制条件。 - `realname_link_type` 决定“这张卡是否需要实名”,`realname_policy` 决定“什么时候实名”,两者不得互相替代。