Files
cursor/plans/Plan-0abba57d.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

158 lines
8.1 KiB
Markdown
Raw 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.

<!-- 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) 的 useDTO 可保留文件以免大范围删除,但无引用)
### 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 脚本