first commit
This commit is contained in:
174
plans/firstcashout_合并提现_837e2d44.plan.md
Normal file
174
plans/firstcashout_合并提现_837e2d44.plan.md
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
name: firstCashout 合并提现
|
||||
overview: 可以合并:推荐在 Withdraw 入口与 Pay 下单层统一,用 package_id 区分;活动档位状态机仍留在 FreeCreditsLogic,不能把 FC 逻辑硬塞进 WithdrawService::apply 主流程中间。
|
||||
todos:
|
||||
- id: dto-package-id
|
||||
content: WithdrawApplyDTO 增加 package_id;WithdrawValidator 增加 FC scene(package_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) 当前顺序(L164–219):
|
||||
|
||||
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`,再 Pay,catch 回滚
|
||||
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`
|
||||
Reference in New Issue
Block a user