Xboard/app/Services/PlanService.php
xboard 0f43fff242
Some checks are pending
Docker Build and Publish / build (push) Waiting to run
feat: new xboard
2025-01-21 14:57:54 +08:00

194 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\Plan;
use App\Models\User;
use App\Exceptions\ApiException;
use Illuminate\Database\Eloquent\Collection;
class PlanService
{
public Plan $plan;
public function __construct(Plan $plan)
{
$this->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;
}
}