# Phase 4: 退款 + 支付 + 运营修复 - Context

**Gathered:** 2026-03-28
**Status:** Ready for planning

<domain>
## Phase Boundary

实现完整退款流程（REFUND-01~03）、补全富友支付 JSAPI/小程序接口（PAY-01~04）、修复运营逻辑断点（OPS-01~02）。共 9 个需求：
- **REFUND**：新功能模块（退款申请→审批→佣金回扣）
- **PAY**：留桩替换（富友支付 JSAPI/小程序，平台代购场景修复）
- **OPS**：业务逻辑修复（卡/设备 status 3/4 触发、payment_status 枚举统一）

</domain>

<decisions>
## Implementation Decisions

### Plan 分组策略

- **D-01:** 拆分为 **2 个 Plan**：
  - **Plan 1（REFUND）**：REFUND-01 先行（DB 迁移 + Model + Store），然后串行完成 REFUND-02（7 个接口）+ REFUND-03（佣金扣减逻辑嵌入 Approve）
  - **Plan 2（PAY + OPS 合并）**：PAY-01~04 + OPS-01~02 共 6 个需求，两者互相独立可并行编排
- **D-02:** REFUND-01 必须先于 REFUND-02/03 执行（DB 迁移和模型是所有上层代码的基础）
- **D-03:** 每个子任务独立一个 commit（延续 Phase 1/2/3 粒度规范）

### 退款状态机（REFUND）

- **D-04:** 退款单号生成规则：`{order_no}-RF{seq}`（同一订单第 1 次退款为 RF1，第 2 次为 RF2，以此类推）。Create 时查询该订单现有退款数量 +1 作为序号
- **D-05:** 同一订单同时只允许一个"待处理"状态的退款申请（status=pending/returned），防止重复退款
- **D-06:** 退款状态流转：
  - `pending` → `approved`（审批通过，触发佣金扣减 + 更新订单 payment_status=4）
  - `pending` → `rejected`（审批拒绝，必填拒绝原因）
  - `pending` → `returned`（审批人退回，申请人可重提）
  - `returned` → `pending`（申请人 Resubmit 重提）
- **D-07:** **Return（退回）由审批人操作**，退回后状态变为 `returned`，申请人可调用 Resubmit 重新提交变回 `pending`
- **D-08:** **审批人可修改退款金额**（approved_refund_amount 可与 requested_refund_amount 不同）。佣金扣减以 approved_refund_amount 为准
- **D-09:** 审批通过后**同步更新订单 payment_status = 4**（已退款），与 OPS-02 的 payment_status 枚举统一对齐

### 退款佣金扣减（REFUND-03）

- **D-10:** 佣金扣减**内嵌在 Approve() 事务内同步执行**（不走 Asynq 异步任务）。原子性：退款审批通过、佣金扣减、订单状态更新在同一事务中完成，失败可回滚
- **D-11:** 佣金扣减算法（**禁用 float64，使用整数算术**）：
  ```go
  deductAmount := commission.Amount * refund.ApprovedRefundAmount / refund.ActualReceivedAmount
  ```
  先乘后除，全程 int64，防止整数除法精度丢失。对该订单所有佣金记录逐一计算扣减额，写负向交易流水
- **D-12:** 佣金扣减允许余额变为负数（规格书明确：允许为负数）

### 富友支付（PAY）

- **D-13:** `FuiouPayJSAPI` 和 `FuiouPayMiniApp` 函数签名需从 `error` 改为 `(*dto.FuiouPayJSAPIResponse, error)`，参考 `WechatPayJSAPI` 的返回结构
- **D-14:** 新建 `FuiouPayJSAPIResponse` DTO，字段从 `pkg/fuiou/types.go` 的 `WxPreCreateResponse` SDK 字段映射（SdkAppid, SdkTimestamp, SdkNoncestr, SdkPrepayid, SdkPackage, SdkSigntype, SdkPaysign），与微信官方 `wx.requestPayment()` 参数对齐
- **D-15:** JSAPI（公众号）tradeType = `JSAPI`；小程序 tradeType = `LETPAY`（两者逻辑相同，仅此字段不同）
- **D-16:** 已有富友 SDK 实现（`pkg/fuiou/client.go` + `wxprecreate.go`），直接调用 `client.WxPreCreate()` 方法

### 平台代理钱包代购（PAY-04）

- **D-17:** 修改 **`CreateAdminOrder`**（**不是 CreateLegacy**，CreateLegacy 已废弃，Handler 实际调用的是 CreateAdminOrder）
- **D-18:** 触发条件：`buyerType == ""` && `resourceShopID != nil`（平台用户在 Handler 层传入 buyerType="" buyerID=0）
- **D-19:** 钱包和成本价均来自**资产所属代理（*resourceShopID）**的钱包和成本价（等同于代理自购场景）
- **D-20:** 订单标记：`purchaseRole = purchased_by_platform`，`operatorType = "platform"`
- **D-21:** `sellerCostPrice = resourceShop 代理的成本价`（与代理自购相同，上下级佣金链照常计算）

### 卡/设备 status 3/4 触发（OPS-01）

- **D-22:** **status=3（已激活）**：在 `activation_service.go` 套餐激活事务内同步更新卡/设备表的 status=3（不走 goroutine，保证原子性）
- **D-23:** **status=4（已停用）**：在 `activation_service.go` 的套餐到期检测逻辑中，调用 `activateNextMainPackage` 后，若无法找到下一个待激活套餐（即无剩余 active/pending 套餐），则同步更新卡/设备 status=4
- **D-24:** 卡和设备**分开更新两张表**（tb_iot_card 和 tb_device），根据 carrierType 分支处理，无需统一封装
- **D-25:** status 更新在已有套餐激活/到期事务内完成，失败可回滚

### payment_status 枚举统一（OPS-02）

- **D-26:** **删除所有 C 端映射函数**：
  - `internal/service/client_order/service.go` 中的 `orderStatusToClientStatus()`
  - `internal/handler/app/client_order.go` 中的 `orderStatusToClientPaymentStatus()` 和 `clientPaymentStatusToOrderStatus()`
- **D-27:** C 端直接输出 DB 原始 `payment_status` 字段（1=待支付, 2=已支付, 3=已取消, 4=已退款），与管理端完全一致

### the Agent's Discretion

- 富友支付 `WxPreCreate` 调用时的 `goodsDesc` 字段内容（可从 order.OrderNo 或套餐名称组合）
- 退款 Store 层具体查询方法（参考现有 commission_withdrawal_request_store.go 风格）
- status=3/4 更新时的日志内容（按项目中文日志规范）
- `getCostPrice` 函数参数（已有现成封装，直接复用）

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### 修复规格（核心参考）
- `.sisyphus/plans/修正业务-完整方案.md` — 完整修复规格书：
  - 方案 I（REFUND-01~03）：第 1716 行起（退款表结构、7 个接口清单、佣金扣减逻辑）
  - 方案 J（PAY + OPS）：第 1862 行起（J-1/J-2/J-3/J-4 各子方案代码片段、人工验收清单）
    - J-1：富友支付 JSAPI/小程序（PAY-01/02/03）
    - J-2：平台钱包代购（PAY-04）—— **注意：规格书错误引用了 CreateLegacy（已废弃），实际修改目标是 CreateAdminOrder**
    - J-3：status 3/4 触发逻辑（OPS-01）
    - J-4：payment_status 枚举统一（OPS-02）

### 核心修改文件
- `internal/service/order/service.go` — FuiouPayJSAPI/FuiouPayMiniApp 留桩替换；CreateAdminOrder 钱包支付 platform 分支（PAY）
- `pkg/fuiou/client.go` + `pkg/fuiou/wxprecreate.go` — 富友 SDK（直接调用 WxPreCreate）
- `pkg/fuiou/types.go` — WxPreCreateResponse 字段（FuiouPayJSAPIResponse DTO 来源）
- `internal/service/package/activation_service.go` — 套餐激活/到期逻辑（OPS-01 插入点）
- `internal/service/client_order/service.go` — orderStatusToClientStatus() 删除（OPS-02）
- `internal/handler/app/client_order.go` — orderStatusToClientPaymentStatus() / clientPaymentStatusToOrderStatus() 删除（OPS-02）

### 参考结构（REFUND 设计参照）
- `internal/service/commission_withdrawal/service.go` — Approve/Reject 风格参考（REFUND-02 接口设计）
- `internal/model/dto/commission_withdrawal_dto.go` — DTO 字段风格参考（REFUND DTO 设计）
- `internal/store/postgres/commission_withdrawal_request_store.go` — Store 层风格参考（REFUND Store）

### 依赖项与约束
- `.planning/REQUIREMENTS.md` — REFUND-01~03, PAY-01~04, OPS-01~02 完整需求定义
- `internal/handler/admin/order.go` — PAY-04：Handler 层传入 buyerType="" buyerID=0 的逻辑（了解上下游）

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `pkg/fuiou/client.go:WxPreCreate()` — 富友预下单接口，PAY-01/02 直接调用，无需重写
- `internal/service/order/service.go:WechatPayJSAPI()` — 参考模板：验证 wechatPayment != nil → 查订单 → 调用 SDK → 返回 DTO
- `internal/service/commission_withdrawal/service.go:Approve()` — REFUND Approve 逻辑的最佳参考
- `internal/service/package/activation_service.go` — status=3/4 的插入位置：activateMainPackage（激活）+ 到期检测循环（到期）
- `internal/model/order.go:PurchaseRolePurchasedByPlatform` — PAY-04 购买角色常量已存在

### Established Patterns
- 佣金扣减：参考 `internal/service/commission_withdrawal/service.go` 的钱包扣款方式（使用 version 乐观锁）
- DB 迁移：双文件形式（`.up.sql` / `.down.sql`），编号接续现有最大值（当前 Phase 3 使用了 000090/91）
- 退款单号：参考提现单号的生成逻辑（WD+时间戳+随机数 → 改为 {order_no}-RF{seq}）
- 路由注册：所有新接口通过 `internal/routes/registry.go` 的 `Register()` 注册，并同步更新 `cmd/api/docs.go` 和 `cmd/gendocs/main.go`

### Integration Points
- REFUND Handler 注册到 `/api/admin/refunds` 路由组（参考 commission_withdrawal 的路由注册方式）
- PAY-04 修改 `CreateAdminOrder` 的 wallet 分支，Handler 层无需改动
- OPS-01/02 在 activation_service.go 和 client_order 两个文件内修改，无新增路由
- 富友支付 JSAPI/小程序的 Handler 已存在留桩（`internal/handler/app/`），替换 Service 层即可

</code_context>

<specifics>
## Specific Ideas

- **退款佣金允许负数**：规格书明确允许，代理佣金钱包余额可降至负值（为了确保退款公平性）
- **CreateLegacy 废弃问题**：规格书 J-2 错误引用了已废弃的 CreateLegacy，实际修改目标是 CreateAdminOrder（这一决策在讨论中由用户确认）
- **富友 SDK 已完整**：`pkg/fuiou/` 包含完整 client + types + notify + wxprecreate，PAY 工作量主要是替换留桩和编写 DTO

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope.

</deferred>

---

*Phase: 04-refund-pay-ops*
*Context gathered: 2026-03-28*
