From 82e29753b82c89fcdbe56dbd529892fbd8ca5021 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sun, 12 Apr 2026 14:17:01 +0800 Subject: [PATCH] =?UTF-8?q?vip=E4=BC=9A=E5=91=98=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A1=A5=E5=B7=AE=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/VipLevel.php | 38 ++++++++- app/Services/VipPaymentService.php | 34 ++++++-- app/Services/VipService.php | 19 +++-- resources/views/vip/center.blade.php | 111 ++++++++++++++++++--------- 4 files changed, 155 insertions(+), 47 deletions(-) diff --git a/app/Models/VipLevel.php b/app/Models/VipLevel.php index 2254eab..4e601b4 100644 --- a/app/Models/VipLevel.php +++ b/app/Models/VipLevel.php @@ -73,11 +73,47 @@ class VipLevel extends Model 'exp_multiplier' => 'float', 'jjb_multiplier' => 'float', 'sort_order' => 'integer', - 'price' => 'integer', + 'price' => 'float', 'duration_days' => 'integer', 'allow_custom_messages' => 'boolean', ]; + /** + * 判断当前等级是否高于指定等级。 + * 依靠 sort_order 判断。 + */ + public function isHigherThan(self|int|null $other): bool + { + if ($other === null) { + return true; + } + + $otherOrder = ($other instanceof self) + ? $other->sort_order + : self::where('id', $other)->value('sort_order') ?? 0; + + return $this->sort_order > $otherOrder; + } + + /** + * 计算相对于另一个等级的差价。 + * 如果当前等级价格更低,则返回 0。 + */ + public function getUpgradePrice(self|int|null $other): float + { + if ($other === null) { + return (float) $this->price; + } + + $otherPrice = ($other instanceof self) + ? (float) $other->price + : (float) (self::where('id', $other)->value('price') ?? 0); + + $diff = (float) $this->price - $otherPrice; + + return max(0.0, $diff); + } + /** * 关联:该等级下的所有用户 */ diff --git a/app/Services/VipPaymentService.php b/app/Services/VipPaymentService.php index 6c80497..9877b7e 100644 --- a/app/Services/VipPaymentService.php +++ b/app/Services/VipPaymentService.php @@ -35,8 +35,26 @@ class VipPaymentService */ public function createLocalOrder(User $user, VipLevel $vipLevel): VipPaymentOrder { - if ((float) $vipLevel->price <= 0) { - throw new RuntimeException('当前 VIP 等级未设置在线支付价格,暂不支持直接购买。'); + $currentVip = $user->isVip() ? $user->vipLevel : null; + $isUpgrade = $currentVip && $vipLevel->isHigherThan($currentVip); + + // 如果已经是该等级或更高级别,且不是永久会员续费(逻辑上续费应该用原价,但此处 user 需求是升级补差价) + // 这里我们主要处理补差价升级逻辑。 + $price = $isUpgrade + ? $vipLevel->getUpgradePrice($currentVip) + : (float) $vipLevel->price; + + if ($price < 0.01) { + // 如果差价极小或为 0(例如同级或降级),抛出异常或根据业务逻辑处理 + if ($isUpgrade) { + throw new RuntimeException('当前等级差价不足 0.01 元,无法发起升级。'); + } + if ($user->vip_level_id === $vipLevel->id) { + // 续费逻辑保持原价 + $price = (float) $vipLevel->price; + } else { + throw new RuntimeException('不支持降级购买会员。'); + } } return VipPaymentOrder::create([ @@ -45,13 +63,15 @@ class VipPaymentService 'user_id' => $user->id, 'vip_level_id' => $vipLevel->id, 'status' => 'created', - 'amount' => $vipLevel->price, - 'subject' => '购买 VIP 会员 - '.$vipLevel->name, + 'amount' => $price, + 'subject' => ($isUpgrade ? '【升级】' : '购买') . ' VIP 会员 - ' . $vipLevel->name, 'provider' => 'alipay', 'vip_name' => $vipLevel->name, 'vip_duration_days' => (int) $vipLevel->duration_days, 'meta' => [ 'username' => $user->username, + 'is_upgrade' => $isUpgrade, + 'old_vip_level_id' => $currentVip?->id, ], ]); } @@ -188,7 +208,11 @@ class VipPaymentService if (! $lockedOrder->isVipOpened()) { // 只在首次成功支付时开通会员,防止重复回调导致会员时长重复叠加。 $user = User::query()->lockForUpdate()->findOrFail($lockedOrder->user_id); - $this->vipService->grantVip($user, $lockedOrder->vip_level_id, (int) $lockedOrder->vip_duration_days); + + // 从 meta 中提取是否是升级 + $isUpgrade = (bool) ($lockedOrder->meta['is_upgrade'] ?? false); + + $this->vipService->grantVip($user, $lockedOrder->vip_level_id, (int) $lockedOrder->vip_duration_days, $isUpgrade); $lockedOrder->update([ 'opened_vip_at' => now(), diff --git a/app/Services/VipService.php b/app/Services/VipService.php index 6d425a6..4ae70c1 100644 --- a/app/Services/VipService.php +++ b/app/Services/VipService.php @@ -46,17 +46,24 @@ class VipService * @param User $user 目标用户 * @param int $vipLevelId VIP 等级 ID * @param int $days 天数(0=永久) + * @param bool $isUpgrade 是否为补差价升级 */ - public function grantVip(User $user, int $vipLevelId, int $days = 30): void + public function grantVip(User $user, int $vipLevelId, int $days = 30, bool $isUpgrade = false): void { + $oldVipId = $user->vip_level_id; $user->vip_level_id = $vipLevelId; if ($days > 0) { - // 如果用户已有未过期的会员,在现有到期时间上延长 - $baseTime = ($user->hy_time && $user->hy_time->isFuture()) - ? $user->hy_time - : now(); - $user->hy_time = $baseTime->addDays($days); + if ($isUpgrade && $oldVipId && $user->hy_time && $user->hy_time->isFuture()) { + // 如果是升级,到期日期保持不变,除非新等级是永久(days=0) + // 此时只需更新等级 ID,无需修改 hy_time。 + } else { + // 如果是新购或续费,在现有到期时间上延长 + $baseTime = ($user->hy_time && $user->hy_time->isFuture()) + ? $user->hy_time + : now(); + $user->hy_time = $baseTime->addDays($days); + } } else { // 永久会员 $user->hy_time = null; diff --git a/resources/views/vip/center.blade.php b/resources/views/vip/center.blade.php index 23364d0..736bb10 100644 --- a/resources/views/vip/center.blade.php +++ b/resources/views/vip/center.blade.php @@ -87,53 +87,94 @@ @foreach ($vipLevels as $vip) @php $isCurrentVipLevel = $user->isVip() && (int) $user->vip_level_id === (int) $vip->id; + $currentVip = $user->isVip() ? $user->vipLevel : null; + + // 逻辑: + // 1. 如果用户不是 VIP,显示「购买」原价。 + // 2. 如果用户已经是该等级,显示「续费」原价。 + // 3. 如果用户是更高级别,显示「无法降级」。 + // 4. 如果用户是更低级别,显示「升级」差价。 + + $isHigher = $currentVip ? $vip->isHigherThan($currentVip) : false; + $isLower = $currentVip && (int) $user->vip_level_id !== (int) $vip->id && ! $isHigher; + $canUpgrade = $currentVip && $isHigher; + $upgradePrice = $canUpgrade ? $vip->getUpgradePrice($currentVip) : (float) $vip->price; + + $btnText = '立即购买'; + $btnColor = 'bg-gray-900 hover:bg-black text-white'; + $showUpgradeInfo = false; + $isDisabled = false; + + if ($isCurrentVipLevel) { + $btnText = '续费会员'; + $btnColor = 'bg-amber-500 hover:bg-amber-600 text-white'; + } elseif ($canUpgrade) { + $btnText = '补差价升级'; + $btnColor = 'bg-indigo-600 hover:bg-indigo-700 text-white'; + $showUpgradeInfo = true; + } elseif ($isLower) { + $btnText = '无法降级'; + $btnColor = 'bg-gray-200 text-gray-400 cursor-not-allowed'; + $isDisabled = true; + } @endphp
-
{{ $vip->icon }}
+
+ {{ $vip->icon }} +
-

{{ $vip->name }}

-

当前已有 {{ $vip->users_count }} 位用户使用

+

{{ $vip->name }}

+
+ + @if ($vip->duration_days > 0) + 有效期 {{ $vip->duration_days }} 天 + @else + 永久有效 + @endif + +
- @if ($isCurrentVipLevel) - 当前等级 - @endif -
- -
-
-
经验倍率
-
×{{ $vip->exp_multiplier }}
-
-
-
金币倍率
-
×{{ $vip->jjb_multiplier }}
+
+
+ {{ number_format($upgradePrice, 2) }} +
+ @if ($showUpgradeInfo) +
已省 ¥{{ number_format((float) ($vip->price - $upgradePrice), 2) }}
+ @elseif (! $isCurrentVipLevel && ! $isDisabled) +
原价 ¥{{ number_format((float) $vip->price, 2) }}
+ @endif
-
-
有效时长{{ $vip->duration_days > 0 ? $vip->duration_days . ' 天' : '永久' }}
-
支付金额{{ $vip->price > 0 ? '¥' . $vip->price : '联系管理员' }}
+
+
+ + 经验获取 {{ $vip->exp_multiplier }}x +
+
+ + 金币获取 {{ $vip->jjb_multiplier }}x +
+
+ + 专属入场特效 & 横幅 +
-
- @if ($vip->price > 0 && $vipPaymentEnabled) -
- @csrf - - -
- @elseif ($vip->price > 0) -
支付暂未开启
- @else -
请联系管理员开通
- @endif -
+ @if ($vipPaymentEnabled) +
+ @csrf + + +
+ @endif
@endforeach