# 备忘录:代理退款扣回佣金/冻结优化(策略 2 + 策略 5 改版) > 状态:**待实施** > 目的:后续改造 `CancelAgentCommission` 及后台/回调退款链路时,按本文规则实现,避免共享冻结池误扣、部分退款与扣佣口径不一致。 --- ## 一、策略 2:按「本笔佣金」扣款,先冻结、再余额 ### 1.1 问题(现状) - 撤销时使用钱包级「先扣总 `FrozenBalance`、再扣 `Balance`」,可能动到**其他订单**的冻结。 - 解冻任务与提现占用冻结混在同一字段时,易出现对账失败、负冻结等。 ### 1.2 目标行为(实施后) 对**单笔订单**下、待冲回的那笔**直推佣金**(`agent_commission`,且状态为已发放、未被整笔撤销): 1. **若该笔佣金存在仍有效的冻结任务**(与 `commission_id` 关联、`agent_freeze_task.status == 1` 待解冻): - 先从**该任务对应的冻结金额**(`FreezeAmount`,且只从**本笔佣金归属的冻结额度**扣,见下文物理解耦建议)中扣减,**最多扣 `FreezeAmount`**; - **剩余冲回金额** = 本次要对这笔佣金冲回的总金额 − 已从冻结部分扣掉的部分,**全部从 `Balance`(可用余额)扣**。 2. **若该笔佣金没有有效冻结任务**(无任务或未达冻结门槛等): - **本次冲回金额全部从 `Balance` 扣**。 3. **禁止**再用「整钱包 `FrozenBalance` 池先垫满再扣余额」的方式处理**单笔**佣金冲回(除非产品明确采用单字段且不拆分,也应在文档中标记为技术债)。 ### 1.3 工程建议(与策略 2 配套) - **优先**:将「佣金冻结」与「占用的提现冻结」分字段或子账(如 `commission_frozen` / `withdrawal_frozen`),冲回、到期解冻只动佣金侧。 - **至少**:冲回前按 `commission_id` + 任务锁定本单冻结份额;扣款顺序仅在本份额与 `Balance` 之间分配,不侵占其他 `agent_freeze_task` 对应额度。 ### 1.4 部分冲回后的冻结任务(需定义) - 若本次只冲回部分金额:是否**调减** `agent_freeze_task.FreezeAmount`、或拆任务、或标记「已部分冲回」,需在开发时定表结构和状态机,避免到期解冻仍按原 `FreezeAmount` 全额解冻。 --- ## 二、策略 5 改版:部分退款 **不按比例**,而是 **「退多少扣多少」,且封顶佣金** ### 2.1 规则(用户指定) - **不按**「退款金额 / 订单金额 × 佣金」这类比例。 - 设: - `R` = **本笔后台/回调确认的本次退款金额**(元); - `C_direct` = 代理在该订单上**当前仍有效的直推佣金**可冲回上限(已发放未撤销的 `agent_commission.amount`,或按「剩余未冲回」口径若支持多次退款累计,见 2.3); - (返佣侧见 2.4)`C_rebate_i` = 各条 `agent_rebate` 可冲回上限。 则 **对直推佣金** 本次应冲回金额: ```text claw_direct = min(R, C_direct) ``` **示例**: - 订单 100 元,本次退款 50 → **`claw_direct = min(50, C_direct)`**;若 `C_direct = 80`,则冲 **50**(不是 50%×80)。 - 订单 100 元,本次退款 95,`C_direct = 80` → **`claw_direct = min(95, 80) = 80`**(封顶佣金)。 ### 2.2 与策略 2 的结合 对计算出的 `claw_direct`: - 再按**第一节**拆成:有冻结任务则先扣满该任务可承担的冻结份额,余下从 `Balance`;无冻结则全部从 `Balance`。 ### 2.3 多次退款(累计) - 若同一订单可**多次退款**:需约定 `C_direct` 是「原始佣金」还是「剩余可冲回」。 - **建议**:维护「本单已冲回佣金累计」或等价地更新 `agent_commission` 剩余金额/子状态,使每次: ```text claw_direct = min(R, max(0, C_direct - 已累计已冲回)) ``` 具体字段是否新增 `revoked_amount`、是否只依赖多条 `order_refund` 汇总,实施时定案。 ### 2.4 返佣(`agent_rebate`) - 用户口头规则主要针对「佣金」;**返佣是否沿用同一公式**需在实施前产品拍板: - **方案 A**:对每条返佣单独 `claw_rebate_i = min(R, cap_i)`,且注意 `R` 与直推已分摊是否重复扣(通常应 **`R` 全局一次,再在直推与多条返佣间分配**,否则 `min(R,C)` 对多受益方会超标)。 - **方案 B**:`R` 先在直推与返佣链上按约定顺序分配(例如先扣直推至封顶,再扣上级返佣至各自封顶),总额不超过 `R` 且各方不超过各自佣金上限。 **备忘录约束**:实施前必须写清 **「一笔退款 R 在直推与多返佣之间的分配顺序与上限」**,避免多扣或少扣。 ### 2.5 调用链改造要点 - `CancelAgentCommission` 需接收 **`refund_amount`(或累计上下文)**,不能只传 `order_id` 默认全额冲回。 - 后台 `AdminRefundOrder`、微信退款成功回调等,在退款成功后调用冲回逻辑时传入**本次** `R`。 - 支付宝/微信**全额**退款:若 `R >=` 订单剩余实付等,则直推/返佣可按封顶一次性冲完(与现有「整单撤销」行为对齐时,等价于 `claw = min(R, C)` 且 `R` 足够大)。 --- ## 三、实施检查清单(供开发自用) - [ ] `giveAgentCommission` / 冻结任务与钱包字段是否需调整以支持「只扣本笔冻结份额」。 - [ ] `deductCommissionFromWallet` 重写:入参含 `claw_amount` + `freeze_task` + 分桶或任务份额。 - [ ] 返佣 `deductRebateFromWallet`:是否按 `min(R_remaining, rebate_cap)` 及分配顺序。 - [ ] 部分冲回后 `agent_commission.status`:仍为已发放 / 部分撤销 / 新状态;与统计、列表过滤一致。 - [ ] 多次退款与 `order_refund` 记录一一对应,冲回幂等与审计日志。 - [ ] 提现、定时解冻与冲回共用冻结时的边界用例(负冻结防护)。 --- ## 四、参考现状代码(实施时对照) - 冲回入口:`AgentService.CancelAgentCommission`、`deductCommissionFromWallet`、`deductRebateFromWallet` - 退款后调用:`admin_order/adminrefundorderlogic.go`、`pay/wechatpayrefundcallbacklogic.go` 等 --- *文档版本:初稿(按对话需求整理),实施时可增删小节并与产品确认返佣分配。*