Files
cursor/plans/firstcashout_合并代码评审_b7afdfc6.plan.md
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

7.2 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
firstCashout 合并代码评审 你对「merge post + 统一 WithdrawService::apply」的改法方向正确但当前 WithdrawService 在 package_id>0 时仍执行 checkInfo/手续费/黑规则,且 Pay 失败无回滚,会导致第一档提现不可用或卡在 processing。
id content status
fc-early-return WithdrawService::apply 中 package_id>0 走独立 applyFreeCreditsFirstCashout 并 early return completed
id content status
skip-checkinfo-fc FC 分支跳过 checkInfo、getAmountAndFee、黑规则或产品确认保留项 completed
id content status
pay-fail-rollback Pay apply 失败时 handleFirstCashoutResult 回滚 processing completed
id content status
fc-withdrawal-info FC 使用 package amount_qf、fee=0、auditType=2 组 WithdrawalInfo completed
false

firstCashout 合并改动 — 代码评审

你的改动(理解正确)

sequenceDiagram
    participant Ctrl as WithdrawController
    participant FC as FreeCreditsLogic
    participant WS as WithdrawService
    participant Pay as slot_pay

    Ctrl->>FC: mergeFirstCashoutIntoPost
    Note over FC: amount + bizType + package_id
    Ctrl->>WS: apply(DTO)
    WS->>FC: firstCashout(uid, packageId, orderId)
    Note over FC: markFirstCashoutProcessing
    WS->>Pay: apply(WithdrawalInfo)

P0FC 仍会走 checkInfo,大概率直接失败

WithdrawService::apply 当前顺序:

$bankInfo = $this->checkBankInfo($applyDTO);
$this->checkInfo($applyDTO->type, $applyDTO->amount);  // 始终执行
// ...
if ($applyDTO->package_id > 0) {
    firstCashout(...);  // 永远走不到checkInfo 已抛错)
}

checkInfo 会校验钱包可提现余额 ≥ amountWithdrawService.php L333-335)。
Free Credits 第一档资金在活动池,不在普通 withdraw 余额里 → 典型报错 Insufficient balance

结论:与需求「不进入普通钱包」冲突,第一档线上基本提不了现。

建议package_id > 0(或 bizType === free_credit_first_cashout)时 跳过 checkInfo(及与之绑定的首提 min/max 规则)。


P0Pay 失败时档位已置为 processing无回滚

你现在在 Pay 之前 调用 firstCashoutmarkFirstCashoutProcessingstatus=2

原实现是mark → PayService::applycatch 时 handleFirstCashoutResult($orderId, false) 恢复 ready。

当前 WithdrawService::apply L236 调 pay 没有 try/catch 回滚 → 申请失败时 package 会一直 processing,用户无法重试。

建议

if ($applyDTO->package_id > 0) {
    $orderId = CommonFn::generateOrderId(4, $userTag->uid);
    (new FreeCreditsLogic())->firstCashout($userTag->uid, $applyDTO->package_id, $orderId);
    $withdrawalInfo->orderId = $orderId;
    $withdrawalInfo->bizType = FreeCreditsLogic::BIZ_TYPE_FIRST_CASHOUT;
    try {
        $res = PayService::getInstance()->apply($withdrawalInfo->toArray());
        return $res;
    } catch (\Throwable $e) {
        (new FreeCreditsLogic())->handleFirstCashoutResult($orderId, false);
        throw $e;
    }
}

FC 分支应 early return,不要继续走下面黑规则 + getAmountAndFee。)


P1FC 仍走普通手续费 / 黑规则 / auditType

合并后 FC 路径仍执行:

  • getAuditType($applyDTO->amount)BlackApiService、提现倍数检测
  • getAmountAndFee($applyDTO->amount, $this->withdrawal) — 可能扣手续费、按钱包余额改金额

firstCashout 约定:

  • fee = 0
  • auditType = 2(人工审核)
  • amount = package->amount_qf(千分位,不经手续费逻辑)

merge 写入的 amount展示大单位getNumberFormat(qf)),再经 getAmountAndFee 可能与 pay 侧期望的千分位不一致。

建议FC 分支单独组 WithdrawalInfo

字段 FC 取值
amount 从 package 读 amount_qf(或 merge 时额外缓存 _amount_qf
fee 0
auditType 2
bizType free_credit_first_cashout
黑规则 跳过(或产品确认是否要对 FC 也做)

P1firstCashoutmarkFirstCashoutProcessing 重复校验

firstCashout 内再次查 STATUS_READY 后调用 markFirstCashoutProcessing,而 markFirstCashoutProcessing ** again** 要求 STATUS_READYL343
逻辑重复但无害;可简化为只调 markFirstCashoutProcessing,或只保留一处校验。

注意:mergeFirstCashoutIntoPost 已校验 readyfirstCashout 时若并发重复提交,第二次会在 mark 阶段失败 — 符合「处理中不可重复提交」。


P2行为变化需产品确认

原 FC 现统一 apply
is_bind_name 不校验 校验L157
Redis 5s 频控
返回值 {order_id, amount} pay apply 原始结构

若 C 端依赖 data.order_id / data.amount 展示,需确认 pay 返回是否一致。


做得好的部分

  • Controller 单入口 + merge post正确
  • 去掉独立 FC validator scene正确
  • FreeCreditsController::firstCashout 兼容路径与主入口一致:正确
  • pay 层仍靠 bizType 跳过 withdrawFrozen只要 bizType 确实传到 pay 就没问题(当前 L184 已设)。

推荐修复结构(最小 diff

WithdrawService::apply 开头(绑卡、频控之后):

if ($applyDTO->package_id > 0) {
    return $this->applyFreeCreditsFirstCashout($applyDTO, $bankInfo);
}
// 原有普通提现逻辑不变

applyFreeCreditsFirstCashout 内:

  1. 跳过 checkInfo、黑规则、getAmountAndFee
  2. orderIdfirstCashoutmark→ 组 WithdrawalInfoqf amount, fee=0, auditType=2, bizType
  3. try/catch pay + handleFirstCashoutResult 回滚

不要在普通流程中间用 if ($package_id > 0) { firstCashout; } 再接着跑普通逻辑 — 这是当前问题的根源。


验收清单

  • package_id>0 且钱包 withdraw=0能成功提交 pay不报 Insufficient balance
  • pay 申请失败package 回到 ready可再次提现
  • pay 成功 + 回调package completedplayer FIRST_CASH_DONE
  • 普通提现无 package_id:行为与改前一致
  • remark/bizType 在 pay 侧仍为 free_credit_first_cashout,不冻结钱包