9.8 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 首充剩余分档规则 | 将 Free Credits 剩余金额改为 10/20/30/50/100 瀑布分档;后续档解锁为「每档独立的新增累计充值」(自上一档解锁后重新计数,非终身 totalR 一刀切);尾档 unlock 按大单位整元向下取整。 |
|
false |
首充剩余定格 — 分档与解锁规则变更
背景与现状
核心逻辑在 slot_console/app/api/logic/FreeCreditsLogic.php:
- 定格剩余:
leftAmount = frozenAmount - firstCashAmount(第一档默认 $20,来自ext_config.first_cash_amount) - 当前拆分:
splitReleasePackageAmounts($left, package_amount)— 每档min(package_amount, 剩余)循环切分(默认 $10/档) - 当前解锁:第一档看
totalRecharge >= recharge_unlock_amount(默认 $50);后续档看 本笔rechargeAmount >= subsequent_min_recharge(默认 $10),且受max_unlock_per_recharge限制
需求文档旧版 §5.7 见 docs/requirements/首充前免费余额定格与分档释放需求文档.md。
新规则(产品口径)
基数:剩余 = 定格金额 − 第一档免打码提现额($20)
按顺序「吃掉」剩余金额(瀑布式,非按总额选一档):
| 阶段 | 单档面额 | 本阶段最多档数 | 本档所需新增累计充值(自上一档解锁后起算) |
|---|---|---|---|
| 1 | $10 | 3 | 每档再充 ≥ $9 |
| 2 | $20 | 5 | 每档再充 ≥ $19 |
| 3 | $30 | 10 | 每档再充 ≥ $29 |
| 4 | $50 | 10 | 每档再充 ≥ $49 |
| 5 | $100 | 直到剩余 < $100 | 每档再充 ≥ $99 |
| 6 | 尾档 | 剩余 < $100 的一整档 | 再充 ≥ 面额整元向下取整(见下) |
重要:表中金额为「每一档单独重新累计」的充值要求,不是钱包终身 totalRecharge 达到该绝对值。上一档变为 ready 后,充值计数归零,从当前 totalRecharge 重新累计下一档。
金额在代码中为 千分位整数(qf):$10 → 10000。
示例(定格 $78.50,第一档 $20,剩余 $58.50)
阶段1: 3×$10 = $30 → 剩 $28.50
阶段2: 1×$20 = $20 → 剩 $8.50
阶段3–5: 不足整档,跳过
阶段6: 1×$8.50 → 剩 $0(unlock_recharge_qf = 8000,即 $8)
共 5 个 release 档:10,10,10,20,8.5(旧规则为 6 档均 $10 + 尾档)。
尾档解锁门槛:大单位整数(不允许小数)
尾档 amount_qf 仍保留实际剩余(可含「分」级精度,如 15340 qf = $15.34),但写入 unlock_recharge_qf 时必须 向下取整到大单位整数美元:
unlock_recharge_qf = intdiv(amount_qf, 1000) * 1000 // 去掉 qf 余数,等价 floor 到整元
| amount_qf(小单位/千分位) | 档位展示面额 | unlock_recharge_qf(累计充值门槛) |
|---|---|---|
15340 |
$15.34 | 15000(本档需新增累计充 $15,非 $15.34) |
8500 |
$8.50 | 8000($8) |
10000 |
$10.00 | 10000($10) |
实现时抽私有方法,例如 calcTailUnlockRechargeQf(int $amountQf): int,仅用于阶段 6;标准档(10/20/30/50/100)仍为 amount - 1000 qf,本身已是整元。
flowchart TD
left[剩余金额 leftQf]
b1["阶段1: 最多3档 x 10元"]
b2["阶段2: 最多5档 x 20元"]
b3["阶段3: 最多10档 x 30元"]
b4["阶段4: 最多10档 x 50元"]
b5["阶段5: 整百100元直到剩小于100"]
b6["阶段6: 尾档=剩余金额"]
left --> b1 --> b2 --> b3 --> b4 --> b5 --> b6
实现方案
1. 拆分算法(替换 splitReleasePackageAmounts)
在 FreeCreditsLogic 中新增结构化方法,例如:
/**
* @return list<array{amount_qf: int, unlock_recharge_qf: int}>
*/
public static function buildReleasePackages(int $leftAmountQf): array
- 用常量表描述 4 个固定阶段
{amount: 10000|20000|30000|50000, maxCount: 3|5|10|10, unlock: amount-1000} - 阶段 5:
while ($left >= 100000)追加{100000, 99000} - 阶段 6:
if ($left > 0)追加{amount_qf: left, unlock_recharge_qf: calcTailUnlockRechargeQf(left)}(unlock 为整元,不等于 left) - 每阶段只取
min(maxCount, floor(left / amount))个整档,余数进入下一阶段
createPackages() 改为遍历上述结构写入 free_credits_package,不再读 package_amount 配置。
2. 库表:本档门槛 + 充值基线
free_credits_package 新增:
unlock_recharge_qf bigint unsigned NOT NULL DEFAULT 0
COMMENT '解锁本档所需新增累计真实充值(千分位),自上一档解锁后起算'
free_credits_player 新增:
recharge_baseline_qf bigint unsigned NOT NULL DEFAULT 0
COMMENT '后续档计数起点:wallet.totalRecharge 快照(千分位)'
- 定格时:写入各档
unlock_recharge_qf;recharge_baseline_qf = 定格时 wallet.totalRecharge(首笔 release 档从定格后新增充值开始计) - 存量(grandfather):
unlock_recharge_qf=0的包仍走旧逻辑(单笔subsequent_min_recharge)
3. 解锁逻辑(advanceByRecharge)— 每档「新的」累计充值
第一档(免打码提现)不变:仍用终身 totalRecharge >= recharge_unlock_amount(默认 $50)。
后续 release 档:
incrementalRecharge = wallet.totalRecharge - player.recharge_baseline_qf
若 incrementalRecharge >= nextPackage.unlock_recharge_qf → 解锁该档为 ready
→ recharge_baseline_qf = wallet.totalRecharge // 下一档重新累计
循环至多 max_unlock_per_recharge 次(同一笔充值回调内可连续解锁多档,但每解锁一档都会 重置 baseline,故单笔 $27 通常只够解锁一档 $9 档)。
| 项 | 旧 | 新 |
|---|---|---|
| 条件 | 本笔 rechargeAmount >= subsequent_min |
(totalR - baseline) >= unlock_recharge_qf |
| 计数范围 | 单笔 | 档间新增累计(解锁后 baseline 前移) |
| 顺序 | package_no 升序 |
不变 |
sequenceDiagram
participant Wallet
participant Logic
participant Player
Wallet->>Logic: totalRecharge=50000
Note over Player: baseline=41000
Logic->>Logic: incremental=9000 >= unlock 9000
Logic->>Player: baseline=50000
Note over Logic: 下一档需再新增累计充值
与「领取 Claim」:Claim 仍要求档位 ready;解锁条件只影响何时变 ready,不改变 Claim 入账逻辑。
4. 配置与后台
package_amount/subsequent_min_recharge:对新定格不再参与拆分/解锁;后台表单项可标为「已废弃」或隐藏,校验ActivityValidate::checkFreeCreditsExt改为非必填(避免运营误填)max_unlock_per_recharge、first_cash_amount、recharge_unlock_amount继续有效
5. C 端 API(可选增强)
FreeCreditsController::status 当前 packages 仅 id/amount/status。若前端要展示「再充 $X 解锁下一档」,可在每项增加 unlock_recharge_amount(大单位 float);非必须,help 文案可先说明规则。
6. 测试
更新/新增 FreeCreditsLogicAmountTest.php:
- DataProvider:
58500qf 剩余 →[10000×3, 20000, 8500],尾档 unlock=8000(非 8500) - 尾档取整:
15340→ amount=15340, unlock=15000 - 大额:
1000000qf 剩余 → 验证各阶段档数上限与总和守恒 - 边界:
left=0、整阶段边界(30/100/500…)
集成测 FreeCreditsFirstRechargeFreezeTest 断言 package 行数与金额。
解锁单测(增量累计):
- baseline=41000,totalR=50000,unlock=9000 → 解锁;baseline 更新为 50000
- 同上后再充到 59000 → 第二档 unlock=9000 可解锁;一笔 50000→59000 只够第二档若 baseline 已重置
- 尾档 unlock=15000,amount=15340:需新增累计 15000,非 15340
7. 文档
更新 docs/requirements/首充前免费余额定格与分档释放需求文档.md §5.7、§5.8 与示例表。
影响范围(不改)
slot_wallet定格/冻结、Claim 入账slot_pay第一档提现- 第一档 $20 / 累计 $50 解锁第一档 — 本次需求未改
风险与验收
- 金额守恒:
sum(release.amount_qf) === frozen - first_cash - 幂等:定格仍按
first_recharge_order_id幂等,不重复拆档 - 存量:默认 grandfather;上线前确认是否有在途「旧档位」用户
- 验收脚本:
verify-slot-backend.sh+ phpunitFreeCreditsLogicAmountTest
docker exec -w /app/www/slot/slot_console php82 ./vendor/bin/phpunit tests/Unit/FreeCreditsLogicAmountTest.php