This commit is contained in:
ray zhou
2026-05-29 11:21:40 +08:00
parent 5d6d482efe
commit f71a5c59af
447 changed files with 32245 additions and 116 deletions

View File

@@ -0,0 +1,174 @@
---
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 常量