Parcourir la source

召回礼包修改

laowu il y a 1 semaine
Parent
commit
3e1817a65c

+ 229 - 235
app/Http/Controllers/Game/PayRechargeController.php

@@ -1141,7 +1141,50 @@ class PayRechargeController extends Controller
     }
 
     /**
-     * 连续未充值 VIP 礼包(gift_id=305)—— 检查条件和返回档位
+     * 获取召回礼包(gift_id=305)三档金额及对应奖励百分比
+     * 可用档位:9, 19, 29, 39, 49, 59, 79, 99
+     * 初档 70% 加成,后续每档增加 15%
+     * @param float $avgRecharge 用户平均充值金额
+     * @return array [['amount'=>float, 'bonus_percent'=>int], ...] 三档信息
+     */
+    private function getRecallGiftTiers($avgRecharge)
+    {
+        $amounts = [9.99, 19.99, 29.99, 39.99, 49.99, 59.99, 79.99, 99.99];
+        $bonusPercents = [70, 85, 100]; // 初档70%,中档85%,高档100%
+
+        // 找到初档索引:第一个 >= avgRecharge 的档位
+        $index = count($amounts); // 默认未找到,后面会 cap
+        foreach ($amounts as $i => $amt) {
+            if ($avgRecharge <= $amt) {
+                $index = $i;
+                break;
+            }
+        }
+        // 如果 avgRecharge 大于所有档位,取最后三位
+        if ($index > count($amounts) - 3) {
+            $index = count($amounts) - 3;
+        }
+
+        $tiers = [];
+        for ($i = 0; $i < 3; $i++) {
+            $tiers[] = [
+                'amount'        => $amounts[$index + $i],
+                'bonus_percent' => $bonusPercents[$i],
+            ];
+        }
+
+        return $tiers;
+    }
+
+    /**
+     * 召回礼包(gift_id=305)—— 检查条件和返回三档信息
+     *
+     * 新规则:
+     * - 连续 7 天未充值后弹出(原为 3 天)
+     * - 根据平均充值金额确定初档,显示三档(初/中/高)
+     * - 初档 70% 加成,中档 85%,高档 100%,通过 7 天签到领取
+     * - 签到从充值次日开始,按比例分配(7%/9%/11%/13%/14%/21%/25%)
+     * - 第 4-7 天需要当天流水 >= 100
      */
     public function vipInactiveGift(Request $request)
     {
@@ -1149,24 +1192,23 @@ class PayRechargeController extends Controller
         $this->setUserLocale($request);
         $userId = $user->UserID;
 
-        // 1. VIP 判断:是否有过充值(YN_VIPAccount.Recharge > 0)
+        // 1. VIP 判断:是否有过充值
         $userRecharge = DB::table(TableName::QPAccountsDB() . 'YN_VIPAccount')
             ->where('UserID', $userId)
             ->value('Recharge') ?: 0;
-        
+
         if ($userRecharge <= 0) {
-            // 不满足条件:不是 VIP 用户
             return apiReturnSuc([
                 'status' => 0,
                 'message' => __('web.gift.vip_inactive_not_vip')
             ]);
         }
 
-        // 2. 检查最近一次充值时间,计算连续未充值天数
+        // 2. 计算连续未充值天数(排除 305 礼包自身)
         $lastOrder = DB::table('agent.dbo.order')
             ->where('user_id', $userId)
             ->where('pay_status', 1)
-            ->where('GiftsID','<>', 305)
+            ->where('GiftsID', '<>', 305)
             ->orderBy('pay_at', 'desc')
             ->first();
 
@@ -1180,19 +1222,18 @@ class PayRechargeController extends Controller
         $lastDate = date('Y-m-d', strtotime($lastOrder->pay_at));
         $today = date('Y-m-d');
         $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
-        // 连续未充值天数:如果最后一次充值是昨天,则未充值天数为0;如果是前天,则为1;以此类推
-        // 需要 >= 3 天,即最后一次充值至少是3天前
         $inactiveDays = max(0, (int)$diffDays - 1);
 
-        // 5. 查询最近一条 7 日礼包记录状态(用于判断是否进入新一轮)
+        // 3. 查询最近一条礼包记录
         $record = DB::table('agent.dbo.inactive_vip_gift_records')
             ->where('user_id', $userId)
             ->where('gift_id', 305)
             ->orderBy('id', 'desc')
             ->first();
 
-        if ($inactiveDays < 3 && !$record) {
-            // 不满足条件:连续未充值天数不足
+        // 连续 7 天未充值才弹出(原为 3 天)
+        $requiredInactiveDays = 7;
+        if ($inactiveDays < $requiredInactiveDays && !$record) {
             return apiReturnSuc([
                 'status' => 0,
                 'message' => __('web.gift.vip_inactive_days_insufficient'),
@@ -1200,13 +1241,13 @@ class PayRechargeController extends Controller
             ]);
         }
 
-        // 3. 计算总充值与平均单笔(从 RecordUserTotalStatistics)
+        // 4. 计算总充值 & 平均单笔
         $stat = DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')
             ->where('UserID', $userId)
             ->select('Recharge', 'RechargeTimes')
             ->first();
 
-        $totalRecharge = (float)($stat->Recharge ?? 0); // 转换为元
+        $totalRecharge = (float)($stat->Recharge ?? 0);
         $rechargeTimes = (int)($stat->RechargeTimes ?? 0);
 
         if ($totalRecharge <= 0 || $rechargeTimes <= 0) {
@@ -1216,25 +1257,10 @@ class PayRechargeController extends Controller
             ]);
         }
 
-
-
         $avgRecharge = $totalRecharge / $rechargeTimes;
 
-        // 4. 按规则选择礼包金额
-        $amount = null;
-        if ($totalRecharge < 50) {
-            $amount = $avgRecharge < 15 ? 19.99 : 29.99;
-        } else {
-            if ($avgRecharge < 15) {
-                $amount = 19.99;
-            } elseif ($avgRecharge >= 15 && $avgRecharge < 20) {
-                $amount = 29.99;
-            } else {
-                $amount = 49.99;
-            }
-        }
-
-
+        // 5. 根据平均充值获取三档信息
+        $tiers = $this->getRecallGiftTiers($avgRecharge);
 
         /*
          * status 定义:
@@ -1242,37 +1268,37 @@ class PayRechargeController extends Controller
          * 1 满足条件未充值
          * 2 满足条件已充值,奖励未全部领取
          * 3 满足条件已充值,奖励全部领取
-         * 4 满足条件已充值,7天礼包已过期
          */
         $status = 1;
-        $timeLeft = 0; // 倒计时剩余秒数
-        
-        if ($record && strtotime($record->expired_at) >= time()) {
-            // 当前轮次仍有效:根据领取进度展示状态
-            $allClaimedMask = (1 << 7) - 1; // 0b1111111
-            $status = (($record->claimed_days_mask & $allClaimedMask) == $allClaimedMask) ? 3 : 2;
+        $timeLeft = 0;
+
+        if ($record) {
+            // 有记录:根据是否领满7天判断状态
+            $allClaimedMask = (1 << 7) - 1;
+            $allClaimed = (($record->claimed_days_mask & $allClaimedMask) == $allClaimedMask);
+            if ($allClaimed) {
+                $status = 3; // 全部领取(视为过期)
+            } else {
+                $status = 2; // 部分领取,进行中
+            }
         } else {
-            // 无记录 or 上一轮已过期:可进入新一轮,走未充值倒计时逻辑
-            // 满足条件但未充值:检查24小时倒计时(每天重置)
+            // 无记录:24 小时倒计时逻辑
             $today = date('Y-m-d');
             $timerKey = "vip_inactive_gift_timer_{$userId}_{$today}";
             $timerData = Redis::get($timerKey);
-            
+
             if ($timerData) {
                 $timerData = json_decode($timerData, true);
                 $expireTime = $timerData['expire_time'] ?? 0;
                 $timeLeft = max(0, $expireTime - time());
-                
+
                 if ($timeLeft <= 0) {
-                    // 今天的倒计时已结束,不再显示礼包
                     $status = 0;
                 }
             } else {
-                // 今天首次检测到满足条件,初始化24小时倒计时
-                // 从当前时间开始,24小时后过期
-                $expireTime = time() + 86400; // 24小时后
-                $ttl = 86400 + 3600; // Redis过期时间设为25小时,确保跨天也能获取
-                
+                $expireTime = time() + 86400;
+                $ttl = 86400 + 3600;
+
                 Redis::setex($timerKey, $ttl, json_encode([
                     'expire_time' => $expireTime,
                     'created_at' => time(),
@@ -1282,71 +1308,73 @@ class PayRechargeController extends Controller
             }
         }
 
-        $payload = [
-            'status' => $status,
-            'inactive_days' => $inactiveDays,
-//            'total_recharge' => $totalRecharge,
-//            'avg_recharge' => round($avgRecharge, 2),
-            'time_left' => $timeLeft,
-            'expire_at' => $timeLeft > 0 ? (time() + $timeLeft) : null,
-            'message' => $status == 1 ? __('web.gift.vip_inactive_can_recharge') :
-                        ($status == 2 ? __('web.gift.vip_inactive_claimed_partial') :
-                        ($status == 3 ? __('web.gift.vip_inactive_claimed_all') :
-                        __('web.gift.vip_inactive_seven_days_expired')))
-        ];
+        // 构建返回的三档列表
+        $list = [];
+        if ($status == 1) {
+            foreach ($tiers as $i => $tier) {
+                $amount = $tier['amount'];
+                $bonusPercent = $tier['bonus_percent'];
 
-        // status=1 时返回充值档位信息(参考 bankruptcyGift),gift_id 固定 305,加总奖励百分比
-        if ($status == 1 && $amount !== null) {
-            $sevenPercent = 140 + max(0, $inactiveDays - 3) * 10;
-            $sevenPercent = min($sevenPercent, 200);
-            $totalRewardPercent = 20 + $sevenPercent; // 120% 立即 + 7日礼包%
+                $gear = DB::table('agent.dbo.recharge_gear')
+                    ->select('money', 'favorable_price', 'gear', 'give')
+                    ->where('money', $amount)
+                    ->first();
 
-            $gear = DB::table('agent.dbo.recharge_gear')
-                ->select('money', 'favorable_price', 'gear', 'give')
-                ->where('money', $amount)
-                ->where('status', 1)
-                ->first();
+                $rechargeGearAmt = $amount;
+                // favorable_price = 本金(无立即奖励,全部通过签到领取)
+                $favorablePrice = $rechargeGearAmt;
+                $give = 0;
 
-            $favorablePrice = round($amount * 120 / 100, 2); // 305 固定 120% 立即到账
-            $give = $favorablePrice - $amount;
+                $item = $gear ? (clone $gear) : new \stdClass();
 
-            if ($gear) {
-                $gear->gift_id = 305;
-                $gear->favorable_price = $favorablePrice;
-                $gear->give = $give;
-                $gear->total_bonus = $totalRewardPercent;
-                $gear->bonus = $totalRewardPercent;
-                $gear->total_reward_percent = $totalRewardPercent;
-                $gear->recommend = 1;
-                if (!empty($gear->gear)) {
-                    $gear->gear = Util::filterGearByDevice($gear->gear,GlobalUserInfo::toWebData($user));
+                if ($gear) {
+                    $item->money = $amount;
+                    $item->favorable_price = $favorablePrice;
+                    $item->give = $give;
+                } else {
+                    $item->money = $amount;
+                    $item->favorable_price = $favorablePrice;
+                    $item->give = $give;
+                    $item->gear = null;
                 }
-                $payload['list'] = [$gear];
-            } else {
-                $payload['list'] = [(object)[
-                    'money' => $amount,
-                    'favorable_price' => $favorablePrice,
-                    'give' => $give,
-                    'gear' => null,
-                    'gift_id' => 305,
-                    'total_bonus' => $totalRewardPercent,
-                    'bonus' => $totalRewardPercent,
-                    'total_reward_percent' => $totalRewardPercent,
-                    'recommend' => 1,
-                ]];
-            }
-            $payload['amount'] = $amount;
-            $payload['total_reward_percent'] = $totalRewardPercent;
-            $payload['extra_reward'] = round($amount*$totalRewardPercent/100,2);
-            $payload['total_reward'] = round($amount*$totalRewardPercent/100+$amount,2);
-        } else {
-            $payload['amount'] = $amount;
 
-            if($record){
-                $payload['total_reward_percent'] = $record->total_percent-100;
+                $item->gift_id = 305;
+                $item->total_bonus = $bonusPercent;
+                $item->bonus = $bonusPercent;
+                $item->total_reward_percent = $bonusPercent;
+                $item->tier_index = $i; // 0=初档, 1=中档, 2=高档
+                $item->recommend = ($i == 1) ? 1 : 0; // 默认中档
+
+                if (!empty($item->gear)) {
+                    $item->gear = Util::filterGearByDevice($item->gear, GlobalUserInfo::toWebData($user));
+                }
+
+                // 额外奖励信息
+                $item->extra_reward = round($amount * $bonusPercent / 100, 2);
+                $item->total_reward = round($amount * $bonusPercent / 100 + $amount, 2);
+
+                $list[] = $item;
             }
         }
 
+        $payload = [
+            'status'         => $status,
+            'inactive_days'  => $inactiveDays,
+            'time_left'      => $timeLeft,
+            'expire_at'      => $timeLeft > 0 ? (time() + $timeLeft) : null,
+            'message'        => $status == 1 ? __('web.gift.vip_inactive_can_recharge') :
+                               ($status == 2 ? __('web.gift.vip_inactive_claimed_partial') :
+                               ($status == 3 ? __('web.gift.vip_inactive_claimed_all') :
+                               __('web.gift.vip_inactive_seven_days_expired'))),
+            'list'           => $status == 1 ? $list : [],
+            'tiers'          => $status == 1 ? $tiers : [],
+            'distribution'   => [7, 9, 11, 13, 14, 21, 25], // 7 天签到分配比例(%)
+        ];
+
+        if ($status != 1 && $record) {
+            $payload['total_reward_percent'] = $record->total_percent - 100;
+        }
+
         // 2025-05-15 暂停7天 到 2025-05-22
         $payload['list'] = [];
         if ($payload['status'] == 1) {
@@ -1359,6 +1387,8 @@ class PayRechargeController extends Controller
 
     /**
      * 连续未充值 VIP 礼包(gift_id=305)—— 获取 7 日礼包信息
+     * 签到从充值次日开始,按比例分配(7%/9%/11%/13%/14%/21%/25%)
+     * 第 4-7 天需要当天流水 >= 100
      */
     public function vipInactiveGiftSevenDays(Request $request)
     {
@@ -1377,129 +1407,96 @@ class PayRechargeController extends Controller
             return apiReturnFail(['web.gift.vip_inactive_no_record', __('web.gift.vip_inactive_no_record')]);
         }
 
-        // 计算从充值完成后的第几天(第1天为充值当天)
-        // 如果 created_at 是 2024-01-01,今天是 2024-01-01,daysPassed=0,表示可以领取第1天的奖励
+        // 计算从充值完成后的第几天(充值当天=0,次日=1)
         $createdDate = date('Y-m-d', strtotime($record->created_at));
         $today = date('Y-m-d');
         $daysPassed = floor((strtotime($today) - strtotime($createdDate)) / 86400);
-        // daysPassed = 0 表示充值当天(第1天),1 表示第2天,以此类推
 
-        // 检查是否已过期(超过7天)
-        $expiredAt = strtotime($record->expired_at);
-        $isExpired = time() > $expiredAt;
-
-        $perDayAmount = (float)$record->per_day_amount;
+        // 检查是否已领满7天(视为过期)
+        $allClaimedMask = (1 << 7) - 1;
         $claimedMask = (int)$record->claimed_days_mask;
+        $isExpired = ($claimedMask & $allClaimedMask) == $allClaimedMask;
+        $expiredAt = $isExpired ? strtotime($record->expired_at) : null;
+
+        $totalBonus = (float)$record->per_day_amount; // 存储的是总奖励金额
+
+        // 计算已签到次数
+        $claimedDaysCount = 0;
+        for ($i = 0; $i < 7; $i++) {
+            if ($claimedMask & (1 << $i)) {
+                $claimedDaysCount++;
+            }
+        }
 
-        // 构建每一天的状态(第1天为充值当天,对应 daysPassed = 0)
+        // 上次签到日期(用于判断今天是否已签)
+        $lastClaimDate = $record->updated_at ? date('Y-m-d', strtotime($record->updated_at)) : null;
+        $todayDate = date('Y-m-d');
+        $alreadyClaimedToday = ($lastClaimDate === $todayDate);
+
+        // 7 天签到分配比例
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+
+        // 构建每一天的状态
         $days = [];
         for ($day = 1; $day <= 7; $day++) {
             $dayIndex = $day - 1; // 位索引:day1对应bit0
             $isClaimed = ($claimedMask & (1 << $dayIndex)) > 0;
 
-            // 计算这一天对应的日期(充值后第 day-1 天;第1天为充值当天)
-            $targetDate = date('Ymd', strtotime($record->created_at . ' +' . ($day - 1) . ' days'));
+            // 当天可领取金额
+            $dayAmount = round($totalBonus * $distribution[$dayIndex] / 100, 2);
 
-            // 判断状态
-            // 0=不可领取, 1=可领取, 2=已领取, 3=过期
+            // 判断状态:0=不可领取, 1=可领取, 2=已领取, 3=过期
             $dayStatus = 0;
-            $betAmount = 0; // 初始化
-
-            // 第4-7天需要查询流水信息(无论状态如何,都要展示进度)
-            if ($day >= 4) {
-//                var_dump($userId,$day,$targetDate);
-                $todayBet = DB::table('QPRecordDB.dbo.RecordUserDataStatisticsNew')
-                    ->where('UserID', $userId)
-                    ->where('DateID', $targetDate)
-                    ->value('TotalBet') ?? 0;
-                $betAmount = $todayBet / NumConfig::NUM_VALUE; // 转换为元
-            }
-
-            $dayOffset = $day - 1; // 第1天对应 offset=0
+            $betAmount = 0;
 
             if ($isClaimed) {
                 $dayStatus = 2; // 已领取
-            } elseif ($daysPassed > $dayOffset) {
-                // 已过这一天但未领取:过期
-                // 例如:今天是第4天(daysPassed=4),但第3天的奖励还没领取,则第3天过期
-                $dayStatus = 3; // 过期
-            } elseif ($daysPassed < $dayOffset) {
-                // 还没到这一天:第 day 天的奖励需要在充值后的第 day-1 天才能领取
-                // 例如:第1天奖励需要 daysPassed >= 0(充值当天)才能领取
-                $dayStatus = 0; // 不可领取
-            } elseif ($isExpired || $daysPassed >= 7) {
-                // 整体过期(超过7天)时,未领取的奖励都过期
-                $dayStatus = 3; // 过期
-            } else {
-                // 到了这一天,判断是否可以领取
-                if ($day <= 3) {
-                    // 前3天:直接领取
-                    $dayStatus = 1; // 可领取
+            } elseif ($isExpired) {
+                // 整体过期(所有天数已领完)
+                $dayStatus = 3;
+            } elseif ($day == $claimedDaysCount + 1) {
+                // 下一个可签到的天数
+                if ($alreadyClaimedToday) {
+                    // 今天已签到过,不能再签
+                    $dayStatus = 0;
                 } else {
-                    // 第4-7天:需要当天游戏流水 >= 100(流水信息已在上面查询)
-                    if ($betAmount >= 100) {
-                        $dayStatus = 1; // 可领取
-                    } else {
-                        $dayStatus = 0; // 不可领取(流水不足)
-                    }
+                    $dayStatus = 1; // 可领取
                 }
-            }
-
-            // 计算进度条数据(仅第4-7天)
-            $betProgress = null;
-            if ($day >= 4) {
-                $requiredBet = 100;
-                $currentBetValue = round($betAmount, 2);
-                $progressPercent = $requiredBet > 0 ? min(100, round(($currentBetValue / $requiredBet) * 100, 2)) : 0;
-                
-                $betProgress = [
-                    'current' => $currentBetValue,        // 当前流水
-                    'required' => $requiredBet,           // 需要流水
-                    'progress_percent' => $progressPercent, // 进度百分比(0-100)
-                    'is_completed' => $currentBetValue >= $requiredBet // 是否完成
-                ];
+            } else {
+                // 已签过的(不在这里处理)或未来的天数
+                $dayStatus = 0;
             }
 
             $days[] = [
-                'day' => $day,
-                'amount' => $perDayAmount,
-                'status' => $dayStatus,
-                'status_text' => [
+                'day'          => $day,
+                'amount'       => $dayAmount,
+                'percent'      => $distribution[$dayIndex],
+                'status'       => $dayStatus,
+                'status_text'  => [
                     __('web.gift.status_cannot_claim'),
                     __('web.gift.status_can_claim'),
                     __('web.gift.status_claimed'),
                     __('web.gift.status_expired')
                 ][$dayStatus] ?? __('web.gift.status_unknown'),
-                'target_date' => $targetDate,
-                'bet_progress' => $betProgress // 第4-7天的流水进度信息
             ];
         }
 
-        // 检查是否有倒计时(未充值时的24小时倒计时)
-        $timerKey = "vip_inactive_gift_timer_{$userId}";
-        $timerData = Redis::get($timerKey);
-        $timeLeft = 0;
-        if ($timerData) {
-            $timerData = json_decode($timerData, true);
-            $expireTime = $timerData['expire_time'] ?? 0;
-            $timeLeft = max(0, $expireTime - time());
-        }
-
         return apiReturnSuc([
             'record' => [
-                'pay_amount' => (float)$record->pay_amount,
-                'total_percent' => (float)$record->total_percent-100,
-                'seven_days_percent' => (float)$record->seven_days_percent,
-                'per_day_amount' => $perDayAmount,
-                'inactive_days' => (int)$record->inactive_days,
-                'created_at' => $record->created_at,
-                'expired_at' => $record->expired_at
+                'pay_amount'          => (float)$record->pay_amount,
+                'total_percent'       => (float)$record->total_percent - 100,
+                'seven_days_percent'  => (float)$record->seven_days_percent,
+                'total_bonus'         => $totalBonus,
+                'inactive_days'       => (int)$record->inactive_days,
+                'last_sign_day' => $record ? (int)($record->claimed_days_mask ? floor(log($record->claimed_days_mask, 2)) + 1 : 0) : 0,
+                'created_at'          => $record->created_at,
+                'expired_at'          => $record->expired_at
             ],
-            'days' => $days,
-            'current_day' => min($daysPassed + 1, 7), // 当前是第几天
-            'is_expired' => $isExpired,
-            'time_left' => $timeLeft, // 未充值时的倒计时(秒)
-            'expire_at' => $expiredAt
+            'distribution' => $distribution,
+            'days'         => $days,
+            'claimed_count' => $claimedDaysCount, // 已签到次数
+            'is_expired'   => $isExpired,
+            'expire_at'    => $expiredAt
         ]);
     }
 
@@ -1528,58 +1525,47 @@ class PayRechargeController extends Controller
             return apiReturnFail(['web.gift.vip_inactive_no_record', __('web.gift.vip_inactive_no_record')]);
         }
 
-        // 检查是否已过期
-        if (strtotime($record->expired_at) < time()) {
+        // 检查是否已领满7天(视为过期)
+        $allClaimedMask = (1 << 7) - 1;
+        $claimedMaskCheck = (int)$record->claimed_days_mask;
+        if (($claimedMaskCheck & $allClaimedMask) == $allClaimedMask) {
             return apiReturnFail(['web.gift.vip_inactive_expired', __('web.gift.vip_inactive_expired')]);
         }
 
+        // 计算已签到次数,确定下一次签到的天数
+        $claimedMask = (int)$record->claimed_days_mask;
+        $claimedDaysCount = 0;
+        for ($i = 0; $i < 7; $i++) {
+            if ($claimedMask & (1 << $i)) {
+                $claimedDaysCount++;
+            }
+        }
 
-        // 计算从充值完成后的第几天(第1天为充值当天)
-        $createdDate = date('Y-m-d', strtotime($record->created_at));
-        $today = date('Y-m-d');
-        $daysPassed = floor((strtotime($today) - strtotime($createdDate)) / 86400);
-
+        $day = $claimedDaysCount + 1;
 
-        $day = $daysPassed+1;
+        if ($day < 1 || $day > 7) {
+            return apiReturnFail(['web.gift.vip_inactive_day_not_time', __('web.gift.vip_inactive_day_not_time')]);
+        }
 
         // 检查是否已领取
-        $claimedMask = (int)$record->claimed_days_mask;
         $dayIndex = $day - 1;
         if ($claimedMask & (1 << $dayIndex)) {
             return apiReturnFail(['web.gift.vip_inactive_day_claimed', str_replace(':day', $day, __('web.gift.vip_inactive_day_claimed'))]);
         }
 
-
-
-        // 第 day 天的奖励只能在充值后的第 day-1 天领取(daysPassed == day-1)
-        $dayOffset = $day - 1;
-
-        // 检查是否到了这一天
-        if ($daysPassed < $dayOffset) {
-            return apiReturnFail(['web.gift.vip_inactive_day_not_time', str_replace(':day', $day, __('web.gift.vip_inactive_day_not_time'))]);
-        }
-        
-        // 检查是否已过期:如果已经过了这一天(daysPassed > day),则不能领取
-        if ($daysPassed > $dayOffset) {
-            return apiReturnFail(['web.gift.vip_inactive_day_expired', str_replace(':day', $day, __('web.gift.vip_inactive_day_expired'))]);
+        // 每天只能签到一次:检查上次签到日期是否今天
+        $lastClaimDate = $record->updated_at ? date('Y-m-d', strtotime($record->updated_at)) : null;
+        $todayDate = date('Y-m-d');
+        if ($lastClaimDate === $todayDate) {
+            return apiReturnFail(['web.gift.vip_inactive_day_claimed_today', __('web.gift.vip_inactive_day_claimed_today')]);
         }
 
-        // 第4-7天需要检查当天游戏流水 >= 100
-        if ($day >= 4) {
-            $targetDate = date('Ymd', strtotime($record->created_at . ' +' . $dayOffset . ' days'));
-            $todayBet = DB::table('QPRecordDB.dbo.RecordUserDataStatisticsNew')
-                ->where('UserID', $userId)
-                ->where('DateID', $targetDate)
-                ->value('TotalBet') ?? 0;
-
-            $betAmount = $todayBet / NumConfig::NUM_VALUE; // 转换为元
-
-            if ($betAmount < 100) {
-                return apiReturnFail(['web.gift.vip_inactive_bet_insufficient', str_replace(':amount', 100, __('web.gift.vip_inactive_bet_insufficient'))]);
-            }
-        }
 
-        $amount = (float)$record->per_day_amount;
+        // 7 天签到分配比例
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+        $totalBonus = (float)$record->per_day_amount; // 存储的是总奖励金额
+        $dayIndex = (int)($day - 1);
+        $amount = round($totalBonus * $distribution[$dayIndex] / 100, 2);
 
         try {
             // 发放奖励
@@ -1591,12 +1577,20 @@ class PayRechargeController extends Controller
 
             // 更新位标记
             $newMask = $claimedMask | (1 << $dayIndex);
+            $updateData = [
+                'claimed_days_mask' => $newMask,
+                'updated_at' => date('Y-m-d H:i:s')
+            ];
+
+            // 如果领满7天,在 expired_at 记录完成时间
+            $allClaimedMask = (1 << 7) - 1;
+            if (($newMask & $allClaimedMask) == $allClaimedMask) {
+                $updateData['expired_at'] = date('Y-m-d H:i:s');
+            }
+
             DB::connection('write')->table('agent.dbo.inactive_vip_gift_records')
                 ->where('id', $record->id)
-                ->update([
-                    'claimed_days_mask' => $newMask,
-                    'updated_at' => date('Y-m-d H:i:s')
-                ]);
+                ->update($updateData);
 
             \Log::info('连续未充值VIP礼包奖励领取成功', [
                 'user_id' => $userId,

+ 78 - 32
app/Services/OrderServices.php

@@ -201,8 +201,9 @@ class  OrderServices
                     ->where('gift_id', 305)
                     ->orderBy('id', 'desc')
                     ->first();
-
-                $canStartNewRound = !$latestRecord || (strtotime($latestRecord->expired_at) < time());
+                // 有记录:根据是否领满7天判断状态
+                $allClaimedMask = (1 << 7) - 1;
+                $canStartNewRound = !$latestRecord || ((($latestRecord->claimed_days_mask & $allClaimedMask) == $allClaimedMask));
 
                 if (!$canStartNewRound) {
                     // 当前轮次未过期:只发放本金(不发放奖励)
@@ -212,13 +213,33 @@ class  OrderServices
                     $czReason = 1;
                     $cjReason = 45;
                 } else {
-                    // 开启新一轮 305:发放 120% 立即奖励并创建新的 7 日礼包记录
-                    $favorable_price = round($payAmt * 120 / 100, 2);
-                    $give = $favorable_price - $payAmt;
+                    // 开启新一轮 305:无立即奖励,全部奖励通过 7 天签到领取
                     $Recharge = $payAmt;
+                    $give = 0;
+                    $favorable_price = $payAmt;
                     $czReason = 50;
                     $cjReason = 51;
-                    $this->createInactiveVipGiftRecord($user_id, $payAmt);
+
+                    // 确定奖励百分比
+                    $bonusPercent = 100;
+                    $stat = DB::connection('write')->table('QPRecordDB.dbo.RecordUserTotalStatistics')
+                        ->where('UserID', $user_id)
+                        ->select('Recharge', 'RechargeTimes')
+                        ->first();
+                    $totalRecharge = (float)($stat->Recharge ?? 0);
+                    $rechargeTimes = (int)($stat->RechargeTimes ?? 0);
+                    if ($rechargeTimes > 0) {
+                        $avgRecharge = $totalRecharge / $rechargeTimes;
+                        $tiers = $this->getRecallGiftTiers($avgRecharge);
+                        foreach ($tiers as $tier) {
+                            if ((float)$tier['amount'] == (float)$payAmt) {
+                                $bonusPercent = $tier['bonus_percent'];
+                                break;
+                            }
+                        }
+                    }
+
+                    $this->createInactiveVipGiftRecord($user_id, $payAmt, $bonusPercent);
                 }
 
             } else if ($GiftsID == 306) { // free bonus礼包
@@ -678,56 +699,81 @@ class  OrderServices
     }
 
     /**
-     * 连续未充值 VIP 礼包(gift_id=305)—— 创建 7 日礼包记录
+     * 获取召回礼包(gift_id=305)三档金额及对应奖励百分比
+     * 可用档位:9.99, 19.99, 29.99, 39.99, 49.99, 59.99, 79.99, 99.99
+     * 初档 100% 加成,后续每档增加 15%
+     * @param float $avgRecharge 用户平均充值金额
+     * @return array [['amount'=>float, 'bonus_percent'=>int], ...] 三档信息
+     */
+    private function getRecallGiftTiers($avgRecharge)
+    {
+        $amounts = [9.99, 19.99, 29.99, 39.99, 49.99, 59.99, 79.99, 99.99];
+        $bonusPercents = [100, 115, 130];
+
+        $index = count($amounts);
+        foreach ($amounts as $i => $amt) {
+            if ($avgRecharge <= $amt) {
+                $index = $i;
+                break;
+            }
+        }
+        if ($index > count($amounts) - 3) {
+            $index = count($amounts) - 3;
+        }
+
+        $tiers = [];
+        for ($i = 0; $i < 3; $i++) {
+            $tiers[] = [
+                'amount'        => $amounts[$index + $i],
+                'bonus_percent' => $bonusPercents[$i],
+            ];
+        }
+
+        return $tiers;
+    }
+
+    /**
+     * 召回礼包(gift_id=305)—— 创建 7 日签到礼包记录
      * @param int   $user_id
-     * @param float $payAmt  本次充值金额(元)
+     * @param float $payAmt       本次充值金额(元)
+     * @param int   $bonusPercent 奖励百分比(70/85/100)
      */
-    private function createInactiveVipGiftRecord($user_id, $payAmt)
+    private function createInactiveVipGiftRecord($user_id, $payAmt, $bonusPercent = 70)
     {
-        // 获取上一次充值订单(不包括刚刚充值的这一条),用于计算连续未充值天数
-        // 计算从最后一次充值的日期到今天的未充值天数
+        // 获取上一次充值订单
         $prevOrder = DB::connection('write')->table('agent.dbo.order')
             ->where('user_id', $user_id)
             ->where('pay_status', 1)
-            ->where('GiftsID', '!=', 305) // 排除305礼包本身,避免重复计算
+            ->where('GiftsID', '!=', 305)
             ->orderBy('pay_at', 'desc')
             ->first();
 
-        // 默认连续未充值天数至少为 3(以便至少满足条件)
-        $inactiveDays = 3;
-        
+        $inactiveDays = 7;
         if ($prevOrder) {
-            // 计算从最后一次充值到今天的未充值天数
             $prevDate = date('Y-m-d', strtotime($prevOrder->pay_at));
             $today = date('Y-m-d');
             $diffDays = (strtotime($today) - strtotime($prevDate)) / 86400;
-            // 连续未充值天数:如果最后一次充值是昨天,则未充值天数为0;如果是前天,则为1;以此类推
-            $inactiveDays = max(3, (int)$diffDays - 1); // 至少为3,确保满足条件
+            $inactiveDays = max(7, (int)$diffDays - 1);
         }
 
-        // 七日礼包总奖励百分比:140% + (inactiveDays - 3) * 10%,上限 200%
-        $sevenPercent = 140 + max(0, $inactiveDays - 3) * 10;
-        $sevenPercent = min($sevenPercent, 200);
-
-        // 总奖励百分比 = 120%(立即奖励) + 七日礼包百分比
-        $totalPercent = 120 + $sevenPercent;
+        // 总奖励百分比 = 100% 本金 + bonusPercent% 奖励
+        $totalPercent = 100 + $bonusPercent;
+        $sevenDaysPercent = $bonusPercent;
 
-        // 七日礼包总金额 & 每日金额(7天平均分配,包括充值当天)
-        $sevenTotalAmount = round($payAmt * $sevenPercent / 100, 2);
-        $perDayAmount     = round($sevenTotalAmount / 7, 2);
+        // 7 天签到总奖励金额(全部通过签到领取)
+        $totalBonus = round($payAmt * $bonusPercent / 100, 2);
 
         $now   = date('Y-m-d H:i:s');
-        // 过期时间:从充值当天算起,共 7 天可领取(第1天为充值当天),
-        // 因此 expired_at 设为创建时间 + 6 天
-        $expAt = date('Y-m-d H:i:s', strtotime('+6 days'));
+        // 签到从充值次日开始,共 7 天,所以过期时间为创建时间 + 7 天
+        $expAt = date('Y-m-d H:i:s', strtotime('+7 days'));
 
         DB::connection('write')->table('agent.dbo.inactive_vip_gift_records')->insert([
             'user_id'            => $user_id,
             'gift_id'            => 305,
             'pay_amount'         => $payAmt,
             'total_percent'      => $totalPercent,
-            'seven_days_percent' => $sevenPercent,
-            'per_day_amount'     => $perDayAmount,
+            'seven_days_percent' => $sevenDaysPercent,
+            'per_day_amount'     => $totalBonus, // 存储总奖励金额,按比例分配
             'inactive_days'      => $inactiveDays,
             'claimed_days_mask'  => 0,
             'created_at'         => $now,

+ 1 - 1
resources/lang/en/web.php

@@ -109,7 +109,7 @@ return [
         'status_expired' => 'Expired',
         'status_unknown' => 'Unknown',
         'vip_inactive_not_vip' => 'You are not a VIP user yet',
-        'vip_inactive_days_insufficient' => 'Insufficient consecutive inactive days (need at least 3 days)',
+        'vip_inactive_days_insufficient' => 'Insufficient consecutive inactive days (need at least 7 days)',
         'vip_inactive_can_recharge' => 'Can recharge gift',
         'vip_inactive_claimed_partial' => 'Recharged, rewards not all claimed',
         'vip_inactive_claimed_all' => 'Recharged, all rewards claimed',

+ 340 - 0
tests/Unit/RecallGiftLogicTest.php

@@ -0,0 +1,340 @@
+<?php
+
+namespace Tests\Unit;
+
+use Tests\TestCase;
+
+/**
+ * 测试召回礼包(gift_id=305)核心逻辑:tier 计算、分布比例、天数计算
+ *
+ * @see PayRechargeController::getRecallGiftTiers()
+ * @see PayRechargeController::vipInactiveGiftSevenDays()
+ * @see PayRechargeController::claimVipInactiveGiftDayReward()
+ */
+class RecallGiftLogicTest extends TestCase
+{
+    /**
+     * 可用档位:9, 19, 29, 39, 49, 59, 79, 99
+     * 初档 70%,中档 85%,高档 100%
+     */
+
+    /** @test */
+    public function it_selects_first_three_tiers_when_avg_below_minimum()
+    {
+        // avgRecharge < 9 → 初档=9, 中档=19, 高档=29
+        $tiers = $this->getRecallGiftTiers(5);
+        $this->assertCount(3, $tiers);
+        $this->assertEquals(9, $tiers[0]['amount']);
+        $this->assertEquals(19, $tiers[1]['amount']);
+        $this->assertEquals(29, $tiers[2]['amount']);
+        $this->assertEquals(70, $tiers[0]['bonus_percent']);
+        $this->assertEquals(85, $tiers[1]['bonus_percent']);
+        $this->assertEquals(100, $tiers[2]['bonus_percent']);
+    }
+
+    /** @test */
+    public function it_selects_tiers_based_on_average_recharge()
+    {
+        // avgRecharge = 15,初档应为第一个 >= 15 的档位 = 19
+        $tiers = $this->getRecallGiftTiers(15);
+        $this->assertCount(3, $tiers);
+        $this->assertEquals(19, $tiers[0]['amount']);
+        $this->assertEquals(29, $tiers[1]['amount']);
+        $this->assertEquals(39, $tiers[2]['amount']);
+    }
+
+    /** @test */
+    public function it_selects_last_three_tiers_when_avg_above_maximum()
+    {
+        // avgRecharge = 100 > 99,取最后三位:59, 79, 99
+        $tiers = $this->getRecallGiftTiers(100);
+        $this->assertCount(3, $tiers);
+        $this->assertEquals(59, $tiers[0]['amount']);
+        $this->assertEquals(79, $tiers[1]['amount']);
+        $this->assertEquals(99, $tiers[2]['amount']);
+        $this->assertEquals(70, $tiers[0]['bonus_percent']); // 初档
+        $this->assertEquals(85, $tiers[1]['bonus_percent']); // 中档
+        $this->assertEquals(100, $tiers[2]['bonus_percent']); // 高档
+    }
+
+    /** @test */
+    public function it_handles_exact_boundary_values()
+    {
+        // avgRecharge = 9,恰好等于初档
+        $tiers = $this->getRecallGiftTiers(9);
+        $this->assertEquals(9, $tiers[0]['amount']);
+
+        // avgRecharge = 29,初档应为 29
+        $tiers = $this->getRecallGiftTiers(29);
+        $this->assertEquals(29, $tiers[0]['amount']);
+        $this->assertEquals(39, $tiers[1]['amount']);
+        $this->assertEquals(49, $tiers[2]['amount']);
+    }
+
+    /** @test */
+    public function it_handles_avg_between_tiers()
+    {
+        // avgRecharge = 25,第一个 >= 25 的是 29
+        $tiers = $this->getRecallGiftTiers(25);
+        $this->assertEquals(29, $tiers[0]['amount']);
+        $this->assertEquals(39, $tiers[1]['amount']);
+        $this->assertEquals(49, $tiers[2]['amount']);
+    }
+
+    /** @test */
+    public function distribution_sums_to_100_percent()
+    {
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+        $this->assertEquals(100, array_sum($distribution));
+    }
+
+    /** @test */
+    public function daily_amounts_match_distribution()
+    {
+        // 如果总奖励 = $10.00,按比例分布
+        $totalBonus = 10.00;
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+
+        $dayAmounts = [];
+        for ($i = 0; $i < 7; $i++) {
+            $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
+        }
+
+        $this->assertEquals(0.70, $dayAmounts[0]); // Day 1: 7%
+        $this->assertEquals(0.90, $dayAmounts[1]); // Day 2: 9%
+        $this->assertEquals(1.10, $dayAmounts[2]); // Day 3: 11%
+        $this->assertEquals(1.30, $dayAmounts[3]); // Day 4: 13%
+        $this->assertEquals(1.40, $dayAmounts[4]); // Day 5: 14%
+        $this->assertEquals(2.10, $dayAmounts[5]); // Day 6: 21%
+        $this->assertEquals(2.50, $dayAmounts[6]); // Day 7: 25%
+        // 总和应等于 $10.00(允许浮点误差)
+        $this->assertEquals(10.00, round(array_sum($dayAmounts), 2));
+    }
+
+    /** @test */
+    public function daily_amounts_match_distribution_realistic_bonus()
+    {
+        // payAmt=19, bonusPercent=70% → totalBonus = 19 * 0.70 = 13.30
+        $payAmt = 19.00;
+        $bonusPercent = 70;
+        $totalBonus = round($payAmt * $bonusPercent / 100, 2);
+        $this->assertEquals(13.30, $totalBonus);
+
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+        $dayAmounts = [];
+        for ($i = 0; $i < 7; $i++) {
+            $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
+        }
+        $this->assertEquals(0.93, $dayAmounts[0]); // 13.30 * 7% = 0.931
+        $this->assertEquals(1.20, $dayAmounts[1]); // 13.30 * 9% = 1.197
+        $this->assertEquals(1.46, $dayAmounts[2]); // 13.30 * 11% = 1.463
+        $this->assertEquals(1.73, $dayAmounts[3]); // 13.30 * 13% = 1.729
+        $this->assertEquals(1.86, $dayAmounts[4]); // 13.30 * 14% = 1.862
+        $this->assertEquals(2.79, $dayAmounts[5]); // 13.30 * 21% = 2.793
+        $this->assertEquals(3.33, $dayAmounts[6]); // 13.30 * 25% = 3.325
+    }
+
+    /** @test */
+    public function inactive_days_calculation_is_correct()
+    {
+        // 模拟:最后一次充值在 7 天前
+        $lastDate = date('Y-m-d', strtotime('-7 days'));
+        $today = date('Y-m-d');
+        $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
+        $inactiveDays = max(0, (int)$diffDays - 1);
+
+        // 7天前充值 → diffDays=7 → inactiveDays=6
+        // 但需要连续7天未充值,所以 lastPay 应是8天前
+        $this->assertEquals(6, $inactiveDays);
+
+        // 8天前充值 → diffDays=8 → inactiveDays=7(满足条件)
+        $lastDate = date('Y-m-d', strtotime('-8 days'));
+        $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
+        $inactiveDays = max(0, (int)$diffDays - 1);
+        $this->assertEquals(7, $inactiveDays);
+    }
+
+    /** @test */
+    public function day_offset_from_recharge_to_signin()
+    {
+        // 如果充值在 2026-05-14
+        $createdAt = '2026-05-14';
+        
+        // 签到第1天 = 充值次日 = 2026-05-15
+        $day1Target = date('Ymd', strtotime($createdAt . ' +1 days'));
+        $this->assertEquals('20260515', $day1Target);
+        
+        // 签到第7天 = 充值 + 7天 = 2026-05-21
+        $day7Target = date('Ymd', strtotime($createdAt . ' +7 days'));
+        $this->assertEquals('20260521', $day7Target);
+    }
+
+    /** @test */
+    public function days_passed_calculation()
+    {
+        // 充值当天
+        $createdAt = '2026-05-14';
+        $today = '2026-05-14';
+        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
+        $this->assertEquals(0, $daysPassed); // 还不能签到
+
+        // 充值次日
+        $today = '2026-05-15';
+        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
+        $this->assertEquals(1, $daysPassed); // 可以签到第1天
+
+        // 充值7天后
+        $today = '2026-05-21';
+        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
+        $this->assertEquals(7, $daysPassed); // 可以签到第7天
+
+        // 充值8天后(过期)
+        $today = '2026-05-22';
+        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
+        $this->assertEquals(8, $daysPassed); // 超过7天,过期
+    }
+
+    /** @test */
+    public function claim_day_mapping()
+    {
+        // 按实际签到次数确定天数,不再依赖自然日
+        // 已签到0次 → day=1 (第1次签到) → dayIndex=0 → 7%
+        $claimedDaysCount = 0;
+        $day = $claimedDaysCount + 1;
+        $dayIndex = $day - 1; // 0
+        $distribution = [7, 9, 11, 13, 14, 21, 25];
+        $this->assertEquals(0, $dayIndex);
+        $this->assertEquals(7, $distribution[$dayIndex]);
+
+        // 已签到1次 → day=2 (第2次签到) → dayIndex=1 → 9%
+        $claimedDaysCount = 1;
+        $day = $claimedDaysCount + 1;
+        $dayIndex = $day - 1; // 1
+        $this->assertEquals(1, $dayIndex);
+        $this->assertEquals(9, $distribution[$dayIndex]);
+
+        // 已签到3次 → day=4 (第4次签到) → dayIndex=3 → 13%
+        $claimedDaysCount = 3;
+        $day = $claimedDaysCount + 1;
+        $dayIndex = $day - 1; // 3
+        $this->assertEquals(3, $dayIndex);
+        $this->assertEquals(13, $distribution[$dayIndex]);
+
+        // 已签到6次 → day=7 (第7次签到) → dayIndex=6 → 25%
+        $claimedDaysCount = 6;
+        $day = $claimedDaysCount + 1;
+        $dayIndex = $day - 1; // 6
+        $this->assertEquals(6, $dayIndex);
+        $this->assertEquals(25, $distribution[$dayIndex]);
+    }
+
+    /** @test */
+    public function claimed_count_determines_next_day()
+    {
+        // 签到次数直接影响下一次能签第几天
+
+        // 已签到0次,下次签第1天
+        $claimedMask = 0; // 0b0000000
+        $claimedCount = 0;
+        for ($i = 0; $i < 7; $i++) {
+            if ($claimedMask & (1 << $i)) {
+                $claimedCount++;
+            }
+        }
+        $nextDay = $claimedCount + 1;
+        $this->assertEquals(1, $nextDay);
+
+        // 已签到2次(第1、2天),下次签第3天
+        $claimedMask = (1 << 0) | (1 << 1); // 0b0000011 = 3
+        $claimedCount = 0;
+        for ($i = 0; $i < 7; $i++) {
+            if ($claimedMask & (1 << $i)) {
+                $claimedCount++;
+            }
+        }
+        $nextDay = $claimedCount + 1;
+        $this->assertEquals(3, $nextDay);
+        $this->assertEquals(2, $claimedCount);
+
+        // 已签到7次,所有签完
+        $claimedMask = (1 << 7) - 1; // 0b1111111 = 127
+        $claimedCount = 0;
+        for ($i = 0; $i < 7; $i++) {
+            if ($claimedMask & (1 << $i)) {
+                $claimedCount++;
+            }
+        }
+        $nextDay = $claimedCount + 1;
+        $this->assertEquals(7, $claimedCount);
+        $this->assertEquals(8, $nextDay); // > 7,不能再签
+    }
+
+    /** @test */
+    public function only_one_claim_per_day()
+    {
+        // 如果今天已经签过,不能再次签到
+        $lastClaimDate = '2026-05-15';
+        $todayDate = '2026-05-15';
+        $this->assertTrue($lastClaimDate === $todayDate); // 今天已签
+
+        $lastClaimDate = '2026-05-14';
+        $todayDate = '2026-05-15';
+        $this->assertFalse($lastClaimDate === $todayDate); // 今天未签
+    }
+
+    /** @test */
+    public function claimed_bits_match_seven_days_view()
+    {
+        // sevenDays 用 bit0 表示 day1,bit1 表示 day2 ...
+        // claim 中用 dayIndex = day-1,也映射到 bit0=day1, bit1=day2
+
+        // 模拟已领取 day1, day2, day3
+        $claimedMask = (1 << 0) | (1 << 1) | (1 << 2); // 0b0000111 = 7
+
+        $this->assertTrue((bool)($claimedMask & (1 << 0))); // day1 已领
+        $this->assertTrue((bool)($claimedMask & (1 << 1))); // day2 已领
+        $this->assertTrue((bool)($claimedMask & (1 << 2))); // day3 已领
+        $this->assertFalse((bool)($claimedMask & (1 << 3))); // day4 未领
+        $this->assertFalse((bool)($claimedMask & (1 << 6))); // day7 未领
+
+        // 所有 7 天都已领完
+        $allClaimedMask = (1 << 7) - 1; // 0b1111111 = 127
+        $this->assertEquals(127, $allClaimedMask);
+
+        $isAllClaimed = ($claimedMask & $allClaimedMask) == $allClaimedMask;
+        $this->assertFalse($isAllClaimed); // 只领了3天
+
+        $fullMask = 0b1111111;
+        $this->assertTrue(($fullMask & $allClaimedMask) == $allClaimedMask);
+    }
+
+    /**
+     * 复制控制器中的 getRecallGiftTiers 逻辑用于纯逻辑测试
+     */
+    private function getRecallGiftTiers($avgRecharge)
+    {
+        $amounts = [9, 19, 29, 39, 49, 59, 79, 99];
+        $bonusPercents = [70, 85, 100];
+
+        $index = count($amounts);
+        foreach ($amounts as $i => $amt) {
+            if ($avgRecharge <= $amt) {
+                $index = $i;
+                break;
+            }
+        }
+        if ($index > count($amounts) - 3) {
+            $index = count($amounts) - 3;
+        }
+
+        $tiers = [];
+        for ($i = 0; $i < 3; $i++) {
+            $tiers[] = [
+                'amount'        => $amounts[$index + $i],
+                'bonus_percent' => $bonusPercents[$i],
+            ];
+        }
+
+        return $tiers;
+    }
+}