plan = $plan; } /** * 获取所有可销售的订阅计划列表 * 条件:show 和 sell 为 true,且容量充足 * * @return Collection */ public function getAvailablePlans(): Collection { return Plan::where('show', true) ->where('sell', true) ->orderBy('sort') ->get() ->filter(function ($plan) { return $this->hasCapacity($plan); }); } /** * 获取指定订阅计划的可用状态 * 条件:renew 和 sell 为 true * * @param int $planId * @return Plan|null */ public function getAvailablePlan(int $planId): ?Plan { return Plan::where('id', $planId) ->where('sell', true) ->where('renew', true) ->first(); } /** * 检查指定计划是否可用于指定用户 * * @param Plan $plan * @param User $user * @return bool */ public function isPlanAvailableForUser(Plan $plan, User $user): bool { // 如果是续费 if ($user->plan_id === $plan->id) { return $plan->renew; } // 如果是新购 return $plan->show && $plan->sell && $this->hasCapacity($plan); } public function validatePurchase(User $user, string $period): void { if (!$this->plan) { throw new ApiException(__('Subscription plan does not exist')); } // 转换周期格式为新版格式 $periodKey = self::getPeriodKey($period); if ($periodKey === Plan::PERIOD_RESET_TRAFFIC) { $this->validateResetTrafficPurchase($user); return; } // 检查价格时使用新版格式 if (!isset($this->plan->prices[$periodKey])) { throw new ApiException(__('This payment period cannot be purchased, please choose another period')); } if ($user->plan_id !== $this->plan->id && !$this->hasCapacity($this->plan)) { throw new ApiException(__('Current product is sold out')); } $this->validatePlanAvailability($user); } /** * 智能转换周期格式为新版格式 * 如果是新版格式直接返回,如果是旧版格式则转换为新版格式 * * @param string $period * @return string */ public static function getPeriodKey(string $period): string { // 如果是新版格式直接返回 if (in_array($period, self::getNewPeriods())) { return $period; } // 如果是旧版格式则转换为新版格式 return Plan::LEGACY_PERIOD_MAPPING[$period] ?? $period; } /** * 只能转换周期格式为旧版本 */ public static function convertToLegacyPeriod(string $period): string { return Plan::LEGACY_PERIOD_MAPPING[$period] ?? $period; } /** * 获取所有支持的新版周期格式 * * @return array */ public static function getNewPeriods(): array { return array_values(Plan::LEGACY_PERIOD_MAPPING); } /** * 获取旧版周期格式 * * @param string $period * @return string */ public static function getLegacyPeriod(string $period): string { $flipped = array_flip(Plan::LEGACY_PERIOD_MAPPING); return $flipped[$period] ?? $period; } protected function validateResetTrafficPurchase(User $user): void { if (!app(UserService::class)->isAvailable($user) || $this->plan->id !== $user->plan_id) { throw new ApiException(__('Subscription has expired or no active subscription, unable to purchase Data Reset Package')); } } protected function validatePlanAvailability(User $user): void { if ((!$this->plan->show && !$this->plan->renew) || (!$this->plan->show && $user->plan_id !== $this->plan->id)) { throw new ApiException(__('This subscription has been sold out, please choose another subscription')); } if (!$this->plan->renew && $user->plan_id == $this->plan->id) { throw new ApiException(__('This subscription cannot be renewed, please change to another subscription')); } if (!$this->plan->show && $this->plan->renew && !app(UserService::class)->isAvailable($user)) { throw new ApiException(__('This subscription has expired, please change to another subscription')); } } public function hasCapacity(Plan $plan): bool { if ($plan->capacity_limit === null) { return true; } $activeUserCount = User::where('plan_id', $plan->id) ->where(function ($query) { $query->where('expired_at', '>=', time()) ->orWhereNull('expired_at'); }) ->count(); return ($plan->capacity_limit - $activeUserCount) > 0; } public function getAvailablePeriods(Plan $plan): array { return array_filter( $plan->getActivePeriods(), fn($period) => isset($plan->prices[$period]) && $plan->prices[$period] > 0 ); } public function canResetTraffic(Plan $plan): bool { return $plan->reset_traffic_method !== Plan::RESET_TRAFFIC_NEVER && $plan->getResetTrafficPrice() > 0; } }