ok
This commit is contained in:
174
plans/第一档提现审核拒绝_843062ca.plan.md
Normal file
174
plans/第一档提现审核拒绝_843062ca.plan.md
Normal 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) L133–140),但 **全库无生产端** 发送 `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`、发 MQ(mock `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 常量
|
||||
Reference in New Issue
Block a user