Files
ycc-proxy-server/docs/memo-agent-refund-commission-clawback.md
2026-05-19 20:30:32 +08:00

6.1 KiB
Raw Blame History

备忘录:代理退款扣回佣金/冻结优化(策略 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.4C_rebate_i = 各条 agent_rebate 可冲回上限。

对直推佣金 本次应冲回金额:

 claw_direct = min(R, C_direct)

示例

  • 订单 100 元,本次退款 50 → claw_direct = min(50, C_direct);若 C_direct = 80,则冲 50(不是 50%×80
  • 订单 100 元,本次退款 95C_direct = 80claw_direct = min(95, 80) = 80(封顶佣金)。

2.2 与策略 2 的结合

对计算出的 claw_direct

  • 再按第一节拆成:有冻结任务则先扣满该任务可承担的冻结份额,余下从 Balance;无冻结则全部从 Balance

2.3 多次退款(累计)

  • 若同一订单可多次退款:需约定 C_direct 是「原始佣金」还是「剩余可冲回」。
  • 建议:维护「本单已冲回佣金累计」或等价地更新 agent_commission 剩余金额/子状态,使每次:
 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) 对多受益方会超标)。
    • 方案 BR 先在直推与返佣链上按约定顺序分配(例如先扣直推至封顶,再扣上级返佣至各自封顶),总额不超过 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.CancelAgentCommissiondeductCommissionFromWalletdeductRebateFromWallet
  • 退款后调用:admin_order/adminrefundorderlogic.gopay/wechatpayrefundcallbacklogic.go

文档版本:初稿(按对话需求整理),实施时可增删小节并与产品确认返佣分配。