158 lines
8.1 KiB
Markdown
158 lines
8.1 KiB
Markdown
<!-- 0abba57d-3d88-4972-ad6e-96d899cbee65 -->
|
||
---
|
||
todos:
|
||
- id: "expiry-service-mark-only"
|
||
content: "TrialBalanceExpiryService:新增 markTrialExpiredIfNeeded / isTrialPlayBlocked / ensureCanLaunchGame,停止对外清零编排"
|
||
status: pending
|
||
- id: "ws-connect"
|
||
content: "TrialBalanceExpiryConnectService:仅打 tag + 弹窗,移除 clearExpiredTrialBalanceIfNeeded"
|
||
status: pending
|
||
- id: "entry-logic"
|
||
content: "TrialRewardEntryLogic:移除全路径清零,修正 trial_balance_expired 判定"
|
||
status: pending
|
||
- id: "game-launch-guard"
|
||
content: "GameLogic + GameController::login:体验过期未充值时 BusinessException 拦截"
|
||
status: pending
|
||
- id: "constants-copy"
|
||
content: "TrialRewardEntryConstants:更新弹窗与 launch 拒绝文案"
|
||
status: pending
|
||
- id: "verify"
|
||
content: "docker php -l + slot-backend-completion-report 自检"
|
||
status: pending
|
||
isProject: false
|
||
---
|
||
# 体验到期:仅标识、不清余额、禁止进游戏
|
||
|
||
## 背景与现状
|
||
|
||
当前 [`TrialBalanceExpiryConnectService`](slot-console/app/service/trial/TrialBalanceExpiryConnectService.php) 在 WS 连接时:
|
||
|
||
1. 调用 `clearExpiredTrialBalanceIfNeeded` → wallet `expireTrialBalance` + activity 删 `trial_reward_record`
|
||
2. 打 user tag `trial_bonus_expired`
|
||
3. 推送弹窗(文案写「Trial balance has been cleared」)
|
||
|
||
[`TrialRewardEntryLogic`](slot-console/app/api/logic/TrialRewardEntryLogic.php) 在 entry/status/withdraw-click 三条路径同样调用清零。
|
||
|
||
这与需求文档 [`03_wallet_fund_flow.md`](docs/requirements/trial_withdrawal/03_wallet_fund_flow.md)「不再产生 `TRIAL_BONUS_EXPIRE`」不一致。wallet 侧 [`isTrialPhase`](slot-wallet/app/service/WalletService.php) 在超期后已返回 `false`,体验金留在 `frozen_bonus` 但无法用于试玩下注;需在 **console 启动接口** 显式拦截。
|
||
|
||
## 目标行为
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph wsConnect [WS连接事件]
|
||
A[未充值且体验期已过] --> B[打 user tag trial_bonus_expired]
|
||
B --> C[推送 WS 弹窗一次]
|
||
A --> D[不调用 wallet expireTrialBalance]
|
||
A --> E[不调用 activity trial-balance/expire]
|
||
end
|
||
subgraph gameLaunch [game/launch 与 game/login]
|
||
F[未充值且体验期已过] --> G[拒绝启动 BusinessException]
|
||
H[已充值或仍在体验期内] --> I[正常 launch]
|
||
end
|
||
```
|
||
|
||
| 场景 | 余额 | activity 记录 | user tag | 进游戏 |
|
||
|------|------|---------------|----------|--------|
|
||
| 体验期内、未充值 | 保留 | 保留 | 无 | 允许 |
|
||
| 体验过期、未充值 | **保留 frozen_bonus** | **保留** | 标记过期 | **拒绝** |
|
||
| 已充值 | 按首充规则 | 按首充规则 | 不处理 | 允许 |
|
||
|
||
## 实现步骤
|
||
|
||
### 1. 收敛「到期处理」到仅标识(`TrialBalanceExpiryService`)
|
||
|
||
文件:[`slot-console/app/service/trial/TrialBalanceExpiryService.php`](slot-console/app/service/trial/TrialBalanceExpiryService.php)
|
||
|
||
- 新增 `markTrialExpiredIfNeeded(int $uid, string $source, string $currency, int $organization): bool`:
|
||
- 已充值 → 直接返回 `false`
|
||
- `resolveWalletSnapshot` + `resolveTrialBalanceExpiry` 未过期 → `false`
|
||
- 已过期 → 仅 `markTrialBonusExpiredUserTag($uid)`,返回 `true`
|
||
- **不**调用 `expireTrialBalance`、**不**调用 `markActivityTrialBalanceExpired`
|
||
- 新增 `isTrialPlayBlocked(int $uid, string $source, string $currency, int $organization): bool`(供 launch 守卫复用):
|
||
- 逻辑:`!has_recharged && resolveTrialBalanceExpiry(...).expired`
|
||
- 新增 `ensureCanLaunchGame(...)`:当 `isTrialPlayBlocked` 为真时 `throw new BusinessException(...)`
|
||
- `clearExpiredTrialBalanceIfNeeded` 保留方法体但 **删除所有调用方**(避免误用);类注释改为「到期判定与标识」,去掉「清零」表述
|
||
- `markActivityTrialBalanceExpired` 可保留 private 方法(历史兼容),但本需求下不再被调用
|
||
|
||
### 2. 改写 WS 连接处理
|
||
|
||
文件:[`slot-console/app/service/trial/TrialBalanceExpiryConnectService.php`](slot-console/app/service/trial/TrialBalanceExpiryConnectService.php)
|
||
|
||
- 删除 `clearExpiredTrialBalanceIfNeeded` 及 `wasBalanceCleared` / `hadNoTrialBalance` 分支
|
||
- 流程简化为:
|
||
1. user tag 已标记 → return(幂等防重复弹窗)
|
||
2. 已充值 → return
|
||
3. 未过期 → return
|
||
4. `markTrialBonusExpiredUserTag` + `WsService::notifyClientPOP` + info 日志
|
||
- 更新类 PHPDoc:「检测试玩到期:打 user tag、推送弹窗(不清零余额)」
|
||
|
||
### 3. 入口 Logic 停止清零并修正 `trial_balance_expired`
|
||
|
||
文件:[`slot-console/app/api/logic/TrialRewardEntryLogic.php`](slot-console/app/api/logic/TrialRewardEntryLogic.php)
|
||
|
||
- 在 `buildEntryPayload` / `buildStatusPayload` / `resolveWithdrawClick` 中:
|
||
- 移除 `clearExpiredTrialBalanceIfNeeded` 调用及 `trialBalanceBeforeClear` 相关刷新逻辑
|
||
- `trial_balance_expired` 改为:`$trialExpiry['expired'] && !$walletSnapshot['has_recharged']`(不再依赖 `bonus_amount <= 0`)
|
||
- 删除 private `clearExpiredTrialBalanceIfNeeded` 方法
|
||
- 移除对 [`TrialExpiredBalanceClearCommand`](slot-console/app/api/dto/TrialExpiredBalanceClearCommand.php) 的 use(DTO 可保留文件以免大范围删除,但无引用)
|
||
|
||
### 4. game/launch 与 game/login 拦截
|
||
|
||
文件:[`slot-console/app/api/logic/GameLogic.php`](slot-console/app/api/logic/GameLogic.php)
|
||
|
||
- 构造注入或懒加载 `TrialBalanceExpiryService`
|
||
- `launch()` 开头调用 `ensureCanLaunchGame($dto->uid, source, currency, organization)`
|
||
- source/currency/organization 从 `GameLaunchDTO` 或 `userEntity` 读取(与 wallet 快照现有参数对齐)
|
||
- [`GameController::login`](slot-console/app/api/controller/GameController.php) 在调用 `SlotPlatformService::login` 前同样守卫(deprecated 路径仍可能被 `launchByGameId` 使用)
|
||
|
||
错误处理:抛 `Webman\Exception\BusinessException`,消息使用新常量;[`api/exception/Handler`](slot-console/app/api/exception/Handler.php) 已注册,前端 `useGameLaunch` 会 toast 该 message。
|
||
|
||
### 5. 常量与文案
|
||
|
||
文件:[`slot-console/app/constants/TrialRewardEntryConstants.php`](slot-console/app/constants/TrialRewardEntryConstants.php)
|
||
|
||
- 更新 `POPUP_MESSAGE_TRIAL_BALANCE_EXPIRED`:去掉「balance has been cleared」,改为体验期结束、需充值后才能继续游戏(英文与产品口径一致)
|
||
- 新增 `GAME_LAUNCH_BLOCKED_MESSAGE`(launch 拒绝文案,可与弹窗语义一致)
|
||
|
||
`CLEAR_SOURCE_*` 常量暂保留(activity 接口仍存在,仅本链路不再调用)。
|
||
|
||
### 6. 不改动的服务
|
||
|
||
- **slot-wallet**:不删 `expireTrialBalance` API(历史兼容),本需求不再调用
|
||
- **slot-activity**:`trial-balance/expire` innerapi 保留,本需求不再调用
|
||
- **slot-pwa**:按你的选择不拦截
|
||
- **lobby**:依赖 launch API 错误 message 即可,无需必改
|
||
|
||
## 关键代码对照
|
||
|
||
WS 连接当前清零调用(将删除):
|
||
|
||
```65:75:slot-console/app/service/trial/TrialBalanceExpiryConnectService.php
|
||
$clearOutcome = $this->trialBalanceExpiryService->clearExpiredTrialBalanceIfNeeded(
|
||
new TrialExpiredBalanceClearCommand(
|
||
$uid,
|
||
$channelSource,
|
||
$currency,
|
||
$organization,
|
||
$walletSnapshot,
|
||
[]
|
||
),
|
||
TrialRewardEntryConstants::CLEAR_SOURCE_WS_CONNECT
|
||
);
|
||
```
|
||
|
||
入口当前过期判定(将修改):
|
||
|
||
```112:112:slot-console/app/api/logic/TrialRewardEntryLogic.php
|
||
'trial_balance_expired' => $walletSnapshot['bonus_amount'] <= 0 && $trialExpiry['expired'],
|
||
```
|
||
|
||
## 验证要点
|
||
|
||
1. 模拟未充值用户、`wallet.created_at` 超过配置 `validity_hours`:WS 连接一次 → user tag 写入、收到弹窗、**bonus 余额不变**、无 `TRIAL_BONUS_EXPIRE` 流水
|
||
2. 再次 WS 连接 → 无重复弹窗(tag 幂等)
|
||
3. 同用户调用 `POST /api/game/launch` → BusinessException,不返回 url
|
||
4. 同用户 `GET /api/trial-reward/entry` → `trial_balance_expired=true`,`trial_balance` 仍 > 0
|
||
5. 已充值用户超体验窗口 → launch 正常、WS 不弹窗
|
||
6. 收尾跑 `slot-backend-completion-report` 与 verify 脚本
|