Files
junhong_cmp_fiber/openspec/changes/fix-package-usage-paid-amount-cost-snapshot/design.md
break cbadf77517
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m28s
修复价格不一致的问题
2026-08-13 17:43:14 +08:00

49 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
套餐使用记录 `tb_package_usage` 已存在两个价格快照字段:`paid_amount`(迁移 000129 引入,语义演进为成本价,见迁移 000150「paid_amount 继续存储成本价」)与 `retail_amount`(迁移 000150 引入,存储零售价)。`retail_amount` 的写入来源已经是 `order.TotalAmount`,语义正确;但 `paid_amount` 的 4 个写入点仍从 `order.ActualPaidAmount` 复制,与成本价语义不符。参见 proposal.md - Why。
`order.SellerCostPrice``int64`,非指针)已在订单各购买路径被正确填充为「销售成本价」:代理自购/代购为操作方成本价,平台代扣为目标店铺成本价,平台代购为买家成本价,个人客户下单为卖家店铺成本价,赠送/平台自营为 0。该字段即「成本价」的正确来源。
### 语义演进证据git 历史)
`paid_amount` 当初回填 `actual_paid_amount`(实付金额)不是笔误,而是语义中途漂移留下的不一致:
1. **迁移 0001292026-04-18commit `2b3a9cb`change `add-paid-amount-snapshot-to-package-usage`**当时需求即「快照购买时的实付金额」proposal 原文「用于快照购买时的实付金额」「无法在不 JOIN 订单表的情况下展示历史购买价格」。因此回填 `actual_paid_amount` 在当时语义下是正确实现。
2. **迁移 0001502026-06-01commit `944526d`change `fix-order-price-semantics`**:该 change 将 `Order.ActualPaidAmount` 重新定义为「实际支付成本价」,并声明 `PaidAmount` 继续快照成本价、仍取 `order.ActualPaidAmount`。但其 spec 仅覆盖三个场景(代理钱包支付、平台代购、赠送),三者恰好都满足「实付金额 = 成本价」,唯独遗漏「个人客户购买」场景,导致该假设未被验证。
3. **本次修复的代码事实**:个人客户下单(`client_order`)时 `SellerCostPrice` 取店铺成本价;而第三方支付回调(`HandlePaymentCallback` / 钱包支付 `payOrderByWallet`)把 `ActualPaidAmount` 写成实付金额=零售价。因此个人客户与平台自营 offline 场景下 `ActualPaidAmount ≠ SellerCostPrice``paid_amount` 取前者即错误地快照了零售价。
## Goals / Non-Goals
**Goals:**
- 统一 4 个 `PackageUsage` 创建点的 `PaidAmount` 快照来源为 `order.SellerCostPrice`
- 修正 `model.PackageUsage.PaidAmount` 的字段注释以反映成本价语义。
**Non-Goals:**
- 不回填历史已写入错误的存量 `paid_amount`(用户明确要求历史数据保持原样)。
- 不新增迁移、不改 `retail_amount` 语义、不改资产套餐接口的按角色过滤逻辑。
- 不拆分订单级金额到逐套餐明细(多套餐订单的逐条成本拆分是既有话题,不在本变更范围)。
## Decisions
### 以 `order.SellerCostPrice` 作为 `PaidAmount` 的唯一来源
`paid_amount` 语义为「成本价」,而 `seller_cost_price` 在所有购买路径中都被填充为卖家向平台结算的成本价。选择直接取 `order.SellerCostPrice`,而非按 `buyer_type`/`purchase_role` 分支计算,因为分支计算需要重述订单价格逻辑,且 `seller_cost_price` 已是这些路径各自算好的结论值。
备选方案(已排除):
- 继续用 `actual_paid_amount` 并按 `buyer_type = personal` 特判改用 `seller_cost_price`:引入与订单侧重复的分支判断,且「平台自营 offline」场景`actual_paid_amount` 回退为零售价、`seller_cost_price = 0`)仍会错,覆盖不全。
-`PackageUsage` 新增独立 `seller_cost_price` 快照字段并保留 `paid_amount` 原义:需要新迁移,且 `paid_amount` 字段语义已被迁移 000150 与 DTO 定义为成本价,重复字段造成语义分裂。
### 4 个写入点统一取址赋值
`SellerCostPrice``int64``PaidAmount``*int64`,统一写 `PaidAmount: &order.SellerCostPrice`。4 个写入点必须同步修改,避免后台购买与自动购包两条链路继续产生不一致快照。
### 不写数据回填迁移
用户明确接受历史错误数据。保留迁移目录现状,不新增成对迁移;后续新产生的记录由修正后的代码正确写入。
## Risks / Trade-offs
- [历史错误 `paid_amount` 仍显示错误成本价] → 接受现状,仅修正新写入记录;如后续需要可另行发起数据修复。
- [多套餐订单的 `seller_cost_price` 是订单级合计] → 与现有 `paid_amount`/`retail_amount` 同为订单级快照,保持一致;逐套餐拆分不在本变更范围。
- [`SellerCostPrice` 在个别历史订单可能为 0 或不准确] → 本变更只改新写入逻辑,不触碰历史订单;新订单各路径均已填充该字段。