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

182 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: firstCashout 合并代码评审
overview: 你对「merge post + 统一 WithdrawService::apply」的改法方向正确但当前 WithdrawService 在 package_id>0 时仍执行 checkInfo/手续费/黑规则,且 Pay 失败无回滚,会导致第一档提现不可用或卡在 processing。
todos:
- id: fc-early-return
content: WithdrawService::apply 中 package_id>0 走独立 applyFreeCreditsFirstCashout 并 early return
status: completed
- id: skip-checkinfo-fc
content: FC 分支跳过 checkInfo、getAmountAndFee、黑规则或产品确认保留项
status: completed
- id: pay-fail-rollback
content: Pay apply 失败时 handleFirstCashoutResult 回滚 processing
status: completed
- id: fc-withdrawal-info
content: FC 使用 package amount_qf、fee=0、auditType=2 组 WithdrawalInfo
status: completed
isProject: false
---
# firstCashout 合并改动 — 代码评审
## 你的改动(理解正确)
```mermaid
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)
```
- [`WithdrawController::apply`](slot_console/app/api/controller/WithdrawController.php):有 `package_id` 先 merge再**同一套** `validate($type)` — 符合预期。
- [`mergeFirstCashoutIntoPost`](slot_console/app/api/logic/FreeCreditsLogic.php):补 `amount` / `bizType` / `package_id` — 符合预期。
- [`firstCashout`](slot_console/app/api/logic/FreeCreditsLogic.php) 收窄为只 `markFirstCashoutProcessing` + 外部统一 `PayService::apply` — 思路可行。
---
## P0FC 仍会走 `checkInfo`,大概率直接失败
[`WithdrawService::apply`](slot_console/app/service/WithdrawService.php) 当前顺序:
```php
$bankInfo = $this->checkBankInfo($applyDTO);
$this->checkInfo($applyDTO->type, $applyDTO->amount); // 始终执行
// ...
if ($applyDTO->package_id > 0) {
firstCashout(...); // 永远走不到checkInfo 已抛错)
}
```
`checkInfo` 会校验**钱包可提现余额** ≥ amount[`WithdrawService.php` L333-335](slot_console/app/service/WithdrawService.php))。
Free Credits 第一档资金在**活动池**,不在普通 `withdraw` 余额里 → 典型报错 **`Insufficient balance`**。
**结论**:与需求「不进入普通钱包」冲突,第一档线上基本提不了现。
**建议**`package_id > 0`(或 `bizType === free_credit_first_cashout`)时 **跳过** `checkInfo`(及与之绑定的首提 min/max 规则)。
---
## P0Pay 失败时档位已置为 processing无回滚
你现在在 **Pay 之前** 调用 `firstCashout``markFirstCashoutProcessing`status=2
原实现是mark → `PayService::apply`**catch 时** `handleFirstCashoutResult($orderId, false)` 恢复 ready。
当前 [`WithdrawService::apply`](slot_console/app/service/WithdrawService.php) L236 调 pay **没有** try/catch 回滚 → 申请失败时 package 会一直 **processing**,用户无法重试。
**建议**
```php
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`](slot_console/app/api/logic/FreeCreditsLogic.php) 约定:
- `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 也做) |
---
## P1`firstCashout` 与 `markFirstCashoutProcessing` 重复校验
[`firstCashout`](slot_console/app/api/logic/FreeCreditsLogic.php) 内再次查 `STATUS_READY` 后调用 `markFirstCashoutProcessing`,而 `markFirstCashoutProcessing` ** again** 要求 `STATUS_READY`L343
逻辑重复但无害;可简化为只调 `markFirstCashoutProcessing`,或只保留一处校验。
注意:`mergeFirstCashoutIntoPost` 已校验 ready`firstCashout` 时若并发重复提交,第二次会在 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`](slot_console/app/api/controller/FreeCreditsController.php) 兼容路径与主入口一致:**正确**。
- pay 层仍靠 `bizType` 跳过 `withdrawFrozen`**只要 bizType 确实传到 pay 就没问题**(当前 L184 已设)。
---
## 推荐修复结构(最小 diff
在 [`WithdrawService::apply`](slot_console/app/service/WithdrawService.php) 开头(绑卡、频控之后):
```php
if ($applyDTO->package_id > 0) {
return $this->applyFreeCreditsFirstCashout($applyDTO, $bankInfo);
}
// 原有普通提现逻辑不变
```
`applyFreeCreditsFirstCashout` 内:
1. 跳过 `checkInfo`、黑规则、`getAmountAndFee`
2. `orderId``firstCashout`mark→ 组 `WithdrawalInfo`qf 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`,不冻结钱包