Files
cursor/plans/firstcashout_合并提现_837e2d44.plan.md
2026-05-21 18:16:26 +08:00

7.4 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
firstCashout 合并提现 可以合并:推荐在 Withdraw 入口与 Pay 下单层统一,用 package_id 区分;活动档位状态机仍留在 FreeCreditsLogic不能把 FC 逻辑硬塞进 WithdrawService::apply 主流程中间。
id content status
dto-package-id WithdrawApplyDTO 增加 package_idWithdrawValidator 增加 FC scenepackage_id 替代 amount completed
id content status
withdraw-fc-branch WithdrawService::apply 增加 applyFreeCreditsFirstCashout early return跳过 checkInfo/手续费/黑规则) completed
id content status
controller-unify WithdrawController::apply 统一入口FreeCreditsController::firstCashout 改为兼容 alias completed
id content status
bank-persist FC 分支复用 checkBankInfo与普通提现绑卡行为一致 completed
id content status
pay-rollback-test 补单测/集成测FC 无 withdraw 余额可提交、Pay 失败回滚 ready completed
false

firstCashout 能否合并进现有提现逻辑

结论(后端)

可以合并,且推荐合并「入口 + 校验 + 绑卡 + Pay 下单」;不应把 Free Credits 编排塞进 WithdrawService::apply 主流程中间。

当前仓库状态:尚未合并——仍是双入口:

入口 现状
POST /api/free-credits/first-cashout FreeCreditsLogic::firstCashout 完整编排
POST /api/withdraw/apply WithdrawService::apply,无 package_id

Pay 层已经统一:两类提现最终都走 WithdrawalOrderEntity::apply,靠 bizType=free_credit_first_cashout 跳过 withdrawFrozen 并走独立 MQ 回调。


为什么「可以」合并

C 端参数与需求 §11 一致:独立提现页 = 普通提现页精简版,收款字段相同,仅差:

字段 普通提现 Free Credits 第一档
amount 用户输入 不传,服务端取 package
package_id 必填

因此用 同一 POST /api/withdraw/apply + 可选 package_id 区分业务是合理契约。


为什么不能「整段并入」WithdrawService::apply

WithdrawService::apply 当前顺序L164219

  1. checkBankInfo
  2. checkInfo(校验钱包 withdraw 余额 ≥ amount
  3. 黑规则、getAmountAndFee(手续费)
  4. PayService::apply

Free Credits 第一档资金在活动池,不在普通 withdraw 余额。若在中间插入 firstCashout 而不 early return,会先被 checkInfo 打成 Insufficient balance(评审计划已记录为 P0

此外 FC 固定规则与普通提现不同:

普通提现 FC 第一档
金额来源 用户 amount package->amount_qf
手续费 getAmountAndFee 0
审核 动态 getAuditType + 黑规则 auditType=2
钱包冻结 pay 侧 bizType 分支)
活动状态 markFirstCashoutProcessing / 失败回滚
Pay 失败回滚 钱包解冻 handleFirstCashoutResult 恢复 ready

这些差异属于 Logic 编排,符合 backend-layering:不应把 FreeCreditsLogic 整段搬进 WithdrawService 当「又一层 Service」。


推荐合并结构

flowchart TB
    Client["POST /api/withdraw/apply"]
    Client --> Branch{package_id 存在?}
    Branch -->|否| Normal["WithdrawService::apply 原逻辑"]
    Branch -->|是| FC["WithdrawService::applyFreeCreditsFirstCashout"]
    FC --> Mark["FreeCreditsLogic::markFirstCashoutProcessing"]
    FC --> Pay["PayService::apply bizType=free_credit_first_cashout"]
    Normal --> Pay2["PayService::apply 普通"]
    Pay --> PayEntity["slot_pay WithdrawalOrderEntity"]
    Pay2 --> PayEntity

合并层(推荐做)

  1. WithdrawController::apply

    • package_id → 走 FC 分支(或 DTO 带 package_id 后交给 Service early return
    • package_id → 现有普通提现
  2. WithdrawValidator

    • 新增 FC scene与普通 apply 对称,把 amount 换成 package_idSCENE_APPLY_CASH_FC 等)
  3. WithdrawApplyDTO

    • 增加可选 package_id
  4. 绑卡

    • FC 复用 WithdrawService::checkBankInfo(当前 firstCashout 只读绑卡、不写库,合并后应对齐普通提现)
  5. FreeCreditsController::firstCashout

    • 保留为 兼容 alias(内部转调 withdraw apply或标记 deprecated

保持分离(必须)

  • FreeCreditsLogicmarkFirstCashoutProcessinghandleFirstCashoutResult、package 状态机、assertEligibleParticipant
  • WithdrawService::applyFreeCreditsFirstCashout(新建私有方法):
    • 跳过 checkInfo、黑规则、getAmountAndFee
    • amount = package->amount_qffee = 0auditType = 2
    • try/catch Pay失败时 handleFirstCashoutResult($orderId, false)

已统一、无需再改

  • slot_paybizType / remark 分支跳过冻结、Success/Fail/Rejected → Console MQ
  • slot_console EventBusFreeCreditsFirstCashoutSuccess/Fail/RejectedhandleFirstCashoutResult

不推荐的做法

WithdrawService::apply 中间写:

$this->checkInfo(...);
// ...
if ($package_id > 0) {
    (new FreeCreditsLogic())->firstCashout(...);
}
$amountInfo = $this->getAmountAndFee(...);
PayService::apply(...);

会导致余额校验失败、手续费错误、Pay 失败不回滚档位processing 卡死)。


与「单独提现」需求的关系

需求 合并后是否满足
不进入普通钱包§15.3 是,仍靠 pay bizType
固定第一档金额§11.3 是,服务端取 package
处理中不可重复提交§5.6 是,仍由 package status 控制
成功/失败/拒绝状态§20.2 是,回调逻辑不变

合并的是 HTTP 入口与收款参数校验,不是改掉「独立提现」的账务语义。


实施要点(若执行)

  1. WithdrawApplyDTO 增加 package_id
  2. WithdrawService::apply 在绑卡 + Redis 频控后 early returnapplyFreeCreditsFirstCashout
  3. FC 分支内:先 markFirstCashoutProcessing,再 Paycatch 回滚
  4. 单测FC 在 withdraw=0 时可提交Pay 失败 package 回 ready普通 apply 无回归
  5. 文档:/api/free-credits/first-cashout → 指向 /api/withdraw/apply + package_id

验收清单

  • package_id>0 且钱包可提现余额为 0可成功提交 pay
  • pay 申请失败package 回到 ready,可重试
  • pay 回调成功package completedplayer FIRST_CASH_DONE
  • 不传 package_id:普通提现与现网一致
  • pay 订单 remark 仍为 free_credit_first_cashout,不触发 withdrawFrozen