# 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` 决定“什么时候实名”，两者不得互相替代。

