All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m6s
10 KiB
10 KiB
Phase 4: 退款 + 支付 + 运营修复 - Context
Gathered: 2026-03-28 Status: Ready for planning
## Phase Boundary实现完整退款流程(REFUND-0103)、补全富友支付 JSAPI/小程序接口(PAY-0104)、修复运营逻辑断点(OPS-01~02)。共 9 个需求:
- REFUND:新功能模块(退款申请→审批→佣金回扣)
- PAY:留桩替换(富友支付 JSAPI/小程序,平台代购场景修复)
- OPS:业务逻辑修复(卡/设备 status 3/4 触发、payment_status 枚举统一)
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-0102 共 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,使用整数算术):
先乘后除,全程 int64,防止整数除法精度丢失。对该订单所有佣金记录逐一计算扣减额,写负向交易流水
deductAmount := commission.Amount * refund.ApprovedRefundAmount / refund.ActualReceivedAmount - D-12: 佣金扣减允许余额变为负数(规格书明确:允许为负数)
富友支付(PAY)
- D-13:
FuiouPayJSAPI和FuiouPayMiniApp函数签名需从error改为(*dto.FuiouPayJSAPIResponse, error),参考WechatPayJSAPI的返回结构 - D-14: 新建
FuiouPayJSAPIResponseDTO,字段从pkg/fuiou/types.go的WxPreCreateResponseSDK 字段映射(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函数参数(已有现成封装,直接复用)
<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-0103, PAY-0104, 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 → 返回 DTOinternal/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>
## Specific Ideas- 退款佣金允许负数:规格书明确允许,代理佣金钱包余额可降至负值(为了确保退款公平性)
- CreateLegacy 废弃问题:规格书 J-2 错误引用了已废弃的 CreateLegacy,实际修改目标是 CreateAdminOrder(这一决策在讨论中由用户确认)
- 富友 SDK 已完整:
pkg/fuiou/包含完整 client + types + notify + wxprecreate,PAY 工作量主要是替换留桩和编写 DTO
None — discussion stayed within phase scope.
Phase: 04-refund-pay-ops Context gathered: 2026-03-28