20 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 每日返水需求文档 | 在现有 user_profit_daily 日下注统计基础上,新增「每日返水」活动:累进档位计费、仅充值用户参与、次日 00:00 起可领且 24 小时内有效;配套用户端 API、后台档位配置与综合统计页。 |
|
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 结算与领取时间轴
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
响应示例字段:
{
"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 logbiz_extra: {activity:"daily_rebate", stat_date}。 - 监控:结算失败/领取失败打日志 + 指标;过期数量日报。
9. 边界与异常
| 场景 | 处理 |
|---|---|
| 结算日用户刚充值、昨日下注 | 昨日结算时已按当时 total_deposit 判断;若需「充值后立即对历史补发」→ 不做,以结算时刻为准 |
| 结算任务重复跑 | upsert,不重复入账 |
| 时区变更 | 禁止随意改;变更需重算规则文档化 |
| bet>0 未充值 | 不生成记录 |
| 领取时档位已改 | 以 tier_snapshot 为准,不受后续配置变更影响 |
| 跨天未关弹窗 | 前端倒计时结束刷新 info;pending→claimable 由后端状态驱动 |
10. 实施拆分(供研发排期)
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. 验收标准(摘要)
- 充值用户 D 日下注 $2000,D+1 00:00 后显示待领取 $50,24h 内领取成功,流水类型正确。
- 未充值用户仅见锁定态,无领取接口成功路径。
- 超 24h 未领变过期,不可再领。
- 后台筛选/status/排序/汇总与文档公式一致;改档位仅影响新结算日。
- 前端 7 日表、Claim 按钮金额与后台一致;当日为 Pending 且随下注刷新。
12. 已确认决策
- 计费方式:累进分段(非整笔落档单一比例)。
- 有效下注:等同
user_profit_daily.bet。 - 档位:后台可配置(
daily_rebate_tier_config)。