{
  "source_path": "work_store/VWCG-1121-gift-account-mvp/technical-design.md",
  "status": "historical-conflict-snapshot-not-an-active-contract",
  "original_sha256": "75c0f9a718a8f75bbcfe255f422af0e7a71b41cb5dfe58ea05f5e4444de82e4c",
  "original_text": "# VWCG-1121 充值赠送账户 MVP 技术方案\n\n> 版本：V0.9（评审稿）\n> 日期：2026-07-27\n> 适用仓库：`store`、`ai_api`、`ai_app`\n> 风险等级：高\n> 结论状态：有条件可实施，7 项产品/范围决策确认后进入开发\n\n## 1. 方案摘要\n\n本需求不是单纯增加一个 `gift_balance` 字段。要完整满足“充值赠送、不可提现、全渠道消费、可配置扣款顺序、原路退款”，必须同时改造账户、充值、消费、退款和对账链路。\n\n推荐方案：\n\n1. 在 `yoshop_user` 增加赠送余额，并把现有现金余额内部拆为“可提现现金 + 活动本金”。\n2. 新建充值赠送规则表与赠送流水表，充值订单复用三金额字段并保存命中规则快照。\n3. 新建账户结算单作为消费/退款幂等锚点，统一 PC、消费设备和小程序扣款算法。\n4. 在 `ydy_meal_order` 增加赠送支付、赠送退款、赠送剩余金额及扣款顺序快照。\n5. `ydy_config` 保存全局扣款顺序，暂定默认值为“餐补 → 现金 → 赠送”；只影响新订单。\n6. 跨账户库与业务库采用“账户侧幂等提交 + 业务侧重试同步 + 对账补偿”，不依赖伪跨库事务。\n7. 充值订单、消费订单及相关人员/部门/设备统计同步调整异步导出。\n\n## 2. 关键冲突与开发前决策\n\n| 编号 | 问题 | 暂定方案 | 是否阻断开发 |\n| --- | --- | --- | --- |\n| D1 | 云效 PRD 默认“餐补→赠送→现金”，本轮输入默认“餐补→现金→赠送” | 以本轮输入为暂定值：`subsidy,cash,gift` | 是 |\n| D2 | 命中赠送活动的充值本金不可提现，但现有 `balance` 没有本金属性 | 新增 `withdrawable_balance`、`activity_principal_balance`，且两者之和等于 `balance` | 是 |\n| D3 | 部分退款如何恢复账户 | 建议按该订单实际扣款逆序恢复，确保每次退款可复算、可追溯 | 是 |\n| D4 | 旧充值套餐赠送与新赠送活动关系 | 同一订单只能命中一种赠送机制；建议新活动表成为统一入口，旧套餐迁移后停用 | 是 |\n| D5 | PC/小程序之外的旧 H5 充值是否改造 | 本期默认不含旧 H5，但上线前必须确认是否仍有真实流量 | 是 |\n| D6 | `source=10` 是否直接产生扣款 | 先确认主/聚合订单是否只是父单；父单不得重复扣款 | 是 |\n| D7 | PRD 的 `scanPay.vue` 在当前 `ai_app` 分支不存在 | 确认目标分支或真实页面后再排期 | 是 |\n\n## 3. 范围与非范围\n\n### 3.1 本期范围\n\n- PC 机构人员列表显示赠送余额。\n- PC 账户设置新增赠送活动、扣款顺序设置及操作日志。\n- PC 和小程序充值展示并记录支付金额、赠送金额、实际到账金额。\n- 充值成功后现金与赠送余额、流水分别入账。\n- PC、小程序、绑盘机、消费机、虚拟订单、外部订单、闸机、点餐机共用账户结算规则。\n- 消费订单展示和保存现金、餐补、赠送拆分。\n- 全额/部分退款按原订单实际来源恢复。\n- 相关统计、流水、导出与人工验收。\n\n### 3.2 本期非范围\n\n- 赠送余额有效期、冻结、转赠、提现。\n- 按商品、餐次、人群、部门等复杂赠送活动条件。\n- 多活动叠加。\n- 对历史订单重算赠送金额。\n- 把两套数据库合并为一个数据库。\n\n## 4. 系统边界与目标架构\n\n```mermaid\nflowchart LR\n    subgraph Clients[\"客户端与设备\"]\n        PC[\"PC 管理后台\"]\n        WX[\"小程序 ai_app\"]\n        DEV[\"绑盘机/消费机/闸机/点餐机\"]\n        EXT[\"外部订单\"]\n    end\n\n    subgraph Services[\"服务层\"]\n        STORE[\"store\\nPC、设备、订单域\"]\n        AI[\"ai_api\\n小程序、账户域\"]\n        SETTLE[\"统一 AccountSettlementService\\n规划、扣款、退款、幂等\"]\n        RECON[\"对账与补偿任务\"]\n    end\n\n    subgraph Databases[\"数据层\"]\n        YDY[(\"业务库 ydy_*\\nmeal_order/config/refund\")]\n        YOSHOP[(\"账户库 yoshop_*\\nuser/recharge/log/settlement\")]\n    end\n\n    PC --> STORE\n    DEV --> STORE\n    EXT --> STORE\n    WX --> AI\n    STORE --> SETTLE\n    AI --> SETTLE\n    SETTLE --> YOSHOP\n    STORE --> YDY\n    AI --> YDY\n    RECON --> YOSHOP\n    RECON --> YDY\n```\n\n### 4.1 设计原则\n\n- 账户库是余额事实源，业务订单库保存业务状态与账户拆分快照。\n- 所有金额使用 `decimal(10,2)`，服务层使用字符串/定点运算，禁止浮点比较。\n- 客户端预览只用于展示，创建充值订单时必须由服务端重新匹配规则。\n- 消费与退款都以业务订单号作为幂等键。\n- 当前配置只用于新订单；历史订单退款只使用订单快照。\n- 新接口字段采用增量兼容，旧客户端可忽略。\n\n## 5. 账户与金额模型\n\n### 5.1 账户定义\n\n| 账户 | 用户可见 | 可消费 | 可提现 | 有效期 | 说明 |\n| --- | ---: | ---: | ---: | --- | --- |\n| 餐补 | 是 | 是 | 否 | 沿用现状 | 继续以有效补贴明细合计为事实值 |\n| 个人现金 | 是 | 是 | 部分可提现 | 永久 | 展示值仍为 `balance` |\n| 可提现现金 | 可在提现页展示 | 是 | 是 | 永久 | `withdrawable_balance` |\n| 活动本金 | 可在明细中标识 | 是 | 否 | 永久 | 命中赠送活动时的 `pay_price` |\n| 赠送余额 | 是 | 是 | 否 | 永久 | `gift_balance` |\n\n### 5.2 核心不变量\n\n```text\nactual_money = pay_price + gift_money\nbalance = withdrawable_balance + activity_principal_balance\nusable_total = valid_subsidy + balance + gift_balance\nmeal_order.pay_price = subsidy + cash + gift\ncash = withdrawable_cash_payment + activity_principal_payment\n累计退款金额 <= 原订单各账户实际扣款金额\n```\n\n建议现金账户消费内部顺序为“活动本金 → 可提现现金”，避免用户先消耗可提现资金而留下不可提现本金。这个内部顺序不改变前台显示的“现金”一级账户顺序，但须保存内部拆分以支持提现与退款。\n\n## 6. 数据库设计\n\n以下为逻辑设计，正式 DDL 应按项目数据库版本、索引长度和发布时间另行生成。\n\n### 6.1 账户库 `yoshop_*`\n\n#### `yoshop_user`\n\n| 字段 | 类型 | 默认值 | 用途 |\n| --- | --- | --- | --- |\n| `gift_balance` | `decimal(10,2)` | `0.00` | 赠送余额 |\n| `withdrawable_balance` | `decimal(10,2)` | 迁移确定 | 可提现现金 |\n| `activity_principal_balance` | `decimal(10,2)` | `0.00` | 活动本金 |\n\n约束：\n\n- 所有余额字段非负。\n- 每次现金变动后检查 `balance = withdrawable_balance + activity_principal_balance`。\n- `subsidy_balance` 继续兼容展示，但有效补贴以补贴明细合计为准。\n\n#### `yoshop_recharge_gift_rule`（新增）\n\n| 字段组 | 建议字段 |\n| --- | --- |\n| 主键与租户 | `rule_id`、`store_id` |\n| 展示 | `rule_name` |\n<<<<<<< HEAD\n| 匹配条件 | `pay_methods`（JSON 数组）、`min_money` |\n=======\n| 匹配条件 | `platform`、`pay_method`、`min_money` |\n>>>>>>> 5d05833ef476571c794ef3016336bc136526872c\n| 奖励 | `gift_money` |\n| 状态 | `status`、`is_delete` |\n| 审计 | `create_user_id/name`、`update_user_id/name`、`create_time`、`update_time` |\n\n匹配规则：\n\n1. 只匹配当前门店、启用且未删除的规则。\n2. `pay_price >= min_money`。\n<<<<<<< HEAD\n3. 一条规则可选择一个或多个支付方式，充值订单的 `pay_method` 包含于规则 `pay_methods` 时命中；枚举仅允许 `cash`、`alipay_offline`、`wechat_offline`、`online_scan`，规则不设置适用端。\n=======\n3. 平台、支付方式匹配；“全部”用明确枚举值表达。\n>>>>>>> 5d05833ef476571c794ef3016336bc136526872c\n4. 多条命中时依次取：`min_money` 最大、`gift_money` 最大、`rule_id/create_time` 最新。\n5. 同一充值订单最多命中一条规则。\n\n推荐索引：\n\n<<<<<<< HEAD\n- `(store_id, status, is_delete, min_money)`；`pay_methods` 为小集合 JSON 数组，由应用层校验命中\n=======\n- `(store_id, status, is_delete, platform, pay_method, min_money)`\n>>>>>>> 5d05833ef476571c794ef3016336bc136526872c\n- 列表页 `(store_id, is_delete, create_time)`\n\n#### `yoshop_recharge_order`（复用并扩展）\n\n继续复用：\n\n- `pay_price`\n- `gift_money`\n- `actual_money`\n\n建议增加快照字段：\n\n| 字段 | 用途 |\n| --- | --- |\n| `gift_rule_id` | 命中规则 ID，未命中为 0 |\n<<<<<<< HEAD\n| `gift_rule_snapshot` | 规则名、门槛、赠送金额、支付方式 JSON 快照 |\n=======\n| `gift_rule_snapshot` | 规则名、门槛、赠送金额、平台、支付方式 JSON 快照 |\n>>>>>>> 5d05833ef476571c794ef3016336bc136526872c\n| `withdrawable_amount` | 本次进入可提现现金的金额 |\n| `activity_principal_amount` | 本次进入活动本金的金额 |\n\n规则：\n\n- 普通充值：`withdrawable_amount = pay_price`，`activity_principal_amount = 0`。\n- 命中赠送：`withdrawable_amount = 0`，`activity_principal_amount = pay_price`。\n- `gift_money` 只进入 `gift_balance`。\n- 旧订单四个新增字段默认 0/空，维持历史含义。\n\n#### `yoshop_user_gift_log`（新增）\n\n| 字段组 | 建议字段 |\n| --- | --- |\n| 主键与租户 | `log_id`、`store_id`、`user_id` |\n| 变动 | `scene`、`money`、`balance_before`、`balance_after` |\n| 业务定位 | `biz_type`、`biz_order_id`、`biz_order_no` |\n| 幂等 | `idempotency_key` |\n| 审计 | `describe`、`remark`、`operator_id/name`、`create_time` |\n\n场景建议：\n\n- 10：充值赠送\n- 20：消费扣款\n- 30：后台调整（本期若无入口则保留枚举、不开放）\n- 40：消费退款\n- 50：充值退款冲正\n\n对 `idempotency_key` 建唯一索引，避免回调/消费/退款重复入账。\n\n#### `yoshop_account_settlement`（新增）\n\n用于跨库消费和退款的幂等、重试与对账，不取代现有账户流水。\n\n| 字段组 | 建议字段 |\n| --- | --- |\n| 业务键 | `settlement_id`、`biz_type`、`biz_order_no`、`refund_no` |\n| 主体 | `user_id`、`store_id`、`order_source` |\n| 金额 | `total_money`、`subsidy_money`、`cash_money`、`gift_money` |\n| 现金内部 | `withdrawable_money`、`activity_principal_money` |\n| 快照 | `deduction_order_snapshot`、`account_before_snapshot`、`account_after_snapshot` |\n| 状态 | `status`：处理中/账户已提交/业务已同步/失败待处理 |\n| 重试 | `retry_count`、`last_error`、`next_retry_time` |\n| 时间 | `create_time`、`update_time`、`completed_time` |\n\n唯一键：\n\n- 消费：`(biz_type, biz_order_no)`\n- 退款：`(biz_type, biz_order_no, refund_no)`\n\n### 6.2 业务库 `ydy_*`\n\n#### `ydy_config`\n\n新增配置：\n\n```text\nkey: account_deduction_order\nvalue: [\"subsidy\",\"cash\",\"gift\"]\ndesc: 消费账户扣款顺序\n```\n\n校验规则：\n\n- 必须恰好包含 `subsidy`、`cash`、`gift` 三项。\n- 不允许重复或未知账户。\n- 缺失、关闭或非法时回退到 `[\"subsidy\",\"cash\",\"gift\"]` 并告警。\n- 保存后使相关缓存失效。\n\n建议新增 `ydy_account_config_log`，记录配置键、变更前、变更后、操作人、时间、IP，满足账户设置审计要求。\n\n#### `ydy_meal_order`\n\n| 字段 | 用途 |\n| --- | --- |\n| `gift` | 本单赠送余额支付金额 |\n| `gift_refund_amount` | 已退赠送金额 |\n| `remain_gift` | 可退赠送金额 |\n| `withdrawable_cash_payment` | 本单消耗的可提现现金 |\n| `activity_principal_payment` | 本单消耗的活动本金 |\n| `withdrawable_cash_refund` | 已恢复的可提现现金 |\n| `activity_principal_refund` | 已恢复的活动本金 |\n| `deduction_order_snapshot` | 本单实际使用的一级账户顺序 |\n| `account_settlement_id` | 账户侧结算单 ID |\n\n兼容规则：\n\n- 新增金额字段默认 `0.00`，历史订单视为 `gift=0`。\n- 历史订单保留已有现金/餐补值，不回填虚构的扣款顺序。\n- 历史订单退款按旧逻辑兼容；新订单以是否存在结算快照区分。\n- 不修改 `source` 既有枚举值。\n\n#### `ydy_meal_order_refund`\n\n增加：\n\n- `gift`\n- `withdrawable_cash`\n- `activity_principal`\n- `account_settlement_id`\n\n退款单保存本次实际恢复金额，不只保存申请金额。\n\n## 7. 充值流程\n\n```mermaid\nsequenceDiagram\n    participant C as PC/小程序\n    participant API as 充值服务\n    participant PAY as 支付渠道\n    participant DB as 账户库\n\n<<<<<<< HEAD\n    C->>API: 充值预览(pay_price, pay_method)\n=======\n    C->>API: 充值预览(pay_price, platform, pay_method)\n>>>>>>> 5d05833ef476571c794ef3016336bc136526872c\n    API->>DB: 匹配启用规则\n    DB-->>API: 最优规则\n    API-->>C: pay/gift/actual 预览\n    C->>API: 创建充值订单\n    API->>DB: 服务端重新匹配并保存规则快照\n    API->>PAY: 发起支付\n    PAY-->>API: 支付成功回调\n    API->>DB: 锁定订单并校验未处理\n    API->>DB: 现金本金入账、赠送入账、分别写流水\n    API->>DB: 条件更新订单为已支付\n    API-->>PAY: 成功\n```\n\n### 7.1 服务端规则\n\n- 预览结果不作为入账依据。\n- 创建订单时重算并保存规则快照，后续规则修改不影响该订单。\n- 支付回调按订单快照入账，不能再次匹配当前规则。\n- 使用订单号/支付回调号做幂等，重复回调返回已有成功结果。\n- 订单、用户余额、现金流水、赠送流水在同一账户库事务中提交。\n- 回调失败必须向上抛出并进入渠道重试或内部补偿，禁止吞异常后返回成功。\n\n### 7.2 三金额与流水\n\n示例：支付 100 元，赠送 20 元。\n\n| 项目 | 变动 |\n| --- | ---: |\n| `pay_price` | 100.00 |\n| `gift_money` | 20.00 |\n| `actual_money` | 120.00 |\n| `balance` | +100.00 |\n| `activity_principal_balance` | +100.00 |\n| `gift_balance` | +20.00 |\n| 现金流水 | +100.00，标记活动本金 |\n| 赠送流水 | +20.00，场景为充值赠送 |\n\n## 8. 消费结算流程\n\n### 8.1 统一算法\n\n输入：\n\n- 用户、门店、业务订单号、订单来源、应付金额。\n- 有效补贴明细、现金内部余额、赠送余额。\n- 当前合法扣款顺序。\n\n输出：\n\n- 餐补、现金、赠送拆分。\n- 现金内部的活动本金、可提现现金拆分。\n- 扣款顺序、账户前后快照和结算单 ID。\n\n伪代码：\n\n```text\nremaining = payable\nfor account in configured_order:\n    used[account] = min(available(account), remaining)\n    remaining -= used[account]\nif remaining > 0:\n    fail(\"账户余额不足\")\n\nif used.cash > 0:\n    used.activity_principal = min(activity_principal_balance, used.cash)\n    used.withdrawable = used.cash - used.activity_principal\n```\n\n### 8.2 跨库一致性\n\n```mermaid\nstateDiagram-v2\n    [*] --> Processing: 创建/锁定幂等结算单\n    Processing --> AccountCommitted: 账户库扣款与流水提交\n    AccountCommitted --> BusinessSynced: 业务订单写入拆分和结算ID\n    AccountCommitted --> PendingSync: 业务库写入失败\n    PendingSync --> BusinessSynced: 重试成功\n    PendingSync --> ManualReview: 超过重试阈值\n    Processing --> Failed: 余额不足或业务校验失败\n```\n\n规则：\n\n- 账户扣款前锁定用户汇总行及需要使用的补贴明细。\n- 同一业务订单只允许一个成功消费结算单。\n- 账户库提交后，业务库更新失败时不得重复扣款；重试只同步原结算结果。\n- 对账任务按“已提交但未同步”“业务已支付但无账户结算”“金额拆分不等”三类检测。\n- 异常订单在设备/客户端返回“处理中”或明确失败，不能返回虚假成功。\n\n### 8.3 所有下单入口接入\n\n| 来源 | 当前入口 | 改造要求 |\n| ---: | --- | --- |\n| 1/2/3/6/7 | `store/application/api/service/Consume.php` | 同步与队列路径调用同一结算服务，删除重复算法 |\n| 4 | `store` 与 `ai_api` 的 `meal/PaySuccess.php` | 统一调用结算服务；只保留渠道编排 |\n| 4 的直接支付 | `ai_api/app/api/service/meal/DirectPay.php` | 移除独立 `deductBalance()` 规则 |\n| 5 | 外部订单入口 | 纳入相同幂等键、余额检查与拆账 |\n| 10 | 主/聚合订单 | 确认是否父单；父单不得与子单重复扣款 |\n\n设备协议新增 `gift_payment`、`remain_gift` 等字段应保持可选，旧设备继续读取既有字段。\n\n## 9. 退款与提现\n\n### 9.1 消费退款\n\n- 全额退款：按原订单各账户实际扣款金额全部恢复。\n- 部分退款：建议从实际扣款顺序的末端向前恢复。例如订单按“餐补→现金→赠送”扣款 10/20/30，退款 35 时先恢复赠送 30，再恢复现金 5。\n- 现金恢复时继续按原订单保存的 `activity_principal_payment`、`withdrawable_cash_payment` 逆序恢复，不把活动本金误恢复为可提现现金。\n- 重复退款以退款单号幂等；累计恢复不能超过原订单拆分。\n- 配置修改不影响历史订单退款。\n\n### 9.2 充值退款/提现\n\n- 用户可提现金额上限为 `withdrawable_balance`，不能再仅依据 `balance` 或充值订单原始 `refundable_money`。\n- 命中赠送活动的充值：支付本金进入活动本金，赠送进入赠送余额，二者均不可提现。\n- 普通充值：支付本金进入可提现现金。\n- 充值退款如仍需支持，应先明确“已消费后的退款”规则，并在账户余额足够时按原充值来源冲正。\n\n### 9.3 历史现金迁移\n\n建议初始迁移：\n\n```text\nwithdrawable_balance = balance\nactivity_principal_balance = 0\ngift_balance = 0\n```\n\n该方案保持历史现金可提现能力，不倒推历史活动属性。若产品要求按 365 天或旧活动记录重建可提现金额，必须另立数据治理任务并做差异报表，本期不能凭现有余额字段可靠推导。\n\n## 10. API 与页面改造\n\n### 10.1 PC 后台\n\n| 模块 | 改造 |\n| --- | --- |\n| 人员管理-机构人员 | 列表返回并展示 `gift_balance`；总余额口径明确为餐补+现金+赠送 |\n| 账户设置-赠送活动 | 规则列表、新增、编辑、启停、删除、命中预览 |\n| 账户设置-扣款顺序 | 三账户拖拽/排序、保存、默认值、操作日志 |\n| 充值订单 | 展示/筛选/统计 `pay_price`、`gift_money`、`actual_money` 及命中规则 |\n| 消费订单 | 展示现金、餐补、赠送及退款拆分 |\n| 流水 | 增加赠送流水页或在统一流水中增加账户类型筛选 |\n\n权限要求：\n\n- 新页面和接口必须同时配置菜单、功能权限与数据权限。\n- 控制器不能只因继承基类就假设权限完整；需核对 `beforeActionList`。\n- 规则新增/编辑/启停、扣款顺序变更必须记录操作人、前后值和时间。\n\n### 10.2 小程序 `ai_app` / `ai_api`\n\n| 页面/接口 | 改造 |\n| --- | --- |\n| 用户资产接口 | 返回现金、可提现现金、活动本金、餐补、赠送及总可用余额 |\n| 余额页 | 展示赠送余额和不可提现说明 |\n| 充值页 | 输入金额后调用预览；展示支付、赠送、到账三金额 |\n| 充值记录 | 展示三金额和命中活动 |\n| 收银台 | 展示预计/实际账户拆分 |\n| 订单详情 | 展示现金、餐补、赠送支付与退款 |\n| 流水页 | 支持赠送账户流水与账户类型筛选 |\n\n创建订单和支付成功响应建议增加：\n\n```json\n{\n  \"pay_price\": \"100.00\",\n  \"gift_money\": \"20.00\",\n  \"actual_money\": \"120.00\",\n  \"account_payment\": {\n    \"subsidy\": \"10.00\",\n    \"cash\": \"20.00\",\n    \"gift\": \"30.00\"\n  },\n  \"deduction_order\": [\"subsidy\", \"cash\", \"gift\"]\n}\n```\n\n金额字段统一使用两位小数字符串，避免前端浮点误差。\n\n## 11. 统计与异步导出\n\n该列表页关联异步导出功能，需同步确认/修改异步导出逻辑。\n\n| 关联列表 | 必须调整的口径 |\n| --- | --- |\n| 消费订单 | 增加赠送支付、赠送退款、赠送净额 |\n| 人员消费明细 | 个人现金、餐补、赠送分列并校验合计 |\n| 部门消费明细 | 汇总增加赠送支付与退款 |\n| 设备消费明细 | 按设备/来源汇总增加赠送 |\n| 充值统计 | 支付金额、赠送金额、实际到账分别统计，不能继续只汇总 `actual_money` |\n\n菜品销量、餐厅经营汇总、档口营收结算是否受影响，取决于其收入口径是否只看订单实付总额。若只看订单总额且不展示账户来源，可不改金额口径，但仍需回归证明。\n\n## 12. 安全、并发与审计\n\n- 所有查询、规则、流水和配置按 `store_id` 做租户隔离。\n- 账户行、补贴批次按确定顺序加锁，降低死锁概率。\n- 充值回调、消费、退款均有唯一业务幂等键。\n- 后端拒绝负数、超过金额精度、非法账户顺序、越权门店和超额退款。\n- 不信任客户端传入的赠送金额、扣款拆分或可提现金额。\n- 流水不允许物理修改；冲正使用相反方向的新流水。\n- 配置与规则变更写审计日志，记录操作前后值。\n- 错误日志不记录支付密钥、数据库口令和完整个人敏感信息。\n\n## 13. 兼容、上线与回滚\n\n### 13.1 上线顺序\n\n1. 在影子环境执行 DDL 并验证表锁、耗时、索引和回滚脚本。\n2. 先部署后端兼容代码，新增字段读取为空时按 0/默认顺序处理。\n3. 执行用户现金属性迁移并产出迁移前后余额对账报告。\n4. 部署 PC 与小程序，保持赠送规则全部停用。\n5. 对内部测试门店启用一条小额规则，覆盖充值、消费、退款、导出。\n6. 观察对账无差异后逐门店启用。\n\n### 13.2 监控\n\n- 充值三金额不相等计数。\n- 账户余额不变量异常数。\n- 重复回调拦截数。\n- 账户已提交但业务未同步数量与最长滞留时间。\n- 订单实付与账户拆分差额。\n- 退款超额/幂等拦截数。\n- 各账户余额负数告警。\n\n### 13.3 回滚\n\n- 业务回滚：先停用全部赠送活动，将扣款顺序恢复默认；旧客户端继续使用原字段。\n- 代码回滚：保留新增字段和表，不在有数据后直接删列；回滚版本应忽略赠送规则并阻止产生新赠送。\n- 数据回滚：对已产生的赠送与消费不得直接覆盖余额，必须用冲正流水和结算单逐笔恢复。\n- 跨库未同步：先停止新交易，完成结算单对账和补偿，再决定代码回滚。\n- DDL 回滚：只在确认新增表/列无业务数据时执行；生产已入账后采用前向修复。\n\n## 14. 测试覆盖矩阵\n\n| 测试域 | 必测场景 | 自动化层级 |\n| --- | --- | --- |\n| 规则匹配 | 门槛前/等于/超过；平台/方式；禁用；多规则排序；规则修改后订单快照不变 | 单元 + 接口 |\n| 充值 | 无赠送/有赠送；三金额；现金/赠送双流水；重复回调；并发回调；回调异常不返回假成功 | 服务 + 集成 |\n| 现金属性 | 普通本金可提现；活动本金不可提现；现金内部优先级；余额不变量 | 单元 + 集成 |\n| 扣款顺序 | 6 种排列；单账户/多账户；临界余额；余额不足；非法配置回退 | 参数化单元 |\n| 订单来源 | 1、2、3、4、5、6、7 逐一覆盖；10 依据决策 | 集成 + 契约 |\n| 幂等并发 | 同一订单重复消费、设备重试、队列重放、并发请求 | 集成 |\n| 跨库补偿 | 账户提交后业务库失败；重试不重复扣款；对账识别并修复 | 集成 + 故障注入 |\n| 退款 | 全额、部分、重复、超额；配置变更后退款；现金内部属性原路恢复 | 服务 + 集成 |\n| 历史兼容 | 老用户、老充值单、老消费单、空快照、赠送字段为 0 | 回归 |\n| 权限 | 无菜单权限、无功能权限、跨门店数据、操作日志 | 接口 + 手工 |\n| 导出统计 | 页面合计与导出合计一致；支付/赠送/到账不混淆 | 接口 + 文件比对 |\n| 小程序/PC | 金额预览、确认页、订单详情、流水、错误/加载/空状态 | 前端 + 手工 |\n| 设备 | 新旧协议字段兼容、超时重试、处理中状态 | 联调 + 手工 |\n\n建议新增专门的 `GiftAccount` 测试组，并回归既有：\n\n- `tests/Security/SecurityFixesTest.php`\n- `tests/apifox/http/run_dengbao_regression.sh`\n- 卡务/消费、充值、补贴、退款相关 PHPUnit 与 Apifox 集。\n\n## 15. 验收映射\n\n| 需求点 | 技术验收证据 |\n| --- | --- |\n| 人员列表赠送余额 | API 返回值、页面截图、租户数据权限测试 |\n| 赠送活动 | 规则 CRUD、命中边界测试、操作日志 |\n| PC+小程序充值 | 三金额订单、双流水、重复回调测试 |\n| 默认扣款顺序 | 配置值、6 种顺序参数化测试、订单快照 |\n| 所有下单 | 来源矩阵测试及各入口统一服务调用证据 |\n| 消费流水 | 现金/餐补/赠送流水和结算单一致性 |\n| 原路退款 | 全额/部分退款账户前后快照 |\n| 不可提现 | 活动本金与赠送提现拦截 |\n| 导出 | 页面、接口、异步导出三方合计比对 |\n| 跨库一致性 | 故障注入、重试与对账报告 |\n\n## 16. 推荐实施切片\n\n1. 数据结构、迁移脚本、账户不变量与对账基线。\n2. 赠送规则和 PC 账户设置权限/审计。\n3. PC、小程序充值三金额与双流水。\n4. 统一账户结算服务和消费幂等结算单。\n5. 按来源逐个接入所有下单入口。\n6. 消费退款、提现资格和跨库补偿。\n7. PC/小程序页面、统计、异步导出与全链路验收。\n\n每个切片都应先补回归测试，再合并到下一切片；资金链路不建议一次性大爆炸发布。\n\n## 17. 评审结论\n\n方案可以作为研发拆分基线，但不能在 D1～D7 未确认前直接编码。尤其是“默认扣款顺序”“活动本金不可提现模型”“部分退款规则”三项会直接决定数据库字段和历史兼容策略。\n\n文档评审通过后，应为各实施切片创建独立研发任务，并分别补充 DDL、API 契约、测试用例和发布证据。\n",
  "blocks": [
    {
      "line": 152,
      "variant_a": "| 匹配条件 | `pay_methods`（JSON 数组）、`min_money` |\n",
      "variant_b": "| 匹配条件 | `platform`、`pay_method`、`min_money` |\n"
    },
    {
      "line": 165,
      "variant_a": "3. 一条规则可选择一个或多个支付方式，充值订单的 `pay_method` 包含于规则 `pay_methods` 时命中；枚举仅允许 `cash`、`alipay_offline`、`wechat_offline`、`online_scan`，规则不设置适用端。\n",
      "variant_b": "3. 平台、支付方式匹配；“全部”用明确枚举值表达。\n"
    },
    {
      "line": 175,
      "variant_a": "- `(store_id, status, is_delete, min_money)`；`pay_methods` 为小集合 JSON 数组，由应用层校验命中\n",
      "variant_b": "- `(store_id, status, is_delete, platform, pay_method, min_money)`\n"
    },
    {
      "line": 195,
      "variant_a": "| `gift_rule_snapshot` | 规则名、门槛、赠送金额、支付方式 JSON 快照 |\n",
      "variant_b": "| `gift_rule_snapshot` | 规则名、门槛、赠送金额、平台、支付方式 JSON 快照 |\n"
    },
    {
      "line": 312,
      "variant_a": "    C->>API: 充值预览(pay_price, pay_method)\n",
      "variant_b": "    C->>API: 充值预览(pay_price, platform, pay_method)\n"
    }
  ]
}
