sanitizeSecret($secret); $incoming = app(ChatWebhookService::class)->sanitizeSecret( (string) $request->header('X-Chat-Webhook-Secret', '') ); if ($secret === '' || ! hash_equals($secret, $incoming)) { Log::warning('support chat notify unauthorized', [ 'has_secret' => $secret !== '', 'has_header' => $incoming !== '', ]); return response()->json(['message' => 'unauthorized'], 401); } if (! self::isSmsNotifEnabled('sms_notif_support_admin')) { Log::info('support chat notify skipped: sms_notif_support_admin disabled'); return response()->json(['ok' => true, 'skipped' => 'disabled']); } $roomId = trim((string) $request->input('room_id', '')); if ($roomId === '') { return response()->json(['message' => 'room_id required'], 422); } if (Cache::has(self::FOCUS_CACHE_PREFIX.$roomId)) { Log::info('support chat notify skipped: admin focused', ['room_id' => $roomId]); return response()->json(['ok' => true, 'skipped' => 'admin_focused']); } $customer = Customer::query() ->where('chat_room_id', $roomId) ->first(); $name = $customer ? trim((string) ($customer->name ?: $customer->username ?: 'مشتری')) : 'مشتری'; if ($name === '') { $name = 'مشتری'; } $companyName = trim((string) self::Config('name_fa')); if ($companyName === '') { $companyName = trim((string) self::Config('name')); } if ($companyName === '') { $companyName = 'پشتیبانی'; } $message = 'شما از '.$name." پیام دارید\n".$companyName; $admins = User::where('send_sms', 1)->get(); if ($admins->isEmpty()) { Log::warning('support chat notify: no admins with send_sms=1', ['room_id' => $roomId]); } try { $admins->each(function ($admin) use ($message) { if (! empty($admin->mobile)) { $this->send($admin->mobile, $message); } }); Log::info('support chat notify SMS sent', [ 'room_id' => $roomId, 'name' => $name, 'company' => $companyName, 'admin_count' => $admins->count(), ]); } catch (\Throwable $e) { Log::warning('support chat SMS notify failed', [ 'room_id' => $roomId, 'error' => $e->getMessage(), ]); } return response()->json(['ok' => true]); } /** * Heartbeat تمرکز ادمین روی یک اتاق پشتیبانی (یا ترک تمرکز). */ public function focus(Request $request) { $roomId = trim((string) $request->input('room_id', '')); $previousRoomId = trim((string) $request->input('previous_room_id', '')); if ($previousRoomId !== '' && $previousRoomId !== $roomId) { Cache::forget(self::FOCUS_CACHE_PREFIX.$previousRoomId); } if ($roomId === '') { return response()->json(['ok' => true]); } Cache::put(self::FOCUS_CACHE_PREFIX.$roomId, true, self::FOCUS_TTL_SECONDS); return response()->json(['ok' => true, 'room_id' => $roomId]); } /** * ترک تمرکز روی یک اتاق مشخص. */ public function unfocus(Request $request) { $roomId = trim((string) $request->input('room_id', '')); if ($roomId !== '') { Cache::forget(self::FOCUS_CACHE_PREFIX.$roomId); } return response()->json(['ok' => true]); } /** * ثبت/تازه‌سازی وب‌هوک روی ah_chat (از پنل ادمین). * همیشه HTTP 200 — شکست ثبت best-effort است و نباید toast خطای 422 بسازد. */ public function ensureWebhook() { $result = app(ChatWebhookService::class)->ensureRegisteredDetailed(); return response()->json([ 'ok' => (bool) ($result['ok'] ?? false), 'webhook_url' => (string) ($result['webhook_url'] ?? ''), 'reason' => (string) ($result['reason'] ?? ''), 'message' => (string) ($result['message'] ?? ''), 'attempts' => (int) ($result['attempts'] ?? 0), ]); } }