Files
cursor/plans/首充剩余分档规则_509262bd.plan.md
ray zhou 225fb2bd28 ok
2026-05-29 19:26:16 +08:00

9.8 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
首充剩余分档规则 将 Free Credits 剩余金额改为 10/20/30/50/100 瀑布分档;后续档解锁为「每档独立的新增累计充值」(自上一档解锁后重新计数,非终身 totalR 一刀切);尾档 unlock 按大单位整元向下取整。
id content status
split-algorithm 实现 buildReleasePackages 瀑布拆分,替换 splitReleasePackageAmounts + createPackages completed
id content status
db-unlock-qf package 增 unlock_recharge_qf本档所需新增累计充值player 增 recharge_baseline_qf迁移脚本 completed
id content status
advance-unlock advanceByRecharge 用 (totalRbaseline)≥unlock 逐档解锁并重置 baseline存量回退旧逻辑 completed
id content status
admin-config 废弃 package_amount/subsequent_min 后台必填;更新 ActivityValidate completed
id content status
tests-docs 单测/集成测 + 需求文档 §5.7/5.8 更新 completed
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
阶段35: 不足整档,跳过
阶段6: 1×$8.50       → 剩 $0unlock_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}
  • 阶段 5while ($left >= 100000) 追加 {100000, 99000}
  • 阶段 6if ($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_qfrecharge_baseline_qf = 定格时 wallet.totalRecharge(首笔 release 档从定格后新增充值开始计)
  • 存量grandfatherunlock_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. 配置与后台

5. C 端 API可选增强

FreeCreditsController::status 当前 packagesid/amount/status。若前端要展示「再充 $X 解锁下一档」,可在每项增加 unlock_recharge_amount(大单位 float非必须help 文案可先说明规则。

6. 测试

更新/新增 FreeCreditsLogicAmountTest.php

  • DataProvider58500 qf 剩余 → [10000×3, 20000, 8500],尾档 unlock=8000(非 8500
  • 尾档取整:15340 → amount=15340, unlock=15000
  • 大额:1000000 qf 剩余 → 验证各阶段档数上限与总和守恒
  • 边界:left=0、整阶段边界30/100/500…

集成测 FreeCreditsFirstRechargeFreezeTest 断言 package 行数与金额。

解锁单测(增量累计):

  • baseline=41000totalR=50000unlock=9000 → 解锁baseline 更新为 50000
  • 同上后再充到 59000 → 第二档 unlock=9000 可解锁;一笔 50000→59000 只够第二档若 baseline 已重置
  • 尾档 unlock=15000amount=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 + phpunit FreeCreditsLogicAmountTest
docker exec -w /app/www/slot/slot_console php82 ./vendor/bin/phpunit tests/Unit/FreeCreditsLogicAmountTest.php