diff --git a/app/Http/Controllers/MarriageController.php b/app/Http/Controllers/MarriageController.php index 9a736dd..8f66102 100644 --- a/app/Http/Controllers/MarriageController.php +++ b/app/Http/Controllers/MarriageController.php @@ -111,6 +111,7 @@ class MarriageController extends Controller $data = $request->validate([ 'target_username' => 'required|string', 'ring_purchase_id' => 'required|integer', + 'wedding_tier_id' => 'nullable|integer', ]); $proposer = $request->user(); @@ -120,7 +121,7 @@ class MarriageController extends Controller return response()->json(['ok' => false, 'message' => '用户不存在。'], 404); } - $result = $this->marriage->propose($proposer, $target, $data['ring_purchase_id']); + $result = $this->marriage->propose($proposer, $target, $data['ring_purchase_id'], $data['wedding_tier_id'] ?? null); if ($result['ok']) { $marriage = Marriage::find($result['marriage_id']); diff --git a/app/Services/MarriageService.php b/app/Services/MarriageService.php index 7c7b984..a518296 100644 --- a/app/Services/MarriageService.php +++ b/app/Services/MarriageService.php @@ -37,10 +37,10 @@ class MarriageService * * @param User $proposer 求婚方 * @param User $target 被求婚方 - * @param int $ringPurchaseId 使用的戒指购买记录 ID + * @param int $weddingTierId 可选的婚礼档位 ID * @return array{ok: bool, message: string, marriage_id: int|null} */ - public function propose(User $proposer, User $target, int $ringPurchaseId): array + public function propose(User $proposer, User $target, int $ringPurchaseId, ?int $weddingTierId = null): array { // 不能向自己求婚 if ($proposer->id === $target->id) { @@ -85,7 +85,17 @@ class MarriageService $expireHours = $this->config->get('proposal_expire_hours', 48); - return DB::transaction(function () use ($proposer, $target, $ring, $expireHours) { + return DB::transaction(function () use ($proposer, $target, $ring, $expireHours, $weddingTierId) { + // 在求婚阶段同时进行婚礼设置(由求婚方一人出全资预扣,属于 "男方付 / scheduled冻结" 模式) + if ($weddingTierId) { + $tier = \App\Models\WeddingTier::find($weddingTierId); + if ($tier && $tier->is_active) { + if (($proposer->jjb ?? 0) < $tier->amount) { + return ['ok' => false, 'message' => "金币不足,该婚礼档位需要 {$tier->amount} 金币。", 'marriage_id' => null]; + } + } + } + // 戒指状态改为占用中 $ring->update(['status' => 'used_pending']); @@ -103,6 +113,15 @@ class MarriageService 'hyname1' => $target->username, ]); + if ($weddingTierId) { + $weddingService = app(WeddingService::class); + // 预扣冻结:payerType=groom, ceremonyType=scheduled + $setupRes = $weddingService->setup($marriage, $weddingTierId, 'groom', 'scheduled'); + if (!$setupRes['ok']) { + throw new \Exception($setupRes['message']); + } + } + return ['ok' => true, 'message' => '求婚成功,等待对方回应。', 'marriage_id' => $marriage->id]; }); } @@ -161,6 +180,15 @@ class MarriageService // 初始亲密度 $this->intimacy->add($marriage, $initIntimacy, IntimacySource::WEDDING_BONUS, "结婚戒指初始亲密度({$ringName})", true); } + + // 如果有预先设置的婚礼(随求婚一起设定的),则将冻结金币转为正式扣除,并触发红包 + $ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first(); + if ($ceremony) { + $weddingService = app(WeddingService::class); + $weddingService->confirmCeremony($ceremony); // 解冻扣除,转为 immediate + $weddingService->trigger($ceremony); + broadcast(new \App\Events\WeddingCelebration($ceremony, $marriage)); + } }); return ['ok' => true, 'message' => '恭喜!你们已正式结婚!']; @@ -184,9 +212,16 @@ class MarriageService return ['ok' => false, 'message' => '无权操作此求婚。']; } - DB::transaction(function () use ($marriage) { + return DB::transaction(function () use ($marriage) { $marriage->update(['status' => 'rejected']); - // 戒指消失(lost = 不退还) + + // 检查是否有由于求婚冻结的婚礼金币,若有则退还 + $ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first(); + if ($ceremony) { + app(WeddingService::class)->cancelAndRefund($ceremony); + } + + // 戒指拒绝后遗失 UserPurchase::where('id', $marriage->ring_purchase_id)->update(['status' => 'lost']); // 记录戒指消失流水(amount=0,仅存档) if ($proposer = $marriage->user) { @@ -343,6 +378,12 @@ class MarriageService if ($proposer = $marriage->user) { $this->currency->change($proposer, 'gold', 0, CurrencySource::RING_LOST, "求婚超时,戒指消失({$marriage->ringItem?->name})"); } + + // 退还当时冻结的婚礼金币 + $ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first(); + if ($ceremony) { + app(WeddingService::class)->cancelAndRefund($ceremony); + } }); } diff --git a/app/Services/WeddingService.php b/app/Services/WeddingService.php index 54db91b..a584a4c 100644 --- a/app/Services/WeddingService.php +++ b/app/Services/WeddingService.php @@ -144,6 +144,66 @@ class WeddingService }); } + // ──────────────────────────── 婚礼生命周期钩子 ─────────────────── + + /** + * 将预先设置好的定时婚礼(因求婚冻结)转为即刻开始(解冻并记录消费)。 + * + * @param WeddingCeremony $ceremony + */ + public function confirmCeremony(WeddingCeremony $ceremony): void + { + DB::transaction(function () use ($ceremony) { + $marriage = $ceremony->marriage; + $tierName = $ceremony->tier?->name ?? '婚礼'; + + if ($ceremony->ceremony_type === 'scheduled') { + // 解除冻结,正式扣款记账 + if ($ceremony->groom_amount > 0) { + $groom = clone $marriage->user; + $marriage->user->decrement('frozen_jjb', $ceremony->groom_amount); + $this->currency->change($groom, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功,正式发红包({$tierName})"); + } + if ($ceremony->partner_amount > 0) { + $partner = clone $marriage->partner; + $marriage->partner->decrement('frozen_jjb', $ceremony->partner_amount); + $this->currency->change($partner, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功,正式发红包({$tierName})"); + } + + // 将类型转为即时开始 + $ceremony->update([ + 'ceremony_type' => 'immediate', + 'ceremony_at' => now(), + 'expires_at' => now()->addHours($this->config->get('envelope_expire_hours', 24)) + ]); + } + }); + } + + /** + * 撤销由于求婚设置的婚礼,并且解冻/退还因为该婚礼冻结的金币。 + * + * @param WeddingCeremony $ceremony + */ + public function cancelAndRefund(WeddingCeremony $ceremony): void + { + DB::transaction(function () use ($ceremony) { + $ceremony->update(['status' => 'cancelled']); + + if ($ceremony->ceremony_type === 'scheduled') { + $marriage = $ceremony->marriage; + if ($ceremony->groom_amount > 0) { + $marriage->user?->decrement('frozen_jjb', $ceremony->groom_amount); + $marriage->user?->increment('jjb', $ceremony->groom_amount); + } + if ($ceremony->partner_amount > 0) { + $marriage->partner?->decrement('frozen_jjb', $ceremony->partner_amount); + $marriage->partner?->increment('jjb', $ceremony->partner_amount); + } + } + }); + } + // ──────────────────────────── 触发婚礼 ──────────────────────────── /** diff --git a/resources/js/chat.js b/resources/js/chat.js index 9531cbb..e0c3e1f 100644 --- a/resources/js/chat.js +++ b/resources/js/chat.js @@ -77,19 +77,19 @@ export function initChat(roomId) { ); }) // ─── 婚姻系统:全局事件(广播给整个房间) ──────────────── - .listen("MarriageAccepted", (e) => { + .listen(".marriage.accepted", (e) => { console.log("结婚公告:", e); window.dispatchEvent( new CustomEvent("chat:marriage-accepted", { detail: e }), ); }) - .listen("MarriageDivorced", (e) => { + .listen(".marriage.divorced", (e) => { console.log("离婚公告:", e); window.dispatchEvent( new CustomEvent("chat:marriage-divorced", { detail: e }), ); }) - .listen("WeddingCelebration", (e) => { + .listen(".wedding.celebration", (e) => { console.log("婚礼庆典:", e); window.dispatchEvent( new CustomEvent("chat:wedding-celebration", { detail: e }), diff --git a/resources/views/chat/partials/marriage-modals.blade.php b/resources/views/chat/partials/marriage-modals.blade.php index 974aa6a..cca649b 100644 --- a/resources/views/chat/partials/marriage-modals.blade.php +++ b/resources/views/chat/partials/marriage-modals.blade.php @@ -114,36 +114,46 @@ - {{-- ── 婚礼费用提示面板 ── --}} + {{-- ── 婚礼档位选择与费用提示面板 ── --}} @php - $minWedding = (int) \App\Models\WeddingTier::where('is_active', true) - ->orderBy('amount') - ->value('amount'); + $activeTiers = \App\Models\WeddingTier::where('is_active', true)->orderBy('amount')->get(); @endphp - @if ($minWedding > 0) -