--- name: 首充定格资金修复 overview: 本次仅改 slot_wallet 与 slot_console。wallet 删除 Recharge bus、首充发 free_credit_init;console 消费该事件完成定格。pay 等其它服务不在本次范围。 todos: - id: wallet-remove-recharge-bus content: 删除 WalletLogic recharge/rechargeSign 中 sendConsoleBus('Recharge') status: completed - id: wallet-send-free-credit-init content: recharge + rechargeSign 首充时发送 free_credit_init(inc/改账前采 balance_before_qf) status: completed - id: wallet-remove-create-wager-first-recharge content: CreateWagerTask 删除 version1/version2 首充特殊打码分支,首充走普通充值打码 status: completed - id: console-event-bus-handler content: EventBus case free_credit_init + FreeCreditInitEvent status: completed - id: console-free-credit-init-logic content: handleFreeCreditInit;RechargeEvent 移除首充定格,保留档位推进 status: completed - id: console-safe-freeze-order content: freezeFirstRecharge 先 RPC 后落库;上限与幂等 status: completed - id: mq-reliability content: free_credit_init 失败 nack/requeue(仅 console EventBus) status: completed - id: tests-and-repair content: wallet/console 单测与集成测;FreeCreditsFreezeDev 补偿(console) status: completed isProject: false --- # 首充定格资金操作修复方案(修订 v4) ## 变更范围(硬约束) **本次仅编辑以下仓库/服务,不修改任何其它服务(含 slot_pay、slot_lib 等):** | 在范围内 | 不在范围内 | |----------|------------| | `slot_wallet` | `slot_pay` | | `slot_console` | `slot_lib`、`slot_agent`、… | > `Recharge` 总线消息假定由 **pay 或其它既有链路** 发送;本次不从 wallet 重复发送,也**不改 pay** 去补发。若线上 pay 未发 `Recharge`,统计/档位问题需另开 pay 任务,**不纳入本 PR**。 --- ## 设计原则 > **情愿用户定格失败,也不能让系统亏钱。** | 服务(本次) | 职责 | |--------------|------| | **slot_wallet** | 入账;首充发 **`free_credit_init`**;**删除** `Recharge` bus;**移除** `CreateWagerTask` 旧首充打码拆分 | | **slot_console** | 消费 `free_credit_init` → 定格扣款 + 活动落库;`RechargeEvent` **不再**做首充定格 | --- ## 目标架构 ```mermaid sequenceDiagram participant Ext as 外部_pay等_本次不改 participant Wallet as slot_wallet participant MQ as console_bus participant Console as slot_console Ext->>Wallet: recharge / rechargeSign Wallet->>Wallet: 采 balance_before_qf,入账 Wallet->>MQ: free_credit_init Ext->>MQ: Recharge(本次不实现) MQ->>Console: FreeCreditInitEvent Console->>Wallet: freeCreditsFreeze RPC Console->>Console: player/package 落库 MQ->>Console: RechargeEvent(既有逻辑,无定格) ``` --- ## 实现步骤 ### 1. slot_wallet #### 1.1 删除 `Recharge` bus 从 [`WalletLogic::recharge()`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php)、[`rechargeSign()`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) 移除: ```php $this->sendConsoleBus('Recharge', $this->requestDTO->recharge); ``` 保留 `sendConsoleBus('reward', ...)` 及其它非 Recharge 类型(本次不动)。 #### 1.2 `maybeSendFreeCreditInit()` + 扩展 `sendConsoleBus` - 改账/ `inc()` **前**:`balance_before_qf`、`is_first_recharge`(`total_deposit == 0`,在 `statModel->inc` 之前判断)。 - 改账成功后:`is_first_recharge && balance_before_qf > 0` 时发送: ```php $this->sendConsoleBus('free_credit_init', 0, [ 'balance_before_qf' => $balanceBeforeQf, 'recharge_amount' => $this->requestDTO->recharge, ]); ``` - [`sendConsoleBus()`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) 增加可选参数 `array $extraData = []`。 #### 1.3 `recharge()` 与 `rechargeSign()` 均接入 签到购买 [`rechargeSign()`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) 与普通 [`recharge()`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) 使用同一套 `maybeSendFreeCreditInit()` 逻辑。 #### 1.4 移除 `CreateWagerTask` 旧首充打码逻辑(已废弃) [`app/command/CreateWagerTask.php`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/command/CreateWagerTask.php) 在 **Free Credits 上线前** 于首充时本地拆分余额打码,与现「console 定格 + 分档释放」重复且易冲突,**本次删除**。 **旧逻辑位置**(条件均为 `RechargeExchangeService::total` 累计充值等于本笔 `recharge_amount`,即首充): | 方法 | 行号(约) | 行为 | |------|-----------|------| | `version1()` | L87–140 | `SOURCE_TYPE_FIRST_RECHARGE` 任务 + `SOURCE_TYPE_FREE` 免打码任务 + `SOURCE_TYPE_FIRST_LEFT`「首充剩余」打码 | | `version2()` | L186–244 | 首充充值/赠送打码 + `SOURCE_TYPE_FIRST_LEFT`(`first_recharge_left` 系数) | **与新方案关系**: - 充值前免费余额 → 由 console `free_credit_init` → `freeCreditsFreeze` 扣出活动池(不再在 wallet 侧拆 `FREE` / `FIRST_LEFT` 任务)。 - 免打码第一档 / 后续释放 → 由 console `FreeCreditsLogic` + `freeCreditsClaim` 创建 Y1(`WalletLogic::freeCreditsClaim` 内 `createTask`)。 - 首充**本笔充值金额**的打码 → 与其它充值相同,走 `elseif ($dto->required_wager > 0)` 通用分支即可。 **改动要点**: 1. 删除 `version1` / `version2` 中整段 `if ($entity->recharge > 0 && $entity->recharge == $dto->recharge_amount) { ... }`。 2. 首充与普通充值统一落入后续 `elseif ($dto->required_wager > 0)`(`version1` L142+、`version2` L246+)。 3. **保留**其中对 `SOURCE_TYPE_BUY_SIGN`(购买签到解锁额度为 0)的处理——该逻辑在 `elseif` 分支内已有,无需首充专用块。 4. 删除后确认无引用孤立的 `SOURCE_TYPE_FIRST_RECHARGE` / `SOURCE_TYPE_FIRST_LEFT` 首充专用路径(常量可保留供历史任务读)。 --- ### 2. slot_console #### 2.1 EventBus 注册 `free_credit_init` [`EventBus::deal()`](file:///Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php) 增加显式分支(类名不能走 default 动态加载): ```php case 'free_credit_init': (new FreeCreditInitEvent())->handle($busEntity); break; ``` 新建 [`app/command/event/FreeCreditInitEvent.php`](file:///Users/ray/Documents/project/www/slot/slot_console/app/command/event/FreeCreditInitEvent.php)。 #### 2.2 `FreeCreditsLogic::handleFreeCreditInit` - 入参:`uid`、`balance_before_qf`、`wallet_amount`、`orderId`(来自 bus `data`)。 - `frozenAmount = max(balance_before_qf, 0)`;**不以**充值后再读余额反推为主路径。 - 活动未开启 / 已定格 → 幂等 return。 - 调用调整后的 `freezeFirstRecharge()`。 #### 2.3 调整 `freezeFirstRecharge`(先扣款、后落库) [`freezeFirstRecharge()`](file:///Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php): 1. 幂等:已有 `free_credits_freeze:{orderId}` 流水则跳过 RPC。 2. `frozenAmount = min(事件金额, RPC 前当前可扣余额)`;≤0 不扣。 3. **先** `freeCreditsFreeze` RPC,**后** `Db::transaction` 写 player/packages。 4. RPC 失败 → 不落库,**抛异常**。 #### 2.4 `RechargeEvent` 去掉首充定格 [`RechargeEvent::handle()`](file:///Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php) **删除** L73-82 对 `handleRecharge` 的调用(首充定格改由 `free_credit_init` 触发)。 保留并可继续调用 **仅档位推进** 的逻辑,例如: - 新增 `FreeCreditsLogic::advanceAfterRecharge($uid, $walletAmount)`,或 - `handleRecharge` 内去掉首充 `freezeFirstRecharge` 分支,仅保留 `advanceByRecharge`(供既有 `Recharge` 消息使用)。 统计、黑名单、代理首充等 **RechargeEvent 现有代码不动**(本次范围外行为保持)。 #### 2.5 MQ 可靠性(仅 console) - `FreeCreditInitEvent` / `free_credit_init`:异常上抛;`EventBus` 对该 type 失败时 **nack/requeue**(需对齐现有 consumer)。 - `Recharge` 路径统计块仍可独立 try/catch(本次不改 pay 发消息前提)。 --- ### 3. 测试(仅 wallet + console) | 用例 | 位置 | |------|------| | wallet 删除 Recharge bus | slot_wallet | | wallet 首充发 `free_credit_init`(含 rechargeSign) | slot_wallet | | 首充不再走 CreateWagerTask 特殊分支 | slot_wallet CreateWagerTask | | `FreeCreditInitEvent` 定格成功 | slot_console 集成测 | | RPC 失败不落库 / 先扣后落库 / 幂等 | slot_console | | `RechargeEvent` 不再触发定格 | 调整 [`FreeCreditsHandleRechargeTest`](file:///Users/ray/Documents/project/www/slot/slot_console/tests/Unit/FreeCreditsHandleRechargeTest.php) 等 | **不新增** pay 侧联调用例。 --- ## 关键改动文件(仅此两份) **slot_wallet** - [`app/api/logic/WalletLogic.php`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php) - [`app/command/CreateWagerTask.php`](file:///Users/ray/Documents/project/www/slot/slot_wallet/app/command/CreateWagerTask.php) **slot_console** - [`app/command/EventBus.php`](file:///Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php) - `app/command/event/FreeCreditInitEvent.php`(新建) - [`app/api/logic/FreeCreditsLogic.php`](file:///Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php) - [`app/command/event/RechargeEvent.php`](file:///Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php) - 相关 tests(console 仓内) --- ## 验收标准(本 PR) - **wallet**:`recharge` / `rechargeSign` 不再发送 `type=Recharge`;首充且 inc 前有免费余额时发送 `free_credit_init`,`balance_before_qf` 正确。 - **wallet**:首充不再触发 `CreateWagerTask` 的 `FIRST_RECHARGE` / `FREE` / `FIRST_LEFT` 拆分,仅按本笔充值/赠送金额走通用打码任务。 - **console**:收到 `free_credit_init` 后完成定格扣款与落库;扣款 ≤ 事件金额且 ≤ 可扣余额;幂等。 - **console**:`RechargeEvent` 不再执行首充定格;已定格用户经 `Recharge` 仍可 `advanceByRecharge`(依赖外部 pay 发消息,**本 PR 不验证 pay**)。 - **范围**:`git diff` 仅涉及 `slot_wallet`、`slot_console` 路径。