优化vip
This commit is contained in:
@@ -13,6 +13,10 @@ namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* 聊天室入场、离场与会员喜报播报服务
|
||||
* 负责统一生成进退场文本,以及会员主题横幅和购买成功喜报的前端载荷。
|
||||
*/
|
||||
class RoomBroadcastService
|
||||
{
|
||||
/**
|
||||
@@ -183,4 +187,33 @@ class RoomBroadcastService
|
||||
'presence_icon' => (string) ($theme['icon'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建会员购买成功喜报的前端载荷。
|
||||
*
|
||||
* @return array<string, string|null>
|
||||
*/
|
||||
public function buildVipPurchasePayload(User $user): array
|
||||
{
|
||||
$theme = $this->vipPresenceService->buildPurchaseTheme($user);
|
||||
|
||||
if (empty($theme['enabled'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$text = trim((string) ($theme['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'presence_type' => 'purchase',
|
||||
'presence_text' => $text,
|
||||
'presence_color' => (string) ($theme['color'] ?? ''),
|
||||
'presence_effect' => $theme['effect'] ? (string) $theme['effect'] : null,
|
||||
'presence_banner_style' => (string) ($theme['banner_style'] ?? ''),
|
||||
'presence_level_name' => (string) ($theme['level_name'] ?? ''),
|
||||
'presence_icon' => (string) ($theme['icon'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Events\EffectBroadcast;
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\User;
|
||||
use App\Models\VipLevel;
|
||||
use App\Models\VipPaymentOrder;
|
||||
@@ -14,6 +16,10 @@ use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* VIP 支付业务服务
|
||||
* 负责支付订单创建、支付回调幂等处理,以及会员开通成功后的聊天室喜报广播。
|
||||
*/
|
||||
class VipPaymentService
|
||||
{
|
||||
/**
|
||||
@@ -25,6 +31,8 @@ class VipPaymentService
|
||||
public function __construct(
|
||||
private readonly VipPaymentCenterClient $paymentCenterClient,
|
||||
private readonly VipService $vipService,
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly RoomBroadcastService $roomBroadcastService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -64,7 +72,7 @@ class VipPaymentService
|
||||
'vip_level_id' => $vipLevel->id,
|
||||
'status' => 'created',
|
||||
'amount' => $price,
|
||||
'subject' => ($isUpgrade ? '【升级】' : '购买') . ' VIP 会员 - ' . $vipLevel->name,
|
||||
'subject' => ($isUpgrade ? '【升级】' : '购买').' VIP 会员 - '.$vipLevel->name,
|
||||
'provider' => 'alipay',
|
||||
'vip_name' => $vipLevel->name,
|
||||
'vip_duration_days' => (int) $vipLevel->duration_days,
|
||||
@@ -173,7 +181,9 @@ class VipPaymentService
|
||||
*/
|
||||
public function markOrderAsPaid(VipPaymentOrder $vipPaymentOrder, array $payload, string $source = 'async'): VipPaymentOrder
|
||||
{
|
||||
return DB::transaction(function () use ($vipPaymentOrder, $payload, $source) {
|
||||
$shouldBroadcastVipCelebration = false;
|
||||
|
||||
$paidOrder = DB::transaction(function () use ($vipPaymentOrder, $payload, $source, &$shouldBroadcastVipCelebration) {
|
||||
$lockedOrder = VipPaymentOrder::query()
|
||||
->with(['user', 'vipLevel'])
|
||||
->lockForUpdate()
|
||||
@@ -208,19 +218,28 @@ class VipPaymentService
|
||||
if (! $lockedOrder->isVipOpened()) {
|
||||
// 只在首次成功支付时开通会员,防止重复回调导致会员时长重复叠加。
|
||||
$user = User::query()->lockForUpdate()->findOrFail($lockedOrder->user_id);
|
||||
|
||||
// 从 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(),
|
||||
]);
|
||||
|
||||
$shouldBroadcastVipCelebration = true;
|
||||
}
|
||||
|
||||
return $lockedOrder->fresh(['user', 'vipLevel']);
|
||||
});
|
||||
|
||||
if ($shouldBroadcastVipCelebration) {
|
||||
$this->broadcastVipPurchaseCelebration($paidOrder);
|
||||
}
|
||||
|
||||
return $paidOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,4 +249,51 @@ class VipPaymentService
|
||||
{
|
||||
return 'VPO'.date('YmdHis').random_int(1000, 9999);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向用户当前在线的聊天室广播 VIP 购买成功喜报与烟花特效。
|
||||
*
|
||||
* @param VipPaymentOrder $vipPaymentOrder 已完成开通的支付订单
|
||||
*/
|
||||
private function broadcastVipPurchaseCelebration(VipPaymentOrder $vipPaymentOrder): void
|
||||
{
|
||||
$user = User::query()->with('vipLevel')->find($vipPaymentOrder->user_id);
|
||||
|
||||
if (! $user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$purchasePayload = $this->roomBroadcastService->buildVipPurchasePayload($user);
|
||||
if (empty($purchasePayload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$roomIds = $this->chatState->getUserRooms($user->username);
|
||||
foreach ($roomIds as $roomId) {
|
||||
// 先把喜报写入房间历史,确保当前在线用户和后续短时间内进房的人都能看到。
|
||||
$celebrationMessage = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '会员播报',
|
||||
'to_user' => '大家',
|
||||
'content' => sprintf(
|
||||
'<span style="color: %s; font-weight: bold;">%s</span>',
|
||||
$purchasePayload['presence_color'] ?: '#f59e0b',
|
||||
$purchasePayload['presence_text']
|
||||
),
|
||||
'is_secret' => false,
|
||||
'font_color' => $purchasePayload['presence_color'] ?: '#f59e0b',
|
||||
'action' => 'vip_presence',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$celebrationMessage = array_merge($celebrationMessage, $purchasePayload);
|
||||
|
||||
$this->chatState->pushMessage($roomId, $celebrationMessage);
|
||||
broadcast(new MessageSent($roomId, $celebrationMessage));
|
||||
|
||||
// 购买成功固定播放烟花,和会员登录时的豪华表现保持一致。
|
||||
broadcast(new EffectBroadcast($roomId, 'fireworks', $user->username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* 会员进退场与购买成功主题服务
|
||||
* 统一生成会员欢迎横幅、离场横幅与购买成功喜报所需的文本、颜色、特效和样式数据。
|
||||
*/
|
||||
class VipPresenceService
|
||||
{
|
||||
/**
|
||||
@@ -39,6 +43,45 @@ class VipPresenceService
|
||||
return $this->buildTheme($user, 'leave');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建会员购买成功主题数据。
|
||||
*
|
||||
* @return array<string, string|null|bool>
|
||||
*/
|
||||
public function buildPurchaseTheme(User $user): array
|
||||
{
|
||||
$vipLevel = $user->vipLevel;
|
||||
|
||||
if (! $user->isVip() || ! $vipLevel) {
|
||||
return [
|
||||
'enabled' => false,
|
||||
'type' => 'purchase',
|
||||
'text' => null,
|
||||
'color' => null,
|
||||
'effect' => null,
|
||||
'banner_style' => null,
|
||||
'level_name' => null,
|
||||
'icon' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'type' => 'purchase',
|
||||
'text' => sprintf(
|
||||
'恭喜 %s 成功开通【%s %s】,尊享 VIP 特权,大家掌声欢迎!',
|
||||
$user->username,
|
||||
$vipLevel->icon ?: '👑',
|
||||
$vipLevel->name
|
||||
),
|
||||
'color' => $vipLevel->color ?: '#f59e0b',
|
||||
'effect' => 'fireworks',
|
||||
'banner_style' => $vipLevel->joinBannerStyleKey(),
|
||||
'level_name' => $vipLevel->name,
|
||||
'icon' => $vipLevel->icon,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一构建会员进场或离场的主题数据。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user