From bfb8b91133bb11c26851386dd7eae54d3d018234 Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 4 Aug 2026 18:59:38 +0330 Subject: [PATCH] added trigger for chat sms --- .../Chat/SupportChatNotifyController.php | 119 ++++++++++++++++++ .../Controllers/Customer/ChatController.php | 3 + app/Models/Setting.php | 1 + app/Services/ChatWebhookService.php | 79 ++++++++++++ database/sql/deploy_settings_const.sql | 20 ++- ...ngs_const_preserve_value_sync_can_edit.sql | 20 ++- routes/admin.php | 4 + routes/api.php | 4 + 8 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/Chat/SupportChatNotifyController.php create mode 100644 app/Services/ChatWebhookService.php diff --git a/app/Http/Controllers/Chat/SupportChatNotifyController.php b/app/Http/Controllers/Chat/SupportChatNotifyController.php new file mode 100644 index 0000000..e253512 --- /dev/null +++ b/app/Http/Controllers/Chat/SupportChatNotifyController.php @@ -0,0 +1,119 @@ +header('X-Chat-Webhook-Secret', ''); + + if ($secret === '' || ! hash_equals($secret, $incoming)) { + return response()->json(['message' => 'unauthorized'], 401); + } + + if (! self::isSmsNotifEnabled('sms_notif_support_admin')) { + 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)) { + 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 = 'مشتری'; + } + + $message = 'شما از '.$name.' پیام دارید'; + + try { + User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { + if (! empty($admin->mobile)) { + $this->send($admin->mobile, $message); + } + }); + } 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 (از پنل ادمین). + */ + public function ensureWebhook() + { + $ok = app(ChatWebhookService::class)->ensureRegistered(); + + return response()->json([ + 'ok' => $ok, + 'message' => $ok ? 'webhook registered' : 'webhook register skipped or failed', + ], $ok ? 200 : 422); + } +} diff --git a/app/Http/Controllers/Customer/ChatController.php b/app/Http/Controllers/Customer/ChatController.php index 239707d..e2e2fdc 100644 --- a/app/Http/Controllers/Customer/ChatController.php +++ b/app/Http/Controllers/Customer/ChatController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Customer; use App\Http\Controllers\Controller; use App\Models\Customer; +use App\Services\ChatWebhookService; use Illuminate\Support\Facades\Http; class ChatController extends Controller @@ -26,6 +27,8 @@ class ChatController extends Controller ? (string) $user->chat_user_id : 'user_'.$user->id; + app(ChatWebhookService::class)->ensureRegistered(); + if (!empty($user->chat_room_id)) { if ($this->chatRoomIsAccessible($host, (string) $chatApiKey, (string) $user->chat_room_id, $userChatId)) { if (empty($user->chat_user_id) || $user->chat_user_id !== $userChatId) { diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 497fc5c..cf15038 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -109,6 +109,7 @@ class Setting extends Model 'value', 'value_json', 'type', + 'can_edit', ]; protected $casts = [ 'value_json'=>'array', diff --git a/app/Services/ChatWebhookService.php b/app/Services/ChatWebhookService.php new file mode 100644 index 0000000..6e6799d --- /dev/null +++ b/app/Services/ChatWebhookService.php @@ -0,0 +1,79 @@ +ensureSecret(); + if ($host === '' || $secret === '') { + return false; + } + + $webhookUrl = rtrim((string) config('app.url'), '/').'/api/chat/support-message-notify'; + + try { + $response = Http::timeout(8)->post('https://'.$host.'/api/projects/set-webhook', [ + 'api_key' => $chatApiKey, + 'webhook_url' => $webhookUrl, + 'webhook_secret' => $secret, + ]); + + return $response->successful(); + } catch (\Throwable $e) { + Log::warning('chat webhook register failed', [ + 'host' => $host, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + public function ensureSecret(): string + { + $existing = trim((string) Controller::Config('chat_webhook_secret')); + if ($existing !== '') { + return $existing; + } + + $secret = Str::random(48); + + $row = Setting::where('key', 'chat_webhook_secret')->first(); + if ($row) { + if (trim((string) $row->value) === '') { + $row->value = $secret; + $row->save(); + } else { + return (string) $row->value; + } + } else { + Setting::create([ + 'key' => 'chat_webhook_secret', + 'value' => $secret, + 'title_fa' => 'رمز وب‌هوک چت', + 'is_active' => true, + 'can_edit' => 0, + 'description' => 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.', + 'type' => 'text', + ]); + } + + return $secret; + } +} diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index e2b28ec..6b08693 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -489,6 +489,16 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de SELECT 'sms_notif_wallet_alert', 'true', 'پیامک هشدار اعتبار پنل پیامک', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_wallet_alert' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_support_admin', 'true', 'پیامک پیام پشتیبانی به ادمین', 1, 1, + 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان اتاق را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_support_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'chat_webhook_secret', '', 'رمز وب‌هوک چت', 0, 1, + 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.', 'text' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'chat_webhook_secret' LIMIT 1); + INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) SELECT 'gift_for_referrer', '0.005', 'پاداش معرف', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'gift_for_referrer' LIMIT 1); @@ -1102,7 +1112,7 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin', 'sms_notif_jibit_admin', 'sms_notif_bank_card_admin', 'sms_notif_bank_card_customer', 'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer', - 'sms_notif_price_trigger', 'sms_notif_wallet_alert', + 'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin', 'montzar_closed', 'montzar_open_buy', 'montzar_open_sell', 'payping_fin_sync_enabled', 'external_order_api_enabled', 'is_store', 'wss_enabled', 'tracking_systems_enabled', 'register_referral_sms_to_referrer', 'register_referral_bonus_message', @@ -1577,6 +1587,8 @@ UPDATE `settings` SET `description` = 'روشن: برداشت از قلک (ری UPDATE `settings` SET `description` = 'روشن: بعد از برداشت از قلک، پیامک رسید به مشتری می‌رود. خاموش: مشتری پیامک برداشت قلک نمی‌گیرد.' WHERE `key` = 'sms_notif_fund_withdraw_customer'; UPDATE `settings` SET `description` = 'روشن: وقتی هشدار قیمت مشتری (اقدام=پیامک) فعال می‌شود، پیامک به مشتری می‌رود. خاموش: هشدار قیمت پیامک نمی‌فرستد.' WHERE `key` = 'sms_notif_price_trigger'; UPDATE `settings` SET `description` = 'روشن: وقتی اعتبار کیف پول پنل پیامک صفر یا منفی شود، هشدار به سوپرادمین و ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: این هشدار نمی‌رود.' WHERE `key` = 'sms_notif_wallet_alert'; +UPDATE `settings` SET `description` = 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان اتاق را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.' WHERE `key` = 'sms_notif_support_admin'; +UPDATE `settings` SET `description` = 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.' WHERE `key` = 'chat_webhook_secret'; UPDATE `settings` SET `description` = 'روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود و ادمین می‌تواند برای هر مشتری متن دلخواه بفرستد. خاموش: دکمه مخفی است. فقط از صفحه دیپلوی تغییر می‌کند.' WHERE `key` = 'customer_direct_sms'; UPDATE `settings` SET `title_fa` = 'پیامک تکی به مشتری از لیست' WHERE `key` = 'customer_direct_sms'; UPDATE `settings` SET `description` = 'روشن: سرویس احراز هویت جیبیت (تطبیق کد ملی/موبایل و کارت بانکی) فعال است. خاموش: هیچ فراخوانی به جیبیت انجام نمی‌شود و احراز هویت خودکار کار نمی‌کند. فقط از صفحه دیپلوی قابل تغییر است.' WHERE `key` = 'jibit_enabled'; @@ -1670,6 +1682,8 @@ UPDATE `settings` SET `title_fa` = 'پیامک برداشت قلک به ادمی UPDATE `settings` SET `title_fa` = 'پیامک برداشت قلک به مشتری' WHERE `key` = 'sms_notif_fund_withdraw_customer'; UPDATE `settings` SET `title_fa` = 'پیامک هشدار قیمت' WHERE `key` = 'sms_notif_price_trigger'; UPDATE `settings` SET `title_fa` = 'پیامک هشدار اعتبار پنل پیامک' WHERE `key` = 'sms_notif_wallet_alert'; +UPDATE `settings` SET `title_fa` = 'پیامک پیام پشتیبانی به ادمین' WHERE `key` = 'sms_notif_support_admin'; +UPDATE `settings` SET `title_fa` = 'رمز وب‌هوک چت' WHERE `key` = 'chat_webhook_secret'; UPDATE `settings` SET `title_fa` = 'UUID نمودار Zaarbox' WHERE `key` = 'chart_id'; UPDATE `settings` SET `title_fa` = 'نمودار از داده خود سایت' WHERE `key` = 'chart_use_local_data'; UPDATE `settings` SET `title_fa` = 'بستن خودکار خارج از ساعت کاری' WHERE `key` = 'close_price'; @@ -1869,7 +1883,7 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN ( 'up_price', 'down_price', 'wss_enabled', 'zaarbox_api_url', - 'show_to_users_chat', 'chat_api', 'chat_api_key', 'chat_upload_url', 'chat_download_url' + 'show_to_users_chat', 'chat_api', 'chat_api_key', 'chat_upload_url', 'chat_download_url', 'chat_webhook_secret' ); -- قابل ویرایش در پنل (can_edit = 1) @@ -1883,7 +1897,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin', 'sms_notif_jibit_admin', 'sms_notif_bank_card_admin', 'sms_notif_bank_card_customer', 'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer', - 'sms_notif_price_trigger', 'sms_notif_wallet_alert', + 'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin', 'deposit_sms_support_number', 'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba', 'endDay', diff --git a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql index e8817de..56d43d5 100644 --- a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql +++ b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql @@ -444,6 +444,16 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de SELECT 'sms_notif_wallet_alert', 'true', 'پیامک هشدار اعتبار پنل پیامک', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_wallet_alert' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_support_admin', 'true', 'پیامک پیام پشتیبانی به ادمین', 1, 1, + 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان اتاق را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_support_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'chat_webhook_secret', '', 'رمز وب‌هوک چت', 0, 1, + 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.', 'text' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'chat_webhook_secret' LIMIT 1); + INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) SELECT 'gift_for_referrer', '0.005', 'پاداش معرف', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'gift_for_referrer' LIMIT 1); @@ -880,7 +890,7 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin', 'sms_notif_jibit_admin', 'sms_notif_bank_card_admin', 'sms_notif_bank_card_customer', 'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer', - 'sms_notif_price_trigger', 'sms_notif_wallet_alert', + 'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin', 'montzar_closed', 'montzar_open_buy', 'montzar_open_sell', 'payping_fin_sync_enabled', 'external_order_api_enabled', 'is_store', 'wss_enabled', 'tracking_systems_enabled', 'register_referral_sms_to_referrer', 'register_referral_bonus_message', @@ -1371,6 +1381,10 @@ UPDATE `settings` SET `description` = 'روشن: برداشت قلک برای ا UPDATE `settings` SET `description` = 'روشن: رسید برداشت قلک به مشتری پیامک می‌شود.' WHERE `key` = 'sms_notif_fund_withdraw_customer'; UPDATE `settings` SET `description` = 'روشن: هشدار قیمت مشتری (اقدام=پیامک) پیامک می‌شود.' WHERE `key` = 'sms_notif_price_trigger'; UPDATE `settings` SET `description` = 'روشن: هشدار اتمام اعتبار پنل پیامک ارسال می‌شود.' WHERE `key` = 'sms_notif_wallet_alert'; +UPDATE `settings` SET `description` = 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان اتاق را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.' WHERE `key` = 'sms_notif_support_admin'; +UPDATE `settings` SET `description` = 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.' WHERE `key` = 'chat_webhook_secret'; +UPDATE `settings` SET `title_fa` = 'پیامک پیام پشتیبانی به ادمین' WHERE `key` = 'sms_notif_support_admin'; +UPDATE `settings` SET `title_fa` = 'رمز وب‌هوک چت' WHERE `key` = 'chat_webhook_secret'; UPDATE `settings` SET `description` = 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.' WHERE `key` = 'group_sms'; UPDATE `settings` SET `description` = 'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.' WHERE `key` = 'sms_panel'; @@ -1453,6 +1467,8 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN ( 'zaarbox_api_url' ); +UPDATE `settings` SET `can_edit` = 0 WHERE `key` = 'chat_webhook_secret'; + UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'name_fa', 'auto_active_customer', 'balance_report_exclude_customer_id', @@ -1462,7 +1478,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin', 'sms_notif_jibit_admin', 'sms_notif_bank_card_admin', 'sms_notif_bank_card_customer', 'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer', - 'sms_notif_price_trigger', 'sms_notif_wallet_alert', + 'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin', 'deposit_sms_support_number', 'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba', 'endDay', diff --git a/routes/admin.php b/routes/admin.php index 2675987..9539b7a 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -23,6 +23,7 @@ use App\Http\Controllers\Site\OfferCodesController; use App\Http\Controllers\Admin\SettingsController; use \App\Http\Controllers\Admin\GiftCardController; use App\Http\Controllers\Admin\CategoryController; +use App\Http\Controllers\Chat\SupportChatNotifyController; use App\Http\Controllers\Admin\PriceSyncLogController; use App\Http\Controllers\Admin\SmsPanelController; use App\Services\ConfigService; @@ -107,6 +108,9 @@ Route::middleware('auth:user')->group(function (){ Route::post('/customer/table',[CustomerController::class,'tableWallets']); Route::get('/customer/online',[CustomerController::class,'onlineList']); Route::get('/customer/chat-presence',[CustomerController::class,'chatPresence']); + Route::post('/chat/focus', [SupportChatNotifyController::class, 'focus']); + Route::post('/chat/unfocus', [SupportChatNotifyController::class, 'unfocus']); + Route::post('/chat/ensure-webhook', [SupportChatNotifyController::class, 'ensureWebhook']); Route::get('/customer/{id}/assets',[CustomerController::class,'assetsSummary']); Route::get('/customer/active/{id}',[CustomerController::class,'active']); Route::get('/customer/archive/{id}',[CustomerController::class,'archive']); diff --git a/routes/api.php b/routes/api.php index 3691118..3c9890e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,6 +14,7 @@ use App\Http\Controllers\Site\RequestsController; use App\Services\ConfigService; use App\Http\Controllers\Customer\CartController; use App\Http\Controllers\Admin\CategoryController; +use App\Http\Controllers\Chat\SupportChatNotifyController; /* |-------------------------------------------------------------------------- @@ -49,6 +50,9 @@ Route::get('/city/all',[CityController::class, 'cityListAll']); Route::get('/city/{state_id}',[CityController::class, 'cityList']); Route::get('get_settings',[SettingsController::class, 'index']); +// وب‌هوک سرور چت → اعلان SMS اولین پیام خوانده‌نشده پشتیبانی +Route::post('/chat/support-message-notify', [SupportChatNotifyController::class, 'notify']); + if (app(ConfigService::class)->getBool('external_order_api_enabled', false)) { Route::middleware('request.validation')->group(function () { Route::post('get_wallet', [RequestsController::class, 'getWallet']);