Files
cursor/plans/第一档提现审核拒绝_843062ca.plan.md
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

175 lines
8.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: 第一档提现审核拒绝
overview: 补齐管理后台「拒绝退钱/拒绝扣钱」对 Free Credits 第一档独立提现free_credit_first_cashout的处理Pay 侧识别业务类型、避免误动钱包,并通过已有 Console 总线回调恢复档位状态。
todos:
- id: persist-biz-type
content: WithdrawalOrderEntity::creatOrder 写入 data_snapshot.biz_type新增 isFreeCreditFirstCashout 判断
status: in_progress
- id: apply-skip-freeze
content: apply() 第一档跳过 withdrawFrozen 与 incTodayWithdrawalInfo
status: pending
- id: audit-reject-branch
content: audit() 拒绝退钱/扣钱:第一档跳过钱包,发 Fail/Rejected 总线;普通单保持原逻辑
status: pending
- id: mq-constants
content: slot_lib MQBusEntity 增加三条 FreeCredits 事件常量
status: pending
- id: event-withdrawal
content: EventWithdrawal 成功/失败按 biz_type 发 Success/Fail 总线,避免误 withdrawSuccess/Fail
status: pending
- id: tests
content: 补充 slot_pay 单测 + 手工验证后台拒绝退钱/扣钱
status: pending
isProject: false
---
# 第一档提现:后台审核拒绝处理
## 问题确认
管理后台 [`slot_admin_vue/.../withdrawal/index.vue`](backend/slot_admin_vue/src/views/game/order/withdrawal/index.vue) 点「拒绝退钱」(`audit_status=2`) / 「拒绝扣钱」(`audit_status=3`) → [`WithdrawalOrderController::audit`](backend/slot_admin/app/game/controller/WithdrawalOrderController.php) → `PayService::withdrawalAudit` → [`WithdrawalOrderEntity::audit`](slot_pay/app/entity/WithdrawalOrderEntity.php)。
当前 `audit()` **一律**对普通提现调用 `withdrawFail` / `withdrawDone`**不**通知 `slot_console``FreeCreditsLogic::handleFirstCashoutResult`
第一档提现([`FreeCreditsLogic::applyFirstCashoutWithdraw`](slot_console/app/api/logic/FreeCreditsLogic.php)`bizType=free_credit_first_cashout`)在提交时已 `markFirstCashoutProcessing`,审核拒绝后档位会卡在 **processing**C 端无法再次提现。
需求文档约定([§20.2](docs/requirements/首充前免费余额定格与分档释放需求文档.md)
| 后台操作 | 产品语义 | 应对 `handleFirstCashoutResult` |
|----------|----------|----------------------------------|
| 拒绝退钱 | 失败,可重试 | `success=false, rejected=false` → 档位 **ready** |
| 拒绝扣钱 | 风控拒绝 | `success=false, rejected=true` → 档位 **rejected** |
`slot_console` 已有消费端([`EventBus.php`](slot_console/app/command/EventBus.php) L133140**全库无生产端** 发送 `FreeCreditsFirstCashoutFail` / `Rejected` / `Success`
```mermaid
flowchart TB
subgraph today [现状]
adminReject[后台拒绝退钱]
payAudit[Pay audit]
walletFail[withdrawFail]
adminReject --> payAudit --> walletFail
end
subgraph target [目标]
adminReject2[后台拒绝退钱]
payAudit2[Pay audit 识别 bizType]
skipWallet[跳过钱包退冻]
mqFail[MQ FreeCreditsFirstCashoutFail]
fcLogic[handleFirstCashoutResult ready]
adminReject2 --> payAudit2 --> skipWallet
payAudit2 --> mqFail --> fcLogic
end
```
## 改造范围(主改 `slot_pay`,常量补 `slot_lib`
### 1. 订单落库时持久化 `biz_type`
文件:[`slot_pay/app/entity/WithdrawalOrderEntity.php`](slot_pay/app/entity/WithdrawalOrderEntity.php) — `creatOrder()`
-`WithdrawalInfo::$bizType` 非空,写入 `data_snapshot.biz_type`(表无独立字段,用现有 JSON 即可)。
- 常量与 C 端一致:`free_credit_first_cashout`(见 [`FreeCreditsLogic::BIZ_TYPE_FIRST_CASHOUT`](slot_console/app/api/logic/FreeCreditsLogic.php))。
新增私有方法(同文件):
```php
private function isFreeCreditFirstCashout(?WithdrawalInfo $info = null, ?self $order = null): bool
```
- 优先读 `$info->bizType`;审核/回调阶段读 `$order->data_snapshot['biz_type']`
### 2. 申请提现:第一档不冻普通钱包
文件:同上 — `apply()`
- `isFreeCreditFirstCashout($info)` 为 true 时:
- **跳过** `withdrawFrozen``UserTagService::incTodayWithdrawalInfo`
-`creatOrder`、人工审核分支、`WebSocketMqService::sendWithdrawalUrl` 保持不变。
- `catch` 中仅当曾冻结时才 `withdrawFail`(与现逻辑一致,第一档不会进入)。
对齐 [`WithdrawalInfo` 注释](slot_lib/src/entity/data/WithdrawalInfo.php) 与需求「第一档不进入普通钱包 withdraw」。
### 3. 审核拒绝/扣钱:分支 + 发总线(核心)
文件:同上 — `audit()`
`audit_status` 设为 2 或 3 后:
| 条件 | 钱包 | Console 总线 `type` | `handleFirstCashoutResult` |
|------|------|---------------------|----------------------------|
| 普通提现 + 拒绝退钱 | `withdrawFail` | 无 | — |
| 普通提现 + 拒绝扣钱 | `withdrawDone` | 无 | — |
| 第一档 + 拒绝退钱 | **不调**钱包;**不调** `decTodayWithdrawalInfo` | `FreeCreditsFirstCashoutFail` | ready可重提 |
| 第一档 + 拒绝扣钱 | **不调**钱包 | `FreeCreditsFirstCashoutRejected` | rejected |
实现方式Pay 依赖已有 `ConsoleMqService`
```php
use slotLib\common\mq\ConsoleMqService;
// audit 拒绝分支内:
if ($this->isFreeCreditFirstCashout(null, $order)) {
$event = $type === self::AUDIT_STATUS_REVIEW
? MQBusEntity::TYPE_FREE_CREDITS_FIRST_CASHOUT_FAIL
: MQBusEntity::TYPE_FREE_CREDITS_FIRST_CASHOUT_REJECTED;
ConsoleMqService::getInstance()->sendConsoleBusEvent($order->uid, $event, [
'order_id' => $order->order_id,
]);
} else {
// 现有 withdrawFail / withdrawDone + decTodayWithdrawalInfo
}
```
站内信逻辑可保留(第一档同样通知用户)。
### 4. 总线常量集中到 `slot_lib`
文件:[`slot_lib/src/entity/mq/MQBusEntity.php`](slot_lib/src/entity/mq/MQBusEntity.php)
新增(与 console 现有字符串一致):
- `TYPE_FREE_CREDITS_FIRST_CASHOUT_SUCCESS = 'FreeCreditsFirstCashoutSuccess'`
- `TYPE_FREE_CREDITS_FIRST_CASHOUT_FAIL = 'FreeCreditsFirstCashoutFail'`
- `TYPE_FREE_CREDITS_FIRST_CASHOUT_REJECTED = 'FreeCreditsFirstCashoutRejected'`
[`slot_console/app/entity/mq/MQBusEntity.php`](slot_console/app/entity/mq/MQBusEntity.php) 可改为 `use slotLib\entity\mq\MQBusEntity as LibMQBusEntity` 引用常量(可选,避免双份字符串)。
### 5. 顺带补齐:打款结果回调(建议同 PR
[`EventWithdrawal::updateOrder()`](slot_pay/app/command/EventWithdrawal.php) 在成功/失败时同样未区分第一档,会误调 `withdrawSuccess` / `withdrawFail`
在同一 PR 中按 `data_snapshot.biz_type` 分支:
- 成功 → 发 `FreeCreditsFirstCashoutSuccess`**不** `withdrawSuccess`
- 失败 → 发 `FreeCreditsFirstCashoutFail`**不** `withdrawFail`
- 普通单保持现状
否则后台「通过」后渠道失败/成功,档位状态仍会错乱。
## 不改动的部分
- **管理后台 UI**:无需改,仍调 `audit(record, 2|3)`
- **`FreeCreditsLogic::handleFirstCashoutResult`**:逻辑已满足需求,只补 Pay 侧触发。
- **数据库 DDL**:用 `data_snapshot`,无需加列。
## 历史订单
已产生、且 `data_snapshot``biz_type` 的第一档单:
- 拒绝时仍会走旧 `withdrawFail`(若当时已冻钱包则退钱正确,但档位仍卡 processing
- 运维可对已知 `order_id` 在 console 手工调用 `handleFirstCashoutResult`,或一次性 SQL 回填 `data_snapshot.biz_type`(按 `free_credits_package.withdraw_order_id` 关联)。
## 测试
| 层级 | 内容 |
|------|------|
| `slot_pay` 单测 | `creatOrder` 写入 `biz_type``isFreeCreditFirstCashout``audit(2)` 第一档 mock 不发 `withdrawFail`、发 MQmock `ConsoleMqService` |
| `slot_console` 已有 | [`FreeCreditsLogicCashoutResultTest`](slot_console/tests/Integration/FreeCreditsLogicCashoutResultTest.php) 覆盖 `handleFirstCashoutResult` |
| 手工 | 后台对第一档人工单:拒绝退钱 → C 端档位回 ready 可再提;拒绝扣钱 → rejected |
## 涉及文件小结
- [`slot_pay/app/entity/WithdrawalOrderEntity.php`](slot_pay/app/entity/WithdrawalOrderEntity.php) — `creatOrder` / `apply` / `audit` + helper
- [`slot_pay/app/command/EventWithdrawal.php`](slot_pay/app/command/EventWithdrawal.php) — 打款结果分支(建议同 PR
- [`slot_lib/src/entity/mq/MQBusEntity.php`](slot_lib/src/entity/mq/MQBusEntity.php) — 事件常量
- (可选)[`slot_console/app/entity/mq/MQBusEntity.php`](slot_console/app/entity/mq/MQBusEntity.php) — 引用 lib 常量