ok
This commit is contained in:
408
plans/每日返水需求文档_20a03147.plan.md
Normal file
408
plans/每日返水需求文档_20a03147.plan.md
Normal file
@@ -0,0 +1,408 @@
|
||||
---
|
||||
name: 每日返水需求文档
|
||||
overview: 在现有 user_profit_daily 日下注统计基础上,新增「每日返水」活动:累进档位计费、仅充值用户参与、次日 00:00 起可领且 24 小时内有效;配套用户端 API、后台档位配置与综合统计页。
|
||||
todos:
|
||||
- id: schema
|
||||
content: 设计并评审 daily_rebate_record、daily_rebate_tier_config 表结构与索引
|
||||
status: in_progress
|
||||
- id: calc-service
|
||||
content: 实现累进分段 RebateCalcService + tier_snapshot + 单元测试(含 $2000→$50 等用例)
|
||||
status: pending
|
||||
- id: cron
|
||||
content: slot_console:dailyRebateSettle / dailyRebateExpire 定时任务
|
||||
status: pending
|
||||
- id: user-api
|
||||
content: slot_console 用户端 info/claim API + slot_wallet 入账与新 transaction type
|
||||
status: pending
|
||||
- id: admin-tier
|
||||
content: slot_admin 每日返水档位 CRUD 页
|
||||
status: pending
|
||||
- id: admin-stats
|
||||
content: slot_admin 综合统计/每日返水统计(列表+筛选+排序+汇总,参考 FreeCreditsStats)
|
||||
status: pending
|
||||
- id: pwa-ui
|
||||
content: 前端三态 UI、7 日表、倒计时、领取按钮
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 每日返水(Daily Rebate 3%)需求规格书
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
- **业务目标**:对已充值用户,按自然日累计有效下注给予累进比例返水,提升留存与投注激励。
|
||||
- **数据基础**:`[slot_console](slot_console)` 已通过 `[LoseReturnDeposit](slot_console/app/command/LoseReturnDeposit.php)` + Redis `[UserProfitService](slot_pwa/app/service/user/UserProfitService.php)` 维护 `[s_statistics.user_profit_daily](slot_console/app/model/statistics/UserProfitDaily.php)`(字段 `bet`/`win`/`profit`/`create_date`/`source`)。
|
||||
- **与 VIP 亏损返利的区别**:VIP 返利基于「当日净亏损」且需 VIP 等级;本活动基于「当日总下注额」、仅需历史充值、档位为累进分段,产品独立。
|
||||
|
||||
---
|
||||
|
||||
## 2. 术语
|
||||
|
||||
|
||||
| 术语 | 定义 |
|
||||
| ---- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| 自然日 | 服务器时区 `Asia/Shanghai`(与 `[slot_center/config/app.php](slot_center/config/app.php)` 一致)的 `00:00:00`–`23:59:59` |
|
||||
| 有效下注 | **等于** `user_profit_daily.bet`(厘),与 PWA 下注时 `incBet` 累计值一致;无单独过滤规则 |
|
||||
| 返水金额 | 按档位配置对当日有效下注做**累进分段**计算后的金额(厘) |
|
||||
| 统计日期 | 发生下注的自然日 `create_date`,非领取日 |
|
||||
| 充值用户 | 结算时刻钱包 `total_deposit > 0`(`[WalletStatModel](slot_wallet/app/model/multi/WalletStatModel.php)`) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心业务规则
|
||||
|
||||
### 3.1 参与资格
|
||||
|
||||
- **未充值**:不参与返水;前端展示「锁定」态(Unlock Cashback),不可领、列表可置灰或仅展示引导文案。
|
||||
- **已充值**:自充值成功当日起,当日及之后有有效下注的自然日可产生返水记录;历史未充值日的下注不补发。
|
||||
|
||||
### 3.2 返水计算(累进分段,非整笔单一比例)
|
||||
|
||||
**已确认**:采用分段累进,与原型及后台示例一致($2000 → $50)。
|
||||
|
||||
默认档位(首版种子数据,**后台可配置**):
|
||||
|
||||
|
||||
| 分段序号 | 下注区间(含边界,美元展示) | 比例 |
|
||||
| ---- | --------------- | ---- |
|
||||
| 1 | $0 – $1,000 | 3% |
|
||||
| 2 | $1,001 – $3,000 | 2% |
|
||||
| 3 | $3,001 – $5,000 | 1% |
|
||||
| 4 | $5,001 及以上 | 0.5% |
|
||||
|
||||
|
||||
**计算公式**(`B` = 当日有效下注,美元;存储与计算用厘,`1 USD = 1000` 厘,与 `[share_config.moneyFormat](slot_center/config/share_config.php)` 一致):
|
||||
|
||||
```
|
||||
rebate_li = Σ segment_i( min(B, max_i) - min(B, min_i) + ε_i ) × rate_i
|
||||
```
|
||||
|
||||
- 各档 `min_i`/`max_i` 以后台配置为准(最后一档 `max` 为空表示无上限)。
|
||||
- 分段左闭右闭:第 1 档覆盖 `[0, 1000]`,第 2 档覆盖 `(1000, 3000]`,依此类推(实现时用「上一档上限 + 1」作为下一档起点,避免重复计费)。
|
||||
- **舍入**:返水金额入库前 `floor` 到厘;前端展示保留 2 位小数美元。
|
||||
|
||||
**计算示例**:
|
||||
|
||||
|
||||
| 有效下注 | 计算过程 | 返水 |
|
||||
| ------- | --------------------------------------- | ------ |
|
||||
| $125.56 | 125.56 × 3% | $3.77 |
|
||||
| $200 | 200 × 3% | $6.00 |
|
||||
| $2,000 | 1000×3% + 1000×2% | $50.00 |
|
||||
| $6,000 | 1000×3% + 2000×2% + 2000×1% + 1000×0.5% | $95.00 |
|
||||
|
||||
|
||||
**待定态(当天)**:用 Redis 当日 `bet` 实时重算返水预览;不入库终态金额,或与 DB 行 `status=pending` 同步更新。
|
||||
|
||||
### 3.3 结算与领取时间轴
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant PWA as slot_pwa
|
||||
participant Redis
|
||||
participant Cron as slot_console_cron
|
||||
participant DB as daily_rebate_record
|
||||
|
||||
Note over User,Redis: D日 00:00-23:59
|
||||
User->>PWA: 下注
|
||||
PWA->>Redis: incBet
|
||||
Note over DB: status=pending 实时 bet/rebate
|
||||
|
||||
Note over Cron,DB: D+1日 00:00后 cron
|
||||
Cron->>Redis: 读取 D日 bet
|
||||
Cron->>DB: 写入/更新 settle: claimable, dead_time=D+2 00:00
|
||||
|
||||
Note over User,DB: D+1日 仅可领 D日 返水
|
||||
User->>PWA: claim(stat_date=D)
|
||||
PWA->>DB: status=claimed
|
||||
|
||||
Note over DB: 超过 dead_time 未领
|
||||
Cron->>DB: status=expired
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
| 时点 | 行为 |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| D 日进行中 | 状态 **待定**;有效下注、返水金额随 Redis 刷新 |
|
||||
| D+1 日 00:00 | 定时任务结算 D 日:有 `bet>0` 且已充值 → 生成/更新记录,状态 **待领取**;`claimable_at = D+1 00:00:00`,`expire_at = D+2 00:00:00`(领取窗口 **24 小时**,对齐 VIP `[vip_rebate_lose_time](slot_center/config/share_config.php)`) |
|
||||
| D+1 日全天 | 用户**只能领取 stat_date=D** 的返水(「前一天」) |
|
||||
| D+2 日 00:00 前未领 | 状态 **过期**;`rebate_amount>0` 且未领 |
|
||||
| 领取成功 | 状态 **已领**;记 `claimed_at`;钱包入账 |
|
||||
|
||||
|
||||
**无有效下注**:不生成记录(后台列表、前端 7 日表均不展示该日)。
|
||||
|
||||
**返水为 0**:若档位计算为 0(`bet=0` 已排除),`bet>0` 但舍入为 0 时可不落库或落库且不可领——建议 **不落库**,与「未有有效下注不显示」一致。
|
||||
|
||||
### 3.4 状态机
|
||||
|
||||
|
||||
| 状态码 | 中文 | 条件 |
|
||||
| ----------- | --- | --------------------------------------------------------------------- |
|
||||
| `pending` | 待定 | `stat_date = 今天`;实时统计未结束 |
|
||||
| `claimable` | 待领取 | `stat_date < 今天`;`rebate_amount > 0`;`claimed_at` 空;`now < expire_at` |
|
||||
| `claimed` | 已领 | `claimed_at` 非空 |
|
||||
| `expired` | 过期 | `rebate_amount > 0`;未领;`now >= expire_at` |
|
||||
|
||||
|
||||
状态迁移:
|
||||
|
||||
- `pending` → `claimable`:日切结算任务(仅昨日及更早批量处理;今日保持 pending)
|
||||
- `claimable` → `claimed`:用户领取接口(幂等)
|
||||
- `claimable` → `expired`:过期扫描任务或领取时校验
|
||||
|
||||
前端映射(英文 UI):
|
||||
|
||||
|
||||
| 状态 | 展示 | 行样式 |
|
||||
| --------- | --------- | -------------------- |
|
||||
| pending | Pending | 绿色标签(进行中) |
|
||||
| claimable | Claimable | 绿色可点 |
|
||||
| claimed | Claimed | 白色/灰色 |
|
||||
| expired | Expired | 红色/灰色 |
|
||||
| 无记录 | No Bets | 灰字(仅前端 7 日占位,无 DB 行) |
|
||||
|
||||
|
||||
### 3.5 领取规则
|
||||
|
||||
- 每次领取**一条**指定 `stat_date`(通常为昨日)。
|
||||
- 并发:DB 行级锁 / `UPDATE ... WHERE status=claimable` 防重复领。
|
||||
- 入账:新增钱包流水类型(建议 `TRANSACTION_TYPE_DAILY_REBATE = 65`,在 `[Consts.php](slot_lib/src/common/const/Consts.php)` 登记);更新 `total_cashback`。
|
||||
- 失败:事务回滚,状态不变。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据设计
|
||||
|
||||
### 4.1 沿用表
|
||||
|
||||
- `**s_statistics.user_profit_daily`**:只读来源,提供 `bet`、`source`、`create_date`;**不扩展**状态/返水字段。
|
||||
|
||||
### 4.2 新建表 `daily_rebate_record`(建议库:`s_common`)
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ----------------------- | ----------------- | ----------------------------------------- |
|
||||
| id | bigint PK | |
|
||||
| uid | bigint | 用户 ID |
|
||||
| source | varchar | 渠道号(结算时快照) |
|
||||
| stat_date | date | 统计日期(下注日) |
|
||||
| bet_amount | bigint | 有效下注(厘) |
|
||||
| rebate_amount | bigint | 返水金额(厘) |
|
||||
| status | tinyint | 1 pending 2 claimable 3 claimed 4 expired |
|
||||
| tier_snapshot | json | 结算时档位快照(审计) |
|
||||
| claimable_at | datetime | 可领取开始 |
|
||||
| expire_at | datetime | 过期时间 |
|
||||
| claimed_at | datetime nullable | 领取时间 |
|
||||
| created_at / updated_at | datetime | |
|
||||
|
||||
|
||||
**唯一索引**:`(uid, stat_date)`。
|
||||
|
||||
**索引**:`(stat_date, status)`、`(source, stat_date)`、`(uid)`。
|
||||
|
||||
### 4.3 新建表 `daily_rebate_tier_config`(后台可配置)
|
||||
|
||||
|
||||
| 字段 | 说明 |
|
||||
| ----------------------- | ------------------ |
|
||||
| id | PK |
|
||||
| sort | 排序(从小到大) |
|
||||
| min_bet | 区间下限(厘,含) |
|
||||
| max_bet | 区间上限(厘,含;NULL=无上限) |
|
||||
| rate_percent | 比例,如 3.00 表示 3% |
|
||||
| status | 启用/停用 |
|
||||
| updated_by / updated_at | 审计 |
|
||||
|
||||
|
||||
校验:档位连续无空洞、无重叠;至少一档;最后一档可无 `max_bet`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 定时任务(slot_console)
|
||||
|
||||
|
||||
| 任务 | 触发 | 职责 |
|
||||
| ----------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------- |
|
||||
| `dailyRebateSettle` | 每日 00:05(可配置) | 结算 **昨日** `user_profit_daily` + 充值校验 → upsert `daily_rebate_record` 为 claimable |
|
||||
| `dailyRebateExpire` | 每小时或 00:10 | 将 `claimable` 且 `now>=expire_at` 置为 expired |
|
||||
| (可选)与现有 `[loseReturnDeposit](slot_console/app/command/LoseReturnDeposit.php)` | 同批读 Redis | 避免重复扫 Redis;**推荐独立命令**以免耦合 VIP 逻辑 |
|
||||
|
||||
|
||||
**待定实时**:用户打开活动页时,Logic 读 Redis `user:profit:{Ymd}` + 当前档位配置计算预览,不写 claimable。
|
||||
|
||||
---
|
||||
|
||||
## 6. 用户端(PWA / Gateway)
|
||||
|
||||
### 6.1 页面态(对齐原型)
|
||||
|
||||
|
||||
| 态 | 条件 | 主按钮 | 表格 |
|
||||
| --- | ------------------------------- | --------------- | ------------- |
|
||||
| 锁定 | `total_deposit==0` | Unlock Cashback | 模糊/遮罩 |
|
||||
| 已解锁 | 已充值,无可领 | Play Now | 近 7 日明细 |
|
||||
| 可领取 | 存在 `claimable` 且 `stat_date=昨天` | Claim($X.XX) | 昨日行 Claimable |
|
||||
|
||||
|
||||
**倒计时**:「THIS ROUND」= 距今日自然日结束的秒数(与 Rates reset 00:00 文案一致)。
|
||||
|
||||
### 6.2 API(建议落在 slot_console `/api/daily-rebate/*`,经 gateway 转发)
|
||||
|
||||
**GET `/api/daily-rebate/info`**
|
||||
|
||||
响应示例字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"unlocked": true,
|
||||
"countdown_seconds": 86399,
|
||||
"claimable": { "stat_date": "2026-05-25", "rebate_amount": 3000, "display": "3.00" },
|
||||
"tiers": [{ "min": 0, "max": 1000000, "rate": 3 }],
|
||||
"records": [
|
||||
{ "stat_date": "05/26", "bet_amount": 125560, "rebate_amount": 3770, "status": "pending" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `records`:最近 7 个自然日,**有 bet 或 DB 行**的日期;无下注日可不返回或前端填 No Bets。
|
||||
- 打开弹窗时 **强制刷新** 当日 pending 数据。
|
||||
|
||||
**POST `/api/daily-rebate/claim`**
|
||||
|
||||
- 入参:`stat_date`(可选,默认昨天)
|
||||
- 校验:已充值、status=claimable、未过期、rebate>0
|
||||
- 出参:领取后余额/流水号
|
||||
|
||||
**GET `/api/daily-rebate/tiers`**(可选,info 已含则省略)
|
||||
|
||||
---
|
||||
|
||||
## 7. 管理后台(slot_admin + slot_admin_vue)
|
||||
|
||||
### 7.1 菜单
|
||||
|
||||
- **活动配置 / 每日返水档位**(新建):CRUD [`daily_rebate_tier_config`],校验区间连续。
|
||||
- **综合统计 / 每日返水统计**(新建):列表 + 汇总。
|
||||
|
||||
实现参考:列表+汇总 `[FreeCreditsStats](backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php)` + 前端 `[freeCreditsStats/index.vue](backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue)`。
|
||||
|
||||
### 7.2 统计列表
|
||||
|
||||
**列**:ID(uid) | 渠道号 | 有效下注额⇅ | 返水金额⇅ | 状态 | 统计日期
|
||||
|
||||
**筛选**:
|
||||
|
||||
- UID(精确)
|
||||
- 渠道(`source`,可搜索下拉,复用 `commonStore.allSourcesOptionsNoAll`)
|
||||
- 状态:待定 / 待领取 / 已领 / 过期(多选)
|
||||
- 统计日期范围(`stat_date[]`)
|
||||
|
||||
**排序白名单**:`bet_amount`、`rebate_amount`(默认 `stat_date desc, id desc`)
|
||||
|
||||
**数据范围**:仅 `total_deposit>0` 用户在结算时已入库的记录;**待定**行可对「今天」合并 Redis 实时 bet(与 Free Credits「打开刷新」一致)。
|
||||
|
||||
### 7.3 顶部汇总(随筛选变化)
|
||||
|
||||
|
||||
| 指标 | 计算 |
|
||||
| ---- | ----------------------------------------------- |
|
||||
| 总领取 | `status=claimed` 的 `sum(rebate_amount)` |
|
||||
| 总待领取 | `status=claimable` 的 sum |
|
||||
| 总过期 | `status=expired` 的 sum |
|
||||
| 待定 | `status=pending` 的 sum;**若筛选日期范围不含今天则为 0** |
|
||||
| 领取率 | `总领取 / (总领取 + 总过期 + 总待领取)`;分母为 0 时显示 `0%` 或 `-` |
|
||||
|
||||
|
||||
展示格式:`$888.88`(`rebate_amount / 1000`,2 位小数)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 非功能需求
|
||||
|
||||
- **幂等**:结算任务对 `(uid, stat_date)` upsert;领取 CAS 更新状态。
|
||||
- **性能**:日活结算批量按日期分页;后台列表分页默认 100,最大 100。
|
||||
- **审计**:`tier_snapshot` 保留结算时档位;领取写 wallet log `biz_extra: {activity:"daily_rebate", stat_date}`。
|
||||
- **监控**:结算失败/领取失败打日志 + 指标;过期数量日报。
|
||||
|
||||
---
|
||||
|
||||
## 9. 边界与异常
|
||||
|
||||
|
||||
| 场景 | 处理 |
|
||||
| ------------- | ----------------------------------------------------------- |
|
||||
| 结算日用户刚充值、昨日下注 | 昨日结算时已按当时 `total_deposit` 判断;若需「充值后立即对历史补发」→ **不做**,以结算时刻为准 |
|
||||
| 结算任务重复跑 | upsert,不重复入账 |
|
||||
| 时区变更 | 禁止随意改;变更需重算规则文档化 |
|
||||
| bet>0 未充值 | 不生成记录 |
|
||||
| 领取时档位已改 | 以 `tier_snapshot` 为准,不受后续配置变更影响 |
|
||||
| 跨天未关弹窗 | 前端倒计时结束刷新 info;pending→claimable 由后端状态驱动 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 10. 实施拆分(供研发排期)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph phase1 [Phase1 数据与配置]
|
||||
T1[daily_rebate_tier_config]
|
||||
T2[daily_rebate_record]
|
||||
T3[admin 档位 CRUD]
|
||||
end
|
||||
subgraph phase2 [Phase2 结算与过期]
|
||||
C1[dailyRebateSettle]
|
||||
C2[dailyRebateExpire]
|
||||
L1[rebate calc service]
|
||||
end
|
||||
subgraph phase3 [Phase3 用户端]
|
||||
A1[info API]
|
||||
A2[claim API + wallet]
|
||||
F1[PWA 三态 UI]
|
||||
end
|
||||
subgraph phase4 [Phase4 后台统计]
|
||||
B1[DailyRebateStats Logic]
|
||||
B2[admin vue 列表汇总]
|
||||
end
|
||||
phase1 --> phase2 --> phase3
|
||||
phase2 --> phase4
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
| 仓库 | 改动要点 |
|
||||
| ---------------------- | ---------------------------------------------------------- |
|
||||
| slot_console | Model、结算/过期 Command、RebateCalcService、用户 API、Redis 读当日 bet |
|
||||
| slot_wallet | 新 transaction type、领取入账 |
|
||||
| slot_lib | Consts 新类型 |
|
||||
| backend/slot_admin | Controller/Logic/Validate、菜单 SQL |
|
||||
| backend/slot_admin_vue | 档位页 + 统计页 |
|
||||
| slot_sdk | 若 gateway 经 sdk 调 console,补 Client 方法 |
|
||||
|
||||
|
||||
**不建议**复用 `vip_rebate_record` 表:业务语义、状态机、计费基础均不同。
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收标准(摘要)
|
||||
|
||||
1. 充值用户 D 日下注 $2000,D+1 00:00 后显示待领取 $50,24h 内领取成功,流水类型正确。
|
||||
2. 未充值用户仅见锁定态,无领取接口成功路径。
|
||||
3. 超 24h 未领变过期,不可再领。
|
||||
4. 后台筛选/status/排序/汇总与文档公式一致;改档位仅影响新结算日。
|
||||
5. 前端 7 日表、Claim 按钮金额与后台一致;当日为 Pending 且随下注刷新。
|
||||
|
||||
---
|
||||
|
||||
## 12. 已确认决策
|
||||
|
||||
- 计费方式:**累进分段**(非整笔落档单一比例)。
|
||||
- 有效下注:**等同 `user_profit_daily.bet`**。
|
||||
- 档位:**后台可配置**(`daily_rebate_tier_config`)。
|
||||
|
||||
Reference in New Issue
Block a user