diff --git a/app/Http/Controllers/Admin/BankAccountController.php b/app/Http/Controllers/Admin/BankAccountController.php index 26be81c..9d3da36 100644 --- a/app/Http/Controllers/Admin/BankAccountController.php +++ b/app/Http/Controllers/Admin/BankAccountController.php @@ -25,7 +25,9 @@ class BankAccountController extends SuperController $accoun->save(); if ($accoun->sts == 2) { $customer = Customer::find($accoun->customer_id); - $this->SendMessage($customer->mobile, 'کارت بانکی شما در ' . $this->Config('name_fa') . ' تایید شد'); + if (self::isSmsNotifEnabled('sms_notif_bank_card_customer')) { + $this->SendMessage($customer->mobile, 'کارت بانکی شما در ' . $this->Config('name_fa') . ' تایید شد'); + } } diff --git a/app/Http/Controllers/Admin/MassageController.php b/app/Http/Controllers/Admin/MassageController.php index 9557fd1..467a814 100644 --- a/app/Http/Controllers/Admin/MassageController.php +++ b/app/Http/Controllers/Admin/MassageController.php @@ -8,6 +8,8 @@ use App\Models\Message; use App\Models\Order; use Illuminate\Http\Request; use App\Http\Controllers\Controller; +use App\Services\ConfigService; +use App\Services\SmsProfanityFilter; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -22,6 +24,9 @@ class MassageController extends Controller // یافتن مشتریان با شماره‌های داده شده $customers = Customer::whereIn('mobile', $request->numbers)->get(); + $filter = app(SmsProfanityFilter::class); + $filter->assertClean((string) ($request->message ?? '')); + foreach ($customers as $customer) { // جایگزین کردن نام در پیام $message = str_replace('%name%', $customer->name, $request->message); @@ -32,6 +37,59 @@ class MassageController extends Controller return response()->json(true); } + + /** + * پیامک تکی به یک مشتری — فقط وقتی customer_direct_sms روشن باشد. + */ + public function send_customer_message(Request $request) + { + if (! app(ConfigService::class)->getBool('customer_direct_sms', false)) { + throw new UnprocessableEntityHttpException('ارسال پیامک تکی به مشتری غیرفعال است.'); + } + + $customerId = (int) ($request->input('customer_id') ?? 0); + $rawMessage = trim((string) ($request->input('message') ?? '')); + + if ($customerId <= 0) { + throw new UnprocessableEntityHttpException('مشتری مشخص نشده است.'); + } + if ($rawMessage === '') { + throw new UnprocessableEntityHttpException('متن پیامک خالی است.'); + } + if (mb_strlen($rawMessage) > 700) { + throw new UnprocessableEntityHttpException('متن پیامک بیش از حد طولانی است.'); + } + + app(SmsProfanityFilter::class)->assertClean($rawMessage); + + $customer = Customer::find($customerId); + if (! $customer || ! $customer->mobile) { + throw new UnprocessableEntityHttpException('مشتری یا شماره موبایل یافت نشد.'); + } + + $cfg = app(ConfigService::class); + $appName = (string) ($cfg->get('name_fa', '') ?: ''); + $appUrl = (string) ($cfg->get('app_url', '') ?: ''); + + $message = str_replace( + ['%name%', '%نام%', '%app_name%', '%نام_اپ%', '%app_url%', '%لینک_اپ%', '%لینک%'], + [ + (string) $customer->name, + (string) $customer->name, + $appName, + $appName, + $appUrl, + $appUrl, + $appUrl, + ], + $rawMessage + ); + + $this->send($customer->mobile, $message); + + return response()->json(['ok' => true]); + } + public function getFunctionDescriptions() { diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index 0c35c07..39e2f75 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -142,7 +142,7 @@ class WalletController extends Controller //send sms to users and customer $status = $data['value'] > 0 ?" شارژ شد ": " کسر شد "; - if($this->Config('charge_notif') == "true"){ + if (self::isSmsNotifEnabled('charge_notif')) { $message = 'کیف پول کاربر ' . $customer->name . ' به مقدار ' . self::formatSmsRialAmount($data['value']) . " ریال در تاریخ " . verta()->format('Y/n/j H:i:s') .$status . "بابت" . "\n" . $data['note'] . "\n" @@ -150,13 +150,13 @@ class WalletController extends Controller User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data) { $this->send($admin->mobile, $message); }); - } - $message = 'کیف پول شما به اندازه ' . self::formatSmsRialAmount($data['value']) . " ریال در تاریخ " . verta()->format('Y/n/j H:i:s') .$status . "بابت" . "\n" - . $data['note'] - . "\n" - . $this->Config('name_fa'); - $this->Send($customer->mobile, $message); + $message = 'کیف پول شما به اندازه ' . self::formatSmsRialAmount($data['value']) . " ریال در تاریخ " . verta()->format('Y/n/j H:i:s') .$status . "بابت" . "\n" + . $data['note'] + . "\n" + . $this->Config('name_fa'); + $this->Send($customer->mobile, $message); + } $this->broadcastCustomerWalletAssets($customer); return response()->json(true); @@ -215,7 +215,7 @@ class WalletController extends Controller //send sms to users and customer $status = $data['value'] > 0 ?" شارژ شد ": " کسر شد "; $weightUnit = self::tradeWeightUnit(); - if($this->Config('charge_notif') == "true"){ + if (self::isSmsNotifEnabled('charge_notif')) { $message = 'کیف پول کاربر ' . $customer->name . ' به مقدار ' . self::formatSmsGoldWeight($data['value']) . " {$weightUnit} در تاریخ " . verta()->format('Y/n/j H:i:s') .$status . "بابت" . "\n" . $data['note'] . "\n" @@ -223,12 +223,12 @@ class WalletController extends Controller User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data) { $this->send($admin->mobile, $message); }); - } - $message = 'کیف پول شما به اندازه ' . self::formatSmsGoldWeight($data['value']) . " {$weightUnit} در تاریخ " . verta()->format('Y/n/j H:i:s') . $status . " بابت" . "\n" - . $data['note'] - . $this->Config('name_fa'); - $this->Send($customer->mobile, $message); + $message = 'کیف پول شما به اندازه ' . self::formatSmsGoldWeight($data['value']) . " {$weightUnit} در تاریخ " . verta()->format('Y/n/j H:i:s') . $status . " بابت" . "\n" + . $data['note'] + . $this->Config('name_fa'); + $this->Send($customer->mobile, $message); + } $this->broadcastCustomerWalletAssets($customer); return response()->json(true); @@ -364,7 +364,7 @@ class WalletController extends Controller $status = $deposit ? ' شارژ شد ' : ' کسر شد '; $formatted = self::formatSmsGoldWeight($data['value']); - if ($this->Config('charge_notif') == 'true') { + if (self::isSmsNotifEnabled('charge_notif')) { $message = 'کیف پول کاربر ' . $customer->name . ' به مقدار ' . $formatted . ' ' . $unitLabel . ' در تاریخ ' . verta()->format('Y/n/j H:i:s') . $status . 'بابت' . "\n" . ($data['note'] ?? '') . "\n" @@ -372,13 +372,13 @@ class WalletController extends Controller User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { $this->send($admin->mobile, $message); }); - } - $message = 'کیف پول شما به اندازه ' . $formatted . ' ' . $unitLabel . ' در تاریخ ' . verta()->format('Y/n/j H:i:s') . $status . ' بابت' . "\n" - . ($data['note'] ?? '') - . "\n" - . $this->Config('name_fa'); - $this->Send($customer->mobile, $message); + $message = 'کیف پول شما به اندازه ' . $formatted . ' ' . $unitLabel . ' در تاریخ ' . verta()->format('Y/n/j H:i:s') . $status . ' بابت' . "\n" + . ($data['note'] ?? '') + . "\n" + . $this->Config('name_fa'); + $this->Send($customer->mobile, $message); + } $this->broadcastCustomerWalletAssets($customer); return response()->json(true); diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index dada1b8..a9bdfba 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -104,7 +104,9 @@ class AuthController extends Controller $user->token_sms = $rand; $user->save(); - $this->SendMessage($mobile, 'رمز عبور یکبار مصرف شما جهت ورود به اپلیکشن ' . $this->Config('name_fa') . ' ' . $rand . ' می باشد' . PHP_EOL . "@$subDomain" . " #" . $rand,true ,"sms"); + if (self::isSmsNotifEnabled('sms_notif_login_otp')) { + $this->SendMessage($mobile, 'رمز عبور یکبار مصرف شما جهت ورود به اپلیکشن ' . $this->Config('name_fa') . ' ' . $rand . ' می باشد' . PHP_EOL . "@$subDomain" . " #" . $rand,true ,"sms"); + } return $user->id; } @@ -151,10 +153,9 @@ class AuthController extends Controller $data['referral_to_id'] = $data['referral_id']; } $data['name'] = $data['fname'] . ' ' . $data['lname']; + // فقط ورود به اپ — نه اجازهٔ معامله؛ معامله با is_legal جداست $data['active'] = $cfg->getBool('auto_active_customer', false); - if (isset($data['type']) && $data['type'] == 2) { - $data['active'] = true; - } + $data['is_legal'] = (int) ($data['is_legal'] ?? 0); $cnt = Cnt::where('key', 'gram')->first(); if ($cnt !== null && trim((string) $cnt->value) !== '') { $data['gram'] = (int) $cnt->value; @@ -201,7 +202,7 @@ class AuthController extends Controller if ($cfg->getBool('register_referral_sms_to_referrer', false)) { $messageRef = "{$data['name']} با کد معرف شما ثبت نام کرد.\nبا اولین خرید او پاداش دریافت میکنید.\n" . $this->Config('name_fa'); $this->Send($referrer->mobile, $messageRef); - } else { + } elseif (self::isSmsNotifEnabled('sms_notif_register_admin')) { $adminMessage = "با معرفی {$referrer->name}، کاربر {$data['name']} در اپ {$this->Config('name_fa')} ثبت نام کرده و منتظر تایید شماست."; User::where('send_sms', 1)->get()->each(function ($admin) use ($adminMessage) { $this->send($admin->mobile, $adminMessage); @@ -213,9 +214,11 @@ class AuthController extends Controller $message = $customer->is_legal === 1 ? 'احرازهویت برای ' . $customer->name . ' با کد ' . $customer->id . ' انجام شد.' : 'احرازهویت برای ' . $customer->name . ' با کد ' . $customer->id . ' تأیید نشد لطفا از پنل ادمین اقدام فرمایید.'; - User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { - $this->SendMessage($admin->mobile, $message); - }); + if (self::isSmsNotifEnabled('sms_notif_jibit_admin')) { + User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { + $this->SendMessage($admin->mobile, $message); + }); + } } $message = "{$data['name']} عزیز، اکانت شما در پلتفرم {$this->Config('name_fa')} ایجاد شد."; @@ -227,7 +230,9 @@ class AuthController extends Controller } } $message .= "\nنام کاربری: {$data['mobile']}\nورود به نرم افزار: {$this->Config('app_url')}"; - $this->Send($data['mobile'], $message); + if (self::isSmsNotifEnabled('sms_notif_register_customer')) { + $this->Send($data['mobile'], $message); + } return response()->json($customer); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index b27312b..1a0dce8 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -103,6 +103,23 @@ class Controller extends BaseController // } } + /** آیا این نوع پیامک اعلان روشن است؟ پیش‌فرض true اگر کلید در settings نباشد. */ + public static function isSmsNotifEnabled(string $key, bool $default = true): bool + { + return app(ConfigService::class)->isSmsNotifEnabled($key, $default); + } + + /** + * خرید/فروش فقط بعد از احراز هویت (is_legal ≠ 0). + * جدا از active / auto_active_customer که فقط ورود به اپ را کنترل می‌کنند. + */ + public static function assertCustomerIdentityVerified(?Customer $customer): void + { + if (!$customer || (int) ($customer->is_legal ?? 0) === 0) { + throw new UnprocessableEntityHttpException('لطفا ابتدا فرآیند احراز هویت خود را تکمیل کنید'); + } + } + public static function SendMessage($to, $msg ,$is_send = false,$force_method ='null') { $credentials = [ @@ -111,7 +128,6 @@ class Controller extends BaseController ], "bale" => [ "token" => self::Config('bale_bot_token') -// "token" => Setting::where('key', 'bale_bot_token')->where('is_active', true)->first()->value ?? null ], "sms" => [ 'user_name' => 'ysms1362', @@ -120,8 +136,9 @@ class Controller extends BaseController ], ]; $platforms = []; - $send_sms = true; $user = User::where('mobile', $to)->first() ?: Customer::where('mobile', $to)->first(); + + // بله و پیامک مستقل‌اند — موفقیت یکی، دیگری را قطع نمی‌کند if ($user) { $baleToken = trim((string) ($credentials['bale']['token'] ?? '')); $baleChatId = trim((string) ($user->bale ?? '')); @@ -137,44 +154,43 @@ class Controller extends BaseController $balePayload = $baleResponse->json(); if ($baleResponse->successful() && ($balePayload['ok'] ?? false) === true) { $platforms[] = "bale:{$baleChatId}"; - $send_sms = false; + } else { + $platforms[] = "bale_failed:{$baleChatId}"; } } catch (\Throwable $e) { report($e); + $platforms[] = "bale_error:{$baleChatId}"; } } } - //SMS - if ($send_sms or $force_method=="sms") { - try { - $gateway = app(SmsGatewayService::class); + try { + $gateway = app(SmsGatewayService::class); - if ($force_method == "sms") { - $smsResult = $gateway->sendOrThrow($to, trim($msg), 'notification'); - } else { - $smsResult = $gateway->send($to, trim($msg), 'notification'); - } - - if ($smsResult->blocked) { - $platforms[] = "blocked:{$to}"; - } elseif ($smsResult->sent && $smsResult->provider) { - $platforms[] = "{$smsResult->provider}:{$to}"; - } else { - $platforms[] = "sms_failed:{$to}"; - } - } catch (SmsBlockedException $e) { - throw $e; - } catch (\Throwable $e) { - report($e); - $platforms[] = "sms_error:{$to}"; + if ($force_method == "sms") { + $smsResult = $gateway->sendOrThrow($to, trim($msg), 'notification'); + } else { + $smsResult = $gateway->send($to, trim($msg), 'notification'); } + + if ($smsResult->blocked) { + $platforms[] = "blocked:{$to}"; + } elseif ($smsResult->sent && $smsResult->provider) { + $platforms[] = "{$smsResult->provider}:{$to}"; + } else { + $platforms[] = "sms_failed:{$to}"; + } + } catch (SmsBlockedException $e) { + throw $e; + } catch (\Throwable $e) { + report($e); + $platforms[] = "sms_error:{$to}"; } Message::create([ 'sms' => $msg, 'receiver' => $to, - 'platforms' => implode(', ', $platforms), // ذخیره لیست سامانه‌ها + 'platforms' => implode(', ', $platforms), ]); } public static function SendTelegram($to , $msg) diff --git a/app/Http/Controllers/Customer/CartController.php b/app/Http/Controllers/Customer/CartController.php index 2b4e430..a697df4 100644 --- a/app/Http/Controllers/Customer/CartController.php +++ b/app/Http/Controllers/Customer/CartController.php @@ -136,8 +136,7 @@ class CartController extends Controller $data = $request->json()->all(); $user= auth()->user(); if ($user->is_legal == 0 || !$user) { - return response()->json(['massage' => 'شما باید اول خود را احراز هویت کنید'], 422); - + throw new UnprocessableEntityHttpException('لطفا ابتدا فرآیند احراز هویت خود را تکمیل کنید'); } // $data->validate([ diff --git a/app/Http/Controllers/Customer/OrderController.php b/app/Http/Controllers/Customer/OrderController.php index 5af99c3..a17fc00 100644 --- a/app/Http/Controllers/Customer/OrderController.php +++ b/app/Http/Controllers/Customer/OrderController.php @@ -18,9 +18,7 @@ class OrderController extends Controller public function store(Request $request) { $user = auth()->user(); - if ($user->is_legal == 0 || $user->id == 4) { - return response()->json(['massage' => 'لطفا ابتدا فرآیند احراز هویت خود را تکمیل کنید'], 422); - } + self::assertCustomerIdentityVerified($user instanceof Customer ? $user : null); if (!auth('customer')->user()->trade_active) { throw new UnprocessableEntityHttpException('امکان سفارش برای شما غیرفعال است.'); diff --git a/app/Http/Controllers/Customer/WalletController.php b/app/Http/Controllers/Customer/WalletController.php index 9acc255..61d2c02 100644 --- a/app/Http/Controllers/Customer/WalletController.php +++ b/app/Http/Controllers/Customer/WalletController.php @@ -419,7 +419,9 @@ class WalletController extends Controller } } User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { - $this->Send($admin->mobile, $message . ' ' . now()->format('H:i:s')); + if (self::isSmsNotifEnabled('sms_notif_withdraw_admin')) { + $this->Send($admin->mobile, $message . ' ' . now()->format('H:i:s')); + } }); return response()->json($Withdraw); @@ -583,10 +585,12 @@ class WalletController extends Controller $otp = rand(1000, 9999); $customer->token_sms = $otp; $customer->save(); - $this->Send( - $customer->mobile, - 'رمز عبور یکبار مصرف شما جهت برداشت پول از اپلیکیشن ' . $this->Config('name_fa') . ' می باشد: ' . $otp - ); + if (self::isSmsNotifEnabled('sms_notif_withdraw_otp')) { + $this->Send( + $customer->mobile, + 'رمز عبور یکبار مصرف شما جهت برداشت پول از اپلیکیشن ' . $this->Config('name_fa') . ' می باشد: ' . $otp + ); + } return response()->json(true); } diff --git a/app/Http/Controllers/Finance/FinanceController.php b/app/Http/Controllers/Finance/FinanceController.php index 31167e7..40add5d 100644 --- a/app/Http/Controllers/Finance/FinanceController.php +++ b/app/Http/Controllers/Finance/FinanceController.php @@ -208,7 +208,9 @@ class FinanceController extends Controller // Getting admin mobile numbers and sending the SMS User::where('active', 1)->get()->each(function($admin) use ($message) { - $this->Send($admin->mobile, $message); + if (self::isSmsNotifEnabled('sms_notif_gateway_charge_admin')) { + $this->Send($admin->mobile, $message); + } }); diff --git a/app/Http/Controllers/Single/BankAccountController.php b/app/Http/Controllers/Single/BankAccountController.php index bb3df83..96b54a7 100644 --- a/app/Http/Controllers/Single/BankAccountController.php +++ b/app/Http/Controllers/Single/BankAccountController.php @@ -97,9 +97,11 @@ class BankAccountController extends SuperController } else { $message = "احرازهویت برای شماره کارت کاربر " . $user->name . " توسط جیبیت انجام نشد. لطفا از پنل ادمین اقدام کنید. "; - User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { - $this->SendMessage($admin->mobile, $message); - }); + if (self::isSmsNotifEnabled('sms_notif_bank_card_admin')) { + User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { + $this->SendMessage($admin->mobile, $message); + }); + } $BA = BankAccount::create([ 'customer_id' => $user->id, 'cart_number' => $request->cart_number, @@ -115,9 +117,11 @@ class BankAccountController extends SuperController $message = "کارت جدیدی برای $customer->name ساخته شد منتظر تایید شما هستیم."; $message .= $this->Config('name_fa'); - User::where('active', 1) ->where('send_sms', 1)->get()->each(function($admin) use ($message) { - $this->Send($admin->mobile, $message); - }); + if (self::isSmsNotifEnabled('sms_notif_bank_card_admin')) { + User::where('active', 1)->where('send_sms', 1)->get()->each(function ($admin) use ($message) { + $this->Send($admin->mobile, $message); + }); + } $data = $request->only(['account_number', 'bank_name', 'cart_number', 'shaba_number', 'customer_id']); BankAccount::create($data); return response()->json($data); diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index e646ea6..ce426cb 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -58,6 +58,7 @@ class SingleOrderController extends Controller if (!$customer || !$customer->trade_active) { throw new UnprocessableEntityHttpException('امکان سفارش برای شما غیرفعال است.'); } + self::assertCustomerIdentityVerified($customer); $data['customer_id'] = auth('customer')->id(); @@ -778,9 +779,11 @@ class SingleOrderController extends Controller $message = "کاربر {$customer->name} با شماره موبایل {$customer->mobile} اقدام به $transactionType {$subjectTitle} به مقدار " . "{$amount} گرم به مبلغ " . number_format($totalPrice) . " ریال کرد. با تشکر، $companyName"; - User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) { - $this->send($admin->mobile, $message); - }); + if (self::isSmsNotifEnabled('sms_notif_order_admin')) { + User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { + $this->send($admin->mobile, $message); + }); + } $wallet = Wallet::where('owner', $data['customer_id']) ->whereNull('fund_id') ->whereNull('product_id') @@ -795,8 +798,9 @@ class SingleOrderController extends Controller $this->Config('app_url'); - // ارسال SMS به مشتری - $this->send($customer->mobile, $customerMessage); + if (self::isSmsNotifEnabled('sms_notif_order_customer')) { + $this->send($customer->mobile, $customerMessage); + } } private function assertProviderOrderAccepted(array &$data): void diff --git a/app/Http/Controllers/Site/ReportController.php b/app/Http/Controllers/Site/ReportController.php index ac21fe0..a8b5c1e 100644 --- a/app/Http/Controllers/Site/ReportController.php +++ b/app/Http/Controllers/Site/ReportController.php @@ -73,7 +73,9 @@ class ReportController extends Controller if ($PriceTrigger->action == 'email') { Mail::to("$customer->email")->send(new YourMailableClass($message, '', '', '', '')); } elseif ($PriceTrigger->action == 'sms') { - $this->send($customer->mobile, $message); + if (self::isSmsNotifEnabled('sms_notif_price_trigger')) { + $this->send($customer->mobile, $message); + } } $PriceTrigger->is_active = false; $PriceTrigger->triggered_at = Carbon::now(); diff --git a/app/Http/Controllers/Site/RequestsController.php b/app/Http/Controllers/Site/RequestsController.php index a86dade..71004ed 100644 --- a/app/Http/Controllers/Site/RequestsController.php +++ b/app/Http/Controllers/Site/RequestsController.php @@ -163,7 +163,9 @@ class RequestsController extends Controller " ریال کرد. با تشکر، $companyName"; } User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) { - $this->send($admin->mobile, $message); + if (self::isSmsNotifEnabled('sms_notif_fund_withdraw_admin')) { + $this->send($admin->mobile, $message); + } }); // User::whereNotNull('telegram')->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) { // $this->SendTelegram($admin->telegram, $message); @@ -214,8 +216,9 @@ class RequestsController extends Controller // "\nمبلغ کل: " . number_format($data['total_price']) . "\n\nموجودی طلایی: {$wallet->gold}\n" . // "مانده ریالی: " . number_format($wallet->cash) . "\n" . $this->Config('app_url'); - // ارسال SMS به مشتری - $this->send($customer->mobile, $customerMessage); + if (self::isSmsNotifEnabled('sms_notif_fund_withdraw_customer')) { + $this->send($customer->mobile, $customerMessage); + } } } diff --git a/app/Http/Controllers/pay/paypingController.php b/app/Http/Controllers/pay/paypingController.php index f4ce40c..6dd76b8 100644 --- a/app/Http/Controllers/pay/paypingController.php +++ b/app/Http/Controllers/pay/paypingController.php @@ -175,7 +175,9 @@ class paypingController extends Controller . ' حساب خود را به مقدار ' . number_format($Withdraw->cash) . ' ریال شارژ کرد. با تشکر، ' . $this->Config('name_fa'); User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { - $this->Send($admin->mobile, $message); + if (self::isSmsNotifEnabled('sms_notif_gateway_charge_admin')) { + $this->Send($admin->mobile, $message); + } }); return view('showTransactionReport', compact('Withdraw', 'app_url')); @@ -249,7 +251,9 @@ class paypingController extends Controller . ' حساب خود را به مقدار ' . number_format($Withdraw->cash) . ' ریال شارژ کرد. با تشکر، ' . $this->Config('name_fa'); User::where('send_sms', 1)->get()->each(function ($admin) use ($message) { - $this->Send($admin->mobile, $message); + if (self::isSmsNotifEnabled('sms_notif_gateway_charge_admin')) { + $this->Send($admin->mobile, $message); + } }); return view('showTransactionReport', compact('Withdraw', 'app_url')); diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index 6c58724..b9e9feb 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -57,6 +57,15 @@ class ConfigService return filter_var($value, FILTER_VALIDATE_BOOLEAN); } + /** + * سوییچ‌های پیامک اعلان (sms_notif_* / charge_notif). + * کلید نباشد → $default (معمولاً true تا رفتار قبلی حفظ شود). + */ + public function isSmsNotifEnabled(string $key, bool $default = true): bool + { + return $this->getBool($key, $default); + } + /** فروشگاهی (قطره / کشکول) — روت‌ها و رفتار store فقط وقتی true باشد فعال می‌شوند */ public function isStore(): bool { diff --git a/app/Services/SmsBilling/SmsAlertService.php b/app/Services/SmsBilling/SmsAlertService.php index 218e528..a5735ac 100644 --- a/app/Services/SmsBilling/SmsAlertService.php +++ b/app/Services/SmsBilling/SmsAlertService.php @@ -42,6 +42,10 @@ class SmsAlertService $message = "موجودی کیف پول پیامک {$appName} صفر یا منفی شده است. لطفاً کیف پول را شارژ کنید."; + if (! app(\App\Services\ConfigService::class)->isSmsNotifEnabled('sms_notif_wallet_alert')) { + return; + } + foreach ($this->alertRecipients() as $mobile) { app(SmsGatewayService::class)->send($mobile, $message, 'wallet_alert', isAlert: true); } diff --git a/app/Services/SmsProfanityFilter.php b/app/Services/SmsProfanityFilter.php new file mode 100644 index 0000000..d874e4f --- /dev/null +++ b/app/Services/SmsProfanityFilter.php @@ -0,0 +1,69 @@ + */ + private const BLOCKED = [ + // English + 'fuck', 'shit', 'bitch', 'asshole', 'bastard', 'dick', 'pussy', 'cunt', + 'motherfucker', 'nigger', 'faggot', 'slut', 'whore', + // Persian (transliteration + common) + 'کیر', 'کس', 'کص', 'جنده', 'جنده‌', 'گایید', 'گاییدن', 'گایدم', 'بیناموس', + 'بی‌ناموس', 'بی ناموس', 'حرومزاده', 'حرامزاده', 'مادرجنده', 'ننه جنده', + 'کونی', 'کون', 'لاشی', 'لاشه‌', 'عن', 'اشغال', 'دیوث', 'قرمساق', + 'fuckyou', 'fck', 'f*ck', 's*it', 'b!tch', + ]; + + /** + * @return array{ok: bool, matched: string|null} + */ + public function check(string $message): array + { + $normalized = $this->normalize($message); + if ($normalized === '') { + return ['ok' => true, 'matched' => null]; + } + + foreach (self::BLOCKED as $word) { + $needle = $this->normalize($word); + if ($needle === '') { + continue; + } + if (mb_strpos($normalized, $needle) !== false) { + return ['ok' => false, 'matched' => $word]; + } + } + + return ['ok' => true, 'matched' => null]; + } + + public function assertClean(string $message): void + { + $result = $this->check($message); + if (! $result['ok']) { + throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException( + 'متن پیام حاوی محتوای نامناسب است. لطفاً متن را اصلاح کنید.' + ); + } + } + + private function normalize(string $text): string + { + $text = mb_strtolower($text, 'UTF-8'); + $text = str_replace( + ['ي', 'ك', 'ة', 'ؤ', 'إ', 'أ', 'آ', '‌', "\u{200c}"], + ['ی', 'ک', 'ه', 'و', 'ا', 'ا', 'ا', '', ''], + $text + ); + // فاصله‌های اضافه و علائم تکرارشونده را ساده کن + $text = preg_replace('/[^\p{L}\p{N}\s]+/u', ' ', $text) ?? $text; + $text = preg_replace('/\s+/u', ' ', $text) ?? $text; + + return trim($text); + } +} diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index 5949624..acde156 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -13,11 +13,13 @@ -- -- اصول: -- • چند بار قابل اجرا (idempotent) --- • value / is_active ردیف‌های موجود دست نخورده --- • can_edit / title_fa / description هر deploy از این فایل override می‌شوند (استاندارد مشترک) --- • فقط INSERT برای کلیدهای جدید + به‌روز type +-- • value / is_active ردیف‌های موجود هرگز دست نخورده — فقط INSERT برای کلید جدید +-- • can_edit / title_fa / description / type هر deploy از این فایل به‌روز می‌شوند -- • schema (جدول/ستون) فقط با migrate — نه این فایل -- +-- ⚠️ هرگز UPDATE روی `value` برای ردیف موجود ننویس (مگر شرط خالی بودن صریح و مستند). +-- ادمین/کارفرما مقدار را در پنل تنظیم می‌کند؛ این فایل فقط کلید جدید با default می‌سازد. +-- -- قبل از اجرا: USE `نام_دیتابیس`; + بکاپ -- ============================================================================= @@ -342,9 +344,75 @@ SELECT 'fee_free', '0', 'تعداد سفارش اول بدون کارمزد', 1, WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_free' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) -SELECT 'charge_notif', 'false', 'پیامک شارژ ادمین', 1, 1, NULL +SELECT 'charge_notif', 'true', 'پیامک شارژ ادمین', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'charge_notif' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'customer_direct_sms', 'false', 'پیامک تکی به مشتری از لیست', 0, 1, + 'روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.', + 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'customer_direct_sms' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_order_admin', 'true', 'پیامک خرید/فروش به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_order_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_order_customer', 'true', 'پیامک خرید/فروش به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_order_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_withdraw_admin', 'true', 'پیامک درخواست برداشت به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_withdraw_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_withdraw_otp', 'true', 'پیامک رمز برداشت', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_withdraw_otp' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_login_otp', 'true', 'پیامک رمز ورود', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_login_otp' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_gateway_charge_admin', 'true', 'پیامک شارژ درگاه به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_gateway_charge_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_register_customer', 'true', 'پیامک خوش‌آمد ثبت‌نام', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_register_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_register_admin', 'true', 'پیامک ثبت‌نام به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_register_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_jibit_admin', 'true', 'پیامک احراز هویت به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_jibit_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_bank_card_admin', 'true', 'پیامک کارت بانکی به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_bank_card_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_bank_card_customer', 'true', 'پیامک تایید کارت به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_bank_card_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_fund_withdraw_admin', 'true', 'پیامک برداشت قلک به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_fund_withdraw_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_fund_withdraw_customer', 'true', 'پیامک برداشت قلک به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_fund_withdraw_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_price_trigger', 'true', 'پیامک هشدار قیمت', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_price_trigger' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +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 'gift_for_referrer', '0.005', 'پاداش معرف', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'gift_for_referrer' LIMIT 1); @@ -812,7 +880,13 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'close_price', 'show_last_price', 'show_gold_ounce_price', 'fee_deduct_from_gold', 'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw', - 'has_page_earn_money', 'charge_notif', 'group_sms', 'sms_panel', + 'has_page_earn_money', 'charge_notif', 'group_sms', 'sms_panel', 'customer_direct_sms', + 'sms_notif_order_admin', 'sms_notif_order_customer', + 'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp', + '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', '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', @@ -1123,7 +1197,8 @@ UPDATE `settings` SET `description` = 'عنوان فلز اصلی در رابط UPDATE `settings` SET `title_fa` = 'نام فلز اصلی (ادمین)', `description` = 'نام کوتاه فلز برای برچسب‌های پنل ادمین (مثلاً «مس» یا «طلا»). «تراز طلایی» می‌شود «تراز مس» — نه «تراز مسی». فقط از دیپلوی/SQL تغییر می‌کند.' WHERE `key` = 'main_metal'; UPDATE `settings` SET `description` = 'واحد نمایش وزن در دارایی‌ها، کیف پول و جزئیات معاملات|گرم,کیلوگرم' WHERE `key` = 'unit'; UPDATE `settings` SET `description` = 'روشن: کاربران جدید می‌توانند ثبت‌نام کنند. خاموش: فقط ورود کاربران موجود مجاز است.' WHERE `key` = 'open_register'; -UPDATE `settings` SET `description` = 'روشن: حساب کاربر بلافاصله پس از ثبت‌نام فعال می‌شود. خاموش: نیاز به تأیید دستی ادمین دارد.' WHERE `key` = 'auto_active_customer'; +UPDATE `settings` SET `description` = 'روشن: بعد از ثبت‌نام کاربر می‌تواند وارد اپ شود (بدون تأیید دستی ادمین). خاموش: ورود فقط بعد از فعال‌سازی دستی در پنل. این سوییچ اجازهٔ خرید/فروش نمی‌دهد — معامله فقط بعد از احراز هویت ممکن است.' WHERE `key` = 'auto_active_customer'; +UPDATE `settings` SET `title_fa` = 'فعال‌سازی خودکار پس از ثبت‌نام' WHERE `key` = 'auto_active_customer'; UPDATE `settings` SET `description` = 'نوع چیدمان اپ: روشن = حالت فروشگاهی (سبد، دسته‌بندی، حواله و …). خاموش = حالت معامله آبشده/محصولات کلاسیک. فقط از دیپلوی تغییر می‌کند.' WHERE `key` = 'is_store'; UPDATE `settings` SET `description` = 'روشن: نمودار قیمت از تاریخچه ذخیره‌شده در دیتابیس خودتان. خاموش: از سرویس تابلو آنلاین (با شناسه chart_id) استفاده می‌شود.' WHERE `key` = 'chart_use_local_data'; @@ -1237,7 +1312,24 @@ UPDATE `settings` SET `description` = 'شماره تماس پشتیبانی. ا UPDATE `settings` SET `description` = 'توکن ربات تلگرام برای ارسال اعلان‌ها. از BotFather دریافت می‌شود. محرمانه.' WHERE `key` = 'telegram_bot_token'; UPDATE `settings` SET `description` = 'توکن ربات بله برای ارسال اعلان‌ها. محرمانه.' WHERE `key` = 'bale_bot_token'; -UPDATE `settings` SET `description` = 'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال می‌شود.' WHERE `key` = 'charge_notif'; +UPDATE `settings` SET `description` = 'روشن: بعد از شارژ یا کسر کیف توسط ادمین، پیامک به مشتری و ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: هیچ‌کدام ارسال نمی‌شود.' WHERE `key` = 'charge_notif'; +UPDATE `settings` SET `description` = 'روشن: بعد از خرید یا فروش موفق، پیامک خلاصه معامله به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: ادمین پیامک معامله نمی‌گیرد.' WHERE `key` = 'sms_notif_order_admin'; +UPDATE `settings` SET `description` = 'روشن: بعد از خرید یا فروش موفق، پیامک رسید (مقدار، مبلغ، موجودی) به مشتری می‌رود. خاموش: مشتری پیامک معامله نمی‌گیرد.' WHERE `key` = 'sms_notif_order_customer'; +UPDATE `settings` SET `description` = 'روشن: وقتی مشتری درخواست برداشت ریالی می‌دهد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان برداشت به ادمین نمی‌رود.' WHERE `key` = 'sms_notif_withdraw_admin'; +UPDATE `settings` SET `description` = 'روشن: رمز یکبارمصرف برداشت برای مشتری پیامک می‌شود. خاموش: رمز پیامک نمی‌شود (برداشت با OTP عملاً کار نمی‌کند). خاموش کردن توصیه نمی‌شود.' WHERE `key` = 'sms_notif_withdraw_otp'; +UPDATE `settings` SET `description` = 'روشن: رمز یکبارمصرف ورود برای کاربر/مشتری پیامک می‌شود. خاموش: ورود با OTP ممکن نیست. خاموش کردن توصیه نمی‌شود.' WHERE `key` = 'sms_notif_login_otp'; +UPDATE `settings` SET `description` = 'روشن: بعد از شارژ موفق کیف از درگاه پرداخت، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان شارژ درگاه نمی‌رود.' WHERE `key` = 'sms_notif_gateway_charge_admin'; +UPDATE `settings` SET `description` = 'روشن: بعد از ثبت‌نام، پیامک خوش‌آمد (نام کاربری و لینک ورود) به مشتری می‌رود. خاموش: پیامک خوش‌آمد ارسال نمی‌شود.' WHERE `key` = 'sms_notif_register_customer'; +UPDATE `settings` SET `description` = 'روشن: وقتی کسی با کد معرف ثبت‌نام می‌کند و پیامک به معرف خاموش است، اعلان ثبت‌نام به ادمین می‌رود. خاموش: این اعلان ادمین نمی‌رود.' WHERE `key` = 'sms_notif_register_admin'; +UPDATE `settings` SET `description` = 'روشن: نتیجه احراز هویت جیبیت در ثبت‌نام برای ادمین‌های دارای «ارسال پیامک» پیامک می‌شود. خاموش: اعلان جیبیت نمی‌رود.' WHERE `key` = 'sms_notif_jibit_admin'; +UPDATE `settings` SET `description` = 'روشن: وقتی مشتری کارت بانکی جدید ثبت می‌کند یا احراز کارت ناموفق است، پیامک به ادمین می‌رود. خاموش: اعلان کارت به ادمین نمی‌رود.' WHERE `key` = 'sms_notif_bank_card_admin'; +UPDATE `settings` SET `description` = 'روشن: بعد از تایید کارت بانکی توسط ادمین، پیامک تایید به مشتری می‌رود. خاموش: مشتری از تایید کارت پیامک نمی‌گیرد.' WHERE `key` = 'sms_notif_bank_card_customer'; +UPDATE `settings` SET `description` = 'روشن: برداشت از قلک (ریالی یا طلایی) برای ادمین‌های دارای «ارسال پیامک» پیامک می‌شود. خاموش: اعلان برداشت قلک به ادمین نمی‌رود.' WHERE `key` = 'sms_notif_fund_withdraw_admin'; +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` = 'روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود و ادمین می‌تواند برای هر مشتری متن دلخواه بفرستد. خاموش: دکمه مخفی است. فقط از صفحه دیپلوی تغییر می‌کند.' WHERE `key` = 'customer_direct_sms'; +UPDATE `settings` SET `title_fa` = 'پیامک تکی به مشتری از لیست' WHERE `key` = 'customer_direct_sms'; UPDATE `settings` SET `description` = 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.' WHERE `key` = 'group_sms'; UPDATE `settings` SET `description` = 'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.' WHERE `key` = 'sms_panel'; @@ -1295,13 +1387,28 @@ WHERE `key` IN ( UPDATE `settings` SET `title_fa` = 'آدرس پنل ادمین' WHERE `key` = 'admin_url'; UPDATE `settings` SET `title_fa` = 'لینک دانلود اپ اندروید' WHERE `key` = 'apk_download_link'; UPDATE `settings` SET `title_fa` = 'آدرس اپ (کاربر)' WHERE `key` = 'app_url'; -UPDATE `settings` SET `title_fa` = 'فعال‌سازی خودکار کاربر' WHERE `key` = 'auto_active_customer'; +UPDATE `settings` SET `title_fa` = 'فعال‌سازی خودکار پس از ثبت‌نام' WHERE `key` = 'auto_active_customer'; UPDATE `settings` SET `title_fa` = 'آدرس پایه' WHERE `key` = 'bace_url'; UPDATE `settings` SET `title_fa` = 'حذف مشتری از تراز' WHERE `key` = 'balance_report_exclude_customer_id'; UPDATE `settings` SET `title_fa` = 'توکن بات بله' WHERE `key` = 'bale_bot_token'; UPDATE `settings` SET `title_fa` = 'نمایش بنرها' WHERE `key` = 'banner_enabled'; UPDATE `settings` SET `title_fa` = 'سود خرید نمایش' WHERE `key` = 'buy_fee'; UPDATE `settings` SET `title_fa` = 'پیامک شارژ ادمین' WHERE `key` = 'charge_notif'; +UPDATE `settings` SET `title_fa` = 'پیامک خرید/فروش به ادمین' WHERE `key` = 'sms_notif_order_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک خرید/فروش به مشتری' WHERE `key` = 'sms_notif_order_customer'; +UPDATE `settings` SET `title_fa` = 'پیامک درخواست برداشت به ادمین' WHERE `key` = 'sms_notif_withdraw_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک رمز برداشت' WHERE `key` = 'sms_notif_withdraw_otp'; +UPDATE `settings` SET `title_fa` = 'پیامک رمز ورود' WHERE `key` = 'sms_notif_login_otp'; +UPDATE `settings` SET `title_fa` = 'پیامک شارژ درگاه به ادمین' WHERE `key` = 'sms_notif_gateway_charge_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک خوش‌آمد ثبت‌نام' WHERE `key` = 'sms_notif_register_customer'; +UPDATE `settings` SET `title_fa` = 'پیامک ثبت‌نام به ادمین' WHERE `key` = 'sms_notif_register_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک احراز هویت به ادمین' WHERE `key` = 'sms_notif_jibit_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک کارت بانکی به ادمین' WHERE `key` = 'sms_notif_bank_card_admin'; +UPDATE `settings` SET `title_fa` = 'پیامک تایید کارت به مشتری' WHERE `key` = 'sms_notif_bank_card_customer'; +UPDATE `settings` SET `title_fa` = 'پیامک برداشت قلک به ادمین' WHERE `key` = 'sms_notif_fund_withdraw_admin'; +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` = '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'; @@ -1430,6 +1537,7 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN ( 'external_order_api_enabled', 'fee_deduct_from_gold', 'group_sms', + 'customer_direct_sms', 'has_page_earn_money', 'is_store', 'sms_panel', @@ -1463,6 +1571,12 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'name_fa', 'auto_active_customer', 'balance_report_exclude_customer_id', 'buy_fee', 'charge_notif', 'close_price', + 'sms_notif_order_admin', 'sms_notif_order_customer', + 'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp', + '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', '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 2a1b59a..2dd4d51 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 @@ -299,9 +299,75 @@ SELECT 'fee_free', '0', 'تعداد سفارش اول بدون کارمزد', 1, WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_free' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) -SELECT 'charge_notif', 'false', 'پیامک شارژ ادمین', 1, 1, NULL +SELECT 'charge_notif', 'true', 'پیامک شارژ ادمین', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'charge_notif' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'customer_direct_sms', 'false', 'پیامک تکی به مشتری از لیست', 0, 1, + 'روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.', + 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'customer_direct_sms' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_order_admin', 'true', 'پیامک خرید/فروش به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_order_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_order_customer', 'true', 'پیامک خرید/فروش به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_order_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_withdraw_admin', 'true', 'پیامک درخواست برداشت به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_withdraw_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_withdraw_otp', 'true', 'پیامک رمز برداشت', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_withdraw_otp' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_login_otp', 'true', 'پیامک رمز ورود', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_login_otp' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_gateway_charge_admin', 'true', 'پیامک شارژ درگاه به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_gateway_charge_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_register_customer', 'true', 'پیامک خوش‌آمد ثبت‌نام', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_register_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_register_admin', 'true', 'پیامک ثبت‌نام به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_register_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_jibit_admin', 'true', 'پیامک احراز هویت به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_jibit_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_bank_card_admin', 'true', 'پیامک کارت بانکی به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_bank_card_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_bank_card_customer', 'true', 'پیامک تایید کارت به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_bank_card_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_fund_withdraw_admin', 'true', 'پیامک برداشت قلک به ادمین', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_fund_withdraw_admin' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_fund_withdraw_customer', 'true', 'پیامک برداشت قلک به مشتری', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_fund_withdraw_customer' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'sms_notif_price_trigger', 'true', 'پیامک هشدار قیمت', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'sms_notif_price_trigger' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +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 'gift_for_referrer', '0.005', 'پاداش معرف', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'gift_for_referrer' LIMIT 1); @@ -696,6 +762,12 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'fee_deduct_from_gold', 'send_to_tahesab', 'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw', 'has_page_earn_money', 'charge_notif', 'group_sms', 'sms_panel', + 'sms_notif_order_admin', 'sms_notif_order_customer', + 'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp', + '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', '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', @@ -1032,7 +1104,7 @@ UPDATE `settings` SET `description` = 'عنوان فلز اصلی در رابط UPDATE `settings` SET `title_fa` = 'نام فلز اصلی (ادمین)', `description` = 'نام کوتاه فلز برای برچسب‌های پنل ادمین (مثلاً «مس» یا «طلا»). «تراز طلایی» می‌شود «تراز مس» — نه «تراز مسی». فقط از دیپلوی/SQL تغییر می‌کند.' WHERE `key` = 'main_metal'; UPDATE `settings` SET `description` = 'واحد نمایش وزن در دارایی‌ها، کیف پول و جزئیات معاملات|گرم,کیلوگرم' WHERE `key` = 'unit'; UPDATE `settings` SET `description` = 'روشن: کاربران جدید می‌توانند ثبت‌نام کنند. خاموش: فقط ورود کاربران موجود مجاز است.' WHERE `key` = 'open_register'; -UPDATE `settings` SET `description` = 'روشن: حساب کاربر بلافاصله پس از ثبت‌نام فعال می‌شود. خاموش: نیاز به تأیید دستی ادمین دارد.' WHERE `key` = 'auto_active_customer'; +UPDATE `settings` SET `description` = 'روشن: بعد از ثبت‌نام کاربر می‌تواند وارد اپ شود (بدون تأیید دستی ادمین). خاموش: ورود فقط بعد از فعال‌سازی دستی در پنل. این سوییچ اجازهٔ خرید/فروش نمی‌دهد — معامله فقط بعد از احراز هویت ممکن است.' WHERE `key` = 'auto_active_customer'; UPDATE `settings` SET `description` = 'نوع چیدمان اپ: روشن = حالت فروشگاهی (سبد، دسته‌بندی، حواله و …). خاموش = حالت معامله آبشده/محصولات کلاسیک. فقط از دیپلوی تغییر می‌کند.' WHERE `key` = 'is_store'; UPDATE `settings` SET `description` = 'روشن: نمودار قیمت از تاریخچه ذخیره‌شده در دیتابیس خودتان. خاموش: از سرویس تابلو آنلاین (با شناسه chart_id) استفاده می‌شود.' WHERE `key` = 'chart_use_local_data'; @@ -1134,7 +1206,24 @@ UPDATE `settings` SET `description` = 'شماره تماس پشتیبانی؛ د UPDATE `settings` SET `description` = 'توکن ربات تلگرام برای ارسال اعلان‌ها. از BotFather دریافت می‌شود. محرمانه.' WHERE `key` = 'telegram_bot_token'; UPDATE `settings` SET `description` = 'توکن ربات بله برای ارسال اعلان‌ها. محرمانه.' WHERE `key` = 'bale_bot_token'; -UPDATE `settings` SET `description` = 'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال می‌شود.' WHERE `key` = 'charge_notif'; +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` = 'charge_notif'; +UPDATE `settings` SET `description` = 'روشن: بعد از خرید یا فروش موفق، پیامک خلاصه معامله به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: ادمین پیامک معامله نمی‌گیرد.' WHERE `key` = 'sms_notif_order_admin'; +UPDATE `settings` SET `description` = 'روشن: بعد از خرید یا فروش موفق، پیامک رسید به مشتری می‌رود. خاموش: مشتری پیامک معامله نمی‌گیرد.' WHERE `key` = 'sms_notif_order_customer'; +UPDATE `settings` SET `description` = 'روشن: درخواست برداشت ریالی مشتری برای ادمین پیامک می‌شود. خاموش: اعلان برداشت به ادمین نمی‌رود.' WHERE `key` = 'sms_notif_withdraw_admin'; +UPDATE `settings` SET `description` = 'روشن: رمز یکبارمصرف برداشت پیامک می‌شود. خاموش کردن توصیه نمی‌شود.' WHERE `key` = 'sms_notif_withdraw_otp'; +UPDATE `settings` SET `description` = 'روشن: رمز یکبارمصرف ورود پیامک می‌شود. خاموش کردن توصیه نمی‌شود.' WHERE `key` = 'sms_notif_login_otp'; +UPDATE `settings` SET `description` = 'روشن: شارژ موفق درگاه برای ادمین پیامک می‌شود. خاموش: اعلان نمی‌رود.' WHERE `key` = 'sms_notif_gateway_charge_admin'; +UPDATE `settings` SET `description` = 'روشن: پیامک خوش‌آمد ثبت‌نام به مشتری می‌رود. خاموش: ارسال نمی‌شود.' WHERE `key` = 'sms_notif_register_customer'; +UPDATE `settings` SET `description` = 'روشن: اعلان ثبت‌نام با معرف به ادمین می‌رود (اگر پیامک معرف خاموش باشد).' WHERE `key` = 'sms_notif_register_admin'; +UPDATE `settings` SET `description` = 'روشن: نتیجه احراز جیبیت برای ادمین پیامک می‌شود.' WHERE `key` = 'sms_notif_jibit_admin'; +UPDATE `settings` SET `description` = 'روشن: ثبت/شکست احراز کارت بانکی برای ادمین پیامک می‌شود.' WHERE `key` = 'sms_notif_bank_card_admin'; +UPDATE `settings` SET `description` = 'روشن: تایید کارت بانکی به مشتری پیامک می‌شود.' WHERE `key` = 'sms_notif_bank_card_customer'; +UPDATE `settings` SET `description` = 'روشن: برداشت قلک برای ادمین پیامک می‌شود.' WHERE `key` = 'sms_notif_fund_withdraw_admin'; +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` = 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.' WHERE `key` = 'group_sms'; UPDATE `settings` SET `description` = 'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.' WHERE `key` = 'sms_panel'; @@ -1184,6 +1273,7 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN ( 'chart_id', 'chart_use_local_data', 'external_order_api_enabled', 'group_sms', + 'customer_direct_sms', 'has_page_earn_money', 'is_store', 'sms_panel', @@ -1215,6 +1305,12 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'name_fa', 'auto_active_customer', 'balance_report_exclude_customer_id', 'buy_fee', 'charge_notif', 'close_price', + 'sms_notif_order_admin', 'sms_notif_order_customer', + 'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp', + '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', '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 e2cf582..e83452b 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -103,6 +103,7 @@ Route::middleware('auth:user')->group(function (){ Route::post('messages/get_customer_list', [MassageController::class, 'getCustomerList']); Route::post('messages/get_customer_list/table', [MassageController::class, 'getCustomerListTable']); Route::post('messages/send_message', [MassageController::class, 'send_message']); + Route::post('messages/send_customer', [MassageController::class, 'send_customer_message']); Route::resource('messages', MassageController::class); //------------single customer