Files
cursor/plans/firstcashout_合并提现_837e2d44.plan.md
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

175 lines
7.4 KiB
Markdown
Raw Permalink 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: 可以合并:推荐在 Withdraw 入口与 Pay 下单层统一,用 package_id 区分;活动档位状态机仍留在 FreeCreditsLogic不能把 FC 逻辑硬塞进 WithdrawService::apply 主流程中间。
todos:
- id: dto-package-id
content: WithdrawApplyDTO 增加 package_idWithdrawValidator 增加 FC scenepackage_id 替代 amount
status: completed
- id: withdraw-fc-branch
content: WithdrawService::apply 增加 applyFreeCreditsFirstCashout early return跳过 checkInfo/手续费/黑规则)
status: completed
- id: controller-unify
content: WithdrawController::apply 统一入口FreeCreditsController::firstCashout 改为兼容 alias
status: completed
- id: bank-persist
content: FC 分支复用 checkBankInfo与普通提现绑卡行为一致
status: completed
- id: pay-rollback-test
content: 补单测/集成测FC 无 withdraw 余额可提交、Pay 失败回滚 ready
status: completed
isProject: false
---
# firstCashout 能否合并进现有提现逻辑
## 结论(后端)
**可以合并,且推荐合并「入口 + 校验 + 绑卡 + Pay 下单」;不应把 Free Credits 编排塞进 [`WithdrawService::apply`](slot_console/app/service/WithdrawService.php) 主流程中间。**
当前仓库状态:**尚未合并**——仍是双入口:
| 入口 | 现状 |
|------|------|
| [`POST /api/free-credits/first-cashout`](slot_console/app/api/controller/FreeCreditsController.php) | `FreeCreditsLogic::firstCashout` 完整编排 |
| [`POST /api/withdraw/apply`](slot_console/app/api/controller/WithdrawController.php) | `WithdrawService::apply`,无 `package_id` |
Pay 层**已经统一**:两类提现最终都走 [`WithdrawalOrderEntity::apply`](slot_pay/app/entity/WithdrawalOrderEntity.php),靠 `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`](slot_console/app/service/WithdrawService.php) 当前顺序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`](file:///Users/ray/.cursor/rules/backend-layering.mdc):不应把 `FreeCreditsLogic` 整段搬进 `WithdrawService` 当「又一层 Service」。
---
## 推荐合并结构
```mermaid
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`](slot_console/app/api/controller/WithdrawController.php)**
-`package_id` → 走 FC 分支(或 DTO 带 `package_id` 后交给 Service early return
-`package_id` → 现有普通提现
2. **[`WithdrawValidator`](slot_console/app/api/validator/WithdrawValidator.php)**
- 新增 FC scene与普通 apply 对称,把 `amount` 换成 `package_id``SCENE_APPLY_CASH_FC` 等)
3. **[`WithdrawApplyDTO`](slot_console/app/api/dto/request/WithdrawApplyDTO.php)**
- 增加可选 `package_id`
4. **绑卡**
- FC 复用 `WithdrawService::checkBankInfo`(当前 `firstCashout` 只读绑卡、不写库,合并后应对齐普通提现)
5. **[`FreeCreditsController::firstCashout`](slot_console/app/api/controller/FreeCreditsController.php)**
- 保留为 **兼容 alias**(内部转调 withdraw apply或标记 deprecated
### 保持分离(必须)
- [`FreeCreditsLogic`](slot_console/app/api/logic/FreeCreditsLogic.php)`markFirstCashoutProcessing``handleFirstCashoutResult`、package 状态机、`assertEligibleParticipant`
- [`WithdrawService::applyFreeCreditsFirstCashout`](slot_console/app/service/WithdrawService.php)(新建私有方法):
- 跳过 `checkInfo`、黑规则、`getAmountAndFee`
- `amount = package->amount_qf``fee = 0``auditType = 2`
- try/catch Pay失败时 `handleFirstCashoutResult($orderId, false)`
### 已统一、无需再改
- **slot_pay**`bizType` / `remark` 分支跳过冻结、Success/Fail/Rejected → Console MQ
- **slot_console EventBus**`FreeCreditsFirstCashoutSuccess/Fail/Rejected``handleFirstCashoutResult`
---
## 不推荐的做法
在 [`WithdrawService::apply`](slot_console/app/service/WithdrawService.php) 中间写:
```php
$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 return**`applyFreeCreditsFirstCashout`
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 `completed`player `FIRST_CASH_DONE`
- 不传 `package_id`:普通提现与现网一致
- pay 订单 `remark` 仍为 `free_credit_first_cashout`,不触发 `withdrawFrozen`