From 49e80a1b9705eb549ad3d94f42296ca486ea7bb2 Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 21 Jul 2026 13:25:18 +0330 Subject: [PATCH] fix pargar bugs 2 --- .../Controllers/Admin/CustomerController.php | 6 +- .../Controllers/Admin/MassageController.php | 2 +- .../Controllers/Admin/PargarController.php | 18 +++- app/Http/Controllers/Auth/AuthController.php | 15 ++- app/Http/Controllers/Controller.php | 12 +++ .../Single/SingleOrderController.php | 4 +- .../Controllers/Site/RequestsController.php | 4 +- app/Http/Controllers/SuperController.php | 2 +- .../Controllers/pay/paypingController.php | 14 +-- app/Models/File.php | 17 +--- app/Services/ConfigService.php | 98 +++++++++++++++++++ app/Services/PargarInboundWalletService.php | 16 +++ app/Services/PriceStreamService.php | 38 +------ app/Services/ReferralGiftService.php | 2 +- app/Services/WebsocketBroadcastService.php | 29 +----- .../views/showTransactionReport.blade.php | 2 +- tests/Unit/ConfigServiceOtpHostTest.php | 55 +++++++++++ 17 files changed, 228 insertions(+), 106 deletions(-) create mode 100644 tests/Unit/ConfigServiceOtpHostTest.php diff --git a/app/Http/Controllers/Admin/CustomerController.php b/app/Http/Controllers/Admin/CustomerController.php index 362ba12..0b2ee52 100644 --- a/app/Http/Controllers/Admin/CustomerController.php +++ b/app/Http/Controllers/Admin/CustomerController.php @@ -247,7 +247,7 @@ class CustomerController extends SuperController if ($active){ $message ='اکانت شما توسط کارشناس سیستم تایید شد '."\n". 'با رفتن به لینک زیر وارد حساب کاربری خود شوید'."\n". - $this->Config('app_url')."\n". + self::publicSiteUrl(request())."\n". 'با تشکر'.$this->Config('name_fa'); } else{ @@ -315,10 +315,10 @@ class CustomerController extends SuperController $companyName = $this->Config('name_fa'); - $companyurl = $this->Config('app_url'); + $companyurl = self::publicSiteUrl(request()); $message ='حساب کاربری شما توسط کارشناس سیستم تایید شد '."\n". 'با رفتن به لینک زیر وارد حساب کاربری خود شوید'."\n". - $this->Config('app_url')."\n". + $companyurl."\n". 'با تشکر'.$this->Config('name_fa'); $this->Send($customer->mobile, $message); diff --git a/app/Http/Controllers/Admin/MassageController.php b/app/Http/Controllers/Admin/MassageController.php index 467a814..63f1620 100644 --- a/app/Http/Controllers/Admin/MassageController.php +++ b/app/Http/Controllers/Admin/MassageController.php @@ -69,7 +69,7 @@ class MassageController extends Controller $cfg = app(ConfigService::class); $appName = (string) ($cfg->get('name_fa', '') ?: ''); - $appUrl = (string) ($cfg->get('app_url', '') ?: ''); + $appUrl = $cfg->resolvePublicSiteUrl($request); $message = str_replace( ['%name%', '%نام%', '%app_name%', '%نام_اپ%', '%app_url%', '%لینک_اپ%', '%لینک%'], diff --git a/app/Http/Controllers/Admin/PargarController.php b/app/Http/Controllers/Admin/PargarController.php index b8f016e..4822f54 100644 --- a/app/Http/Controllers/Admin/PargarController.php +++ b/app/Http/Controllers/Admin/PargarController.php @@ -275,12 +275,24 @@ class PargarController extends Controller $result = app(PargarInboundWalletService::class)->syncWalletFromPargar($customer); $appliedDocs = (int) ($result['documents']['applied'] ?? 0); $appliedMande = (bool) ($result['mande']['applied'] ?? false); + $mandeMessage = (string) ($result['mande']['message'] ?? ''); + + $messages = []; + if ($appliedDocs > 0) { + $messages[] = "{$appliedDocs} سند از پرگار اعمال شد"; + } + if ($appliedMande) { + $messages[] = 'ماندهٔ پرگار روی کیف اعمال شد'; + } + if ($mandeMessage !== '' && ! $appliedMande) { + $messages[] = $mandeMessage; + } return response()->json([ 'ok' => $appliedDocs > 0 || $appliedMande, - 'message' => $appliedDocs > 0 || $appliedMande - ? 'مانده از پرگار روی کیف اعمال شد' - : ($result['mande']['message'] ?? 'سند قابل‌اعمالی یافت نشد'), + 'message' => $messages !== [] + ? implode(' — ', $messages) + : 'سند قابل‌اعمالی یافت نشد', 'result' => $result, ], $appliedDocs > 0 || $appliedMande ? 200 : 422); } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index a9bdfba..63c2f5c 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -83,12 +83,12 @@ class AuthController extends Controller protected function _loginWithSms($mobile, $provider) { + $cfg = app(ConfigService::class); + if ($provider == 'user') { $user = User::where('mobile', $mobile)->first(); - $subDomain = $this->Config('admin_url'); } else { $user = Customer::where('mobile', $mobile)->first(); - $subDomain = $this->Config('app_url'); } if (is_null($user)) { throw new UnprocessableEntityHttpException(trans('لطفا ابتدا ثبت نام بفرمایید')); @@ -105,7 +105,14 @@ class AuthController extends Controller $user->save(); if (self::isSmsNotifEnabled('sms_notif_login_otp')) { - $this->SendMessage($mobile, 'رمز عبور یکبار مصرف شما جهت ورود به اپلیکشن ' . $this->Config('name_fa') . ' ' . $rand . ' می باشد' . PHP_EOL . "@$subDomain" . " #" . $rand,true ,"sms"); + $otpHost = $cfg->resolvePublicHost(request()); + $otpLine = $otpHost !== '' ? PHP_EOL . "@{$otpHost} #{$rand}" : ''; + $this->SendMessage( + $mobile, + 'رمز عبور یکبار مصرف شما جهت ورود به اپلیکشن ' . $this->Config('name_fa') . ' ' . $rand . ' می باشد' . $otpLine, + true, + 'sms' + ); } return $user->id; @@ -229,7 +236,7 @@ class AuthController extends Controller $message .= "\nپس از اولین خرید مقدار، " . number_format((int) ($bonusValues[0]['customer'] ?? 0)) . ' ریال پاداش دریافت خواهید کرد.'; } } - $message .= "\nنام کاربری: {$data['mobile']}\nورود به نرم افزار: {$this->Config('app_url')}"; + $message .= "\nنام کاربری: {$data['mobile']}\nورود به نرم افزار: " . self::publicSiteUrl(request()); if (self::isSmsNotifEnabled('sms_notif_register_customer')) { $this->Send($data['mobile'], $message); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 1a0dce8..8d5f512 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -213,6 +213,18 @@ class Controller extends BaseController return false; } + /** دامنهٔ عمومی سایت — فقط از درخواست HTTP فعلی */ + protected static function publicHost(?\Illuminate\Http\Request $request = null): string + { + return app(ConfigService::class)->resolvePublicHost($request); + } + + /** آدرس کامل وب‌اپ کاربر — مثلاً https://example.ir */ + protected static function publicSiteUrl(?\Illuminate\Http\Request $request = null): string + { + return app(ConfigService::class)->resolvePublicSiteUrl($request); + } + protected static function metalTypeSettingValue(): ?string { $metalType = Setting::where('key', 'metal_type')->value('value'); diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 1b7d3c0..37cec28 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -820,7 +820,7 @@ class SingleOrderController extends Controller "مبلغ کل: " . number_format($totalPrice) . "\n\n" . "موجودی {$balanceLabel}: {$goldBalance}\n" . "مانده ریالی: " . number_format($cashBalance) . "\n" . - $this->Config('app_url'); + self::publicSiteUrl(request()); if (self::isSmsNotifEnabled('sms_notif_order_customer')) { @@ -964,7 +964,7 @@ class SingleOrderController extends Controller ->where('owner_type', $customer->type) ->main() ->first(); - $companyurl = $this->Config('app_url'); + $companyurl = self::publicSiteUrl(request()); $weight = $data['amount']; $totalAmount = $data['total_price']; $pricePerGram = $weight > 0 ? $totalAmount / $weight : 0; diff --git a/app/Http/Controllers/Site/RequestsController.php b/app/Http/Controllers/Site/RequestsController.php index 71004ed..ede7b10 100644 --- a/app/Http/Controllers/Site/RequestsController.php +++ b/app/Http/Controllers/Site/RequestsController.php @@ -204,11 +204,11 @@ class RequestsController extends Controller if($data['method'] == 'cash') { $customerMessage = "$companyName\nبرداشت ریالی \nمبلغ: {$data['amount']}\n" . "\nموجودی طلایی: {$goldBalance}\n" . - "مانده ریالی: " . number_format($cashBalance) . "\n" . $this->Config('app_url'); + "مانده ریالی: " . number_format($cashBalance) . "\n" . self::publicSiteUrl(request()); }elseif ($data['method'] == 'gold') { $customerMessage = "$companyName\nبرداشت طلایی \nگرم: {$data['amount']}\nقیمت گرم: " . number_format($pricePerGram) . "\nمبلغ کل: " . number_format($data['total_price']) . "\nموجودی طلایی: {$goldBalance}\n" . - "مانده ریالی: " . number_format($cashBalance) . "\n" . $this->Config('app_url'); + "مانده ریالی: " . number_format($cashBalance) . "\n" . self::publicSiteUrl(request()); } diff --git a/app/Http/Controllers/SuperController.php b/app/Http/Controllers/SuperController.php index fbd31c2..38914d8 100644 --- a/app/Http/Controllers/SuperController.php +++ b/app/Http/Controllers/SuperController.php @@ -64,7 +64,7 @@ class SuperController extends Controller } while ($existingCustomer); // تکرار تا زمانی که کد یونیک ایجاد شود $data['referral_id'] = $uniqueId; // ذخیره کد یونیک در فیلد referral_id - $message = "{$data['name']} عزیز، اکانت شما در پلتفرم {$this->Config('name_fa')} ایجاد شد.\nنام کاربری: {$data['mobile']}\nورود به نرم افزار: {$this->Config('app_url')}"; + $message = "{$data['name']} عزیز، اکانت شما در پلتفرم {$this->Config('name_fa')} ایجاد شد.\nنام کاربری: {$data['mobile']}\nورود به نرم افزار: " . self::publicSiteUrl(request()); $this->Send($data['mobile'], $message); diff --git a/app/Http/Controllers/pay/paypingController.php b/app/Http/Controllers/pay/paypingController.php index 11e8d6c..e13b4f2 100644 --- a/app/Http/Controllers/pay/paypingController.php +++ b/app/Http/Controllers/pay/paypingController.php @@ -112,7 +112,7 @@ class paypingController extends Controller 'user_id' => $user->id, 'user' => $user, 'title' => $this->Config('name_fa'), - 'return_url' => 'https://' . $this->Config('app_url') . '/transactions', + 'return_url' => $this->cfg()->resolvePublicSiteUrl(request()) . '/transactions', 'server_call_url' => url('/customer/charge_account_verify/' . $Withdraw->id), 'item' => $this->resolveGatewayItem(), ]; @@ -158,7 +158,7 @@ class paypingController extends Controller } $Withdraw = Withdraw::with('customer')->where('id', $id)->first(); - $app_url = $this->Config('app_url'); + $site_url = $this->cfg()->resolvePublicSiteUrl(request()); $User = Customer::where('id', $Withdraw->customer_id)->first(); $GTransaction = Http::withHeaders($this->paypingHeaders()) @@ -206,7 +206,7 @@ class paypingController extends Controller } }); - return view('showTransactionReport', compact('Withdraw', 'app_url')); + return view('showTransactionReport', compact('Withdraw', 'site_url')); } return 'مبلغ پرداختی دستکاری شده !'; @@ -221,13 +221,13 @@ class paypingController extends Controller $Withdraw->sts = 3; $Withdraw->save(); - return view('showTransactionReport', compact('Withdraw', 'app_url')); + return view('showTransactionReport', compact('Withdraw', 'site_url')); } public function verifyTransation() { $Withdraw = Withdraw::with('customer')->where('id', $_GET['clientrefid'])->first(); - $app_url = $this->Config('app_url'); + $site_url = $this->cfg()->resolvePublicSiteUrl(request()); $User = Customer::where('id', $Withdraw->customer_id)->first(); $verifyResponse = Http::withToken('cf09ee0187335f173c786e7660d778d2dd71ab582d282999f751427f5923d525') @@ -297,7 +297,7 @@ class paypingController extends Controller } }); - return view('showTransactionReport', compact('Withdraw', 'app_url')); + return view('showTransactionReport', compact('Withdraw', 'site_url')); } return 'مبلغ پرداختی دستکاری شده !'; @@ -306,6 +306,6 @@ class paypingController extends Controller $Withdraw->sts = 3; $Withdraw->save(); - return view('showTransactionReport', compact('Withdraw', 'app_url')); + return view('showTransactionReport', compact('Withdraw', 'site_url')); } } diff --git a/app/Models/File.php b/app/Models/File.php index f947888..4e868e1 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -170,7 +170,7 @@ class File extends Model { try { $rows = Setting::query() - ->whereIn('key', ['bace_url', 'server_url', 'admin_url', 'app_url']) + ->whereIn('key', ['bace_url', 'server_url']) ->where('is_active', 1) ->pluck('value', 'key'); @@ -180,21 +180,6 @@ class File extends Model return rtrim($value, '/'); } } - - $adminUrl = trim((string) ($rows['admin_url'] ?? '')); - if ($adminUrl !== '' && preg_match('#/admin#i', $adminUrl)) { - return rtrim(preg_replace('#/admin/?$#i', '/server', rtrim($adminUrl, '/')), '/'); - } - - $siteUrl = trim((string) ($rows['app_url'] ?? '')); - if ($siteUrl !== '') { - $siteUrl = rtrim($siteUrl, '/'); - if (! preg_match('#/server$#i', $siteUrl)) { - $siteUrl .= '/server'; - } - - return $siteUrl; - } } catch (\Throwable) { } diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index 39bc9a2..ff4bc77 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -756,4 +756,102 @@ class ConfigService { return $this->accountingProvider() === self::ACCOUNTING_PARGAR; } + + /** + * Hostname for WebOTP SMS (@domain #code) and other public links. + * Only from the current HTTP request — no settings, no .env. + */ + public function resolvePublicHost(?\Illuminate\Http\Request $request = null): string + { + foreach ($this->publicHostCandidates($request) as $host) { + if ($this->isUsableHost($host)) { + return $this->normalizeHost($host); + } + } + + return ''; + } + + /** Full user-facing site URL, e.g. https://example.ir */ + public function resolvePublicSiteUrl(?\Illuminate\Http\Request $request = null): string + { + if ($request !== null) { + $origin = rtrim(trim((string) $request->getSchemeAndHttpHost()), '/'); + $host = $this->parseHostFromUrl($origin); + if ($this->isUsableHost($host)) { + return $origin; + } + } + + if (! app()->runningInConsole() && request()) { + $origin = rtrim(trim((string) request()->getSchemeAndHttpHost()), '/'); + $host = $this->parseHostFromUrl($origin); + if ($this->isUsableHost($host)) { + return $origin; + } + } + + $host = $this->resolvePublicHost($request); + if ($host !== '') { + return 'https://' . $host; + } + + return ''; + } + + /** @deprecated Use resolvePublicHost() — kept for backward compatibility */ + public function resolveHostForOtp(string $urlSettingKey = 'app_url', ?\Illuminate\Http\Request $request = null): string + { + return $this->resolvePublicHost($request); + } + + /** @return list */ + private function publicHostCandidates(?\Illuminate\Http\Request $request): array + { + $candidates = []; + + if ($request !== null) { + $candidates[] = trim((string) $request->getHost()); + } + + if (! app()->runningInConsole() && request()) { + $candidates[] = trim((string) request()->getHost()); + } + + return $candidates; + } + + private function isUsableHost(string $host): bool + { + $host = strtolower(trim($host)); + + return $host !== '' + && ! in_array($host, ['localhost', '127.0.0.1', '0.0.0.0'], true); + } + + private function parseHostFromUrl(string $url): string + { + $url = trim($url); + if ($url === '') { + return ''; + } + + if (! preg_match('#^https?://#i', $url)) { + $url = 'https://' . ltrim($url, '/'); + } + + $host = parse_url($url, PHP_URL_HOST); + if (! is_string($host) || $host === '') { + return ''; + } + + return $this->normalizeHost($host); + } + + private function normalizeHost(string $host): string + { + $host = strtolower(trim($host)); + + return str_starts_with($host, 'www.') ? substr($host, 4) : $host; + } } diff --git a/app/Services/PargarInboundWalletService.php b/app/Services/PargarInboundWalletService.php index 3dce010..a261f61 100644 --- a/app/Services/PargarInboundWalletService.php +++ b/app/Services/PargarInboundWalletService.php @@ -172,6 +172,13 @@ class PargarInboundWalletService */ public function applyLatestMandeDifference(Customer $customer): array { + if ($this->hasPendingOutboundWalletSync((int) $customer->id)) { + return [ + 'applied' => false, + 'message' => 'سند ارسالی از پنل به پرگار هنوز تأیید نشده — برای جلوگیری از برگشت شارژ، ماندهٔ پرگار اعمال نشد', + ]; + } + $log = PargarSyncLog::query() ->where('customer_id', $customer->id) ->where('type', PargarSyncService::TYPE_MANDE) @@ -393,6 +400,15 @@ class PargarInboundWalletService ]); } + private function hasPendingOutboundWalletSync(int $customerId): bool + { + return PargarSyncLog::query() + ->where('customer_id', $customerId) + ->whereIn('type', [PargarSyncService::TYPE_WALLET_CASH, PargarSyncService::TYPE_WALLET_GOLD]) + ->where('sync_status', PargarSyncService::STATUS_PENDING) + ->exists(); + } + private function parseNumber(mixed $value): float { if ($value === null || $value === '') { diff --git a/app/Services/PriceStreamService.php b/app/Services/PriceStreamService.php index eaab4d0..c728ac1 100644 --- a/app/Services/PriceStreamService.php +++ b/app/Services/PriceStreamService.php @@ -38,36 +38,7 @@ class PriceStreamService public function resolveHost(?Request $request = null): string { - $config = app(ConfigService::class); - - $appUrl = trim($config->getString('app_url', '')); - if ($appUrl !== '') { - $host = parse_url($appUrl, PHP_URL_HOST); - if (is_string($host) && $host !== '') { - return $this->normalizeHost($host); - } - } - - if ($request !== null) { - $requestHost = trim((string) $request->getHost()); - if ($requestHost !== '') { - return $this->normalizeHost($requestHost); - } - } - - if (! app()->runningInConsole() && request()) { - $requestHost = trim((string) request()->getHost()); - if ($requestHost !== '') { - return $this->normalizeHost($requestHost); - } - } - - $nameEn = trim((string) $config->getString('name_en', '')); - if ($nameEn !== '') { - return $this->normalizeHost($nameEn); - } - - return ''; + return app(ConfigService::class)->resolvePublicHost($request); } public function currentSeq(): int @@ -219,11 +190,4 @@ class PriceStreamService return md5(json_encode($data, JSON_UNESCAPED_UNICODE)); } - - private function normalizeHost(string $host): string - { - $host = strtolower(trim($host)); - - return strpos($host, 'www.') === 0 ? substr($host, 4) : $host; - } } diff --git a/app/Services/ReferralGiftService.php b/app/Services/ReferralGiftService.php index 5dad8dc..2edd109 100644 --- a/app/Services/ReferralGiftService.php +++ b/app/Services/ReferralGiftService.php @@ -96,7 +96,7 @@ class ReferralGiftService $giftForInviter = (float) $config->get('gift_for_referrer', 0); $giftForInvited = (float) $config->get('gift_for_referral', 0); $companyName = $config->getString('name_fa'); - $companyUrl = $config->getString('app_url'); + $companyUrl = $config->resolvePublicSiteUrl(request()); if ($referrer && $referrerGiftAllowed && $giftForInviter > 0 && !$this->referrerGiftAlreadyGiven($customer, $referrer)) { $this->giveReferrerGift($customer, $referrer, $giftForInviter, $companyName, $companyUrl, $config); diff --git a/app/Services/WebsocketBroadcastService.php b/app/Services/WebsocketBroadcastService.php index 6a116cb..ccd7b81 100644 --- a/app/Services/WebsocketBroadcastService.php +++ b/app/Services/WebsocketBroadcastService.php @@ -74,7 +74,7 @@ class WebsocketBroadcastService } $body = array_merge($payload, ['trigger' => $trigger]); - $host = $this->resolveHost(); + $host = app(ConfigService::class)->resolvePublicHost(); if ($host !== '') { $body['host'] = $host; } @@ -103,31 +103,4 @@ class WebsocketBroadcastService ]); } } - - private function resolveHost(): string - { - $config = app(ConfigService::class); - - $appUrl = trim($config->getString('app_url', '')); - if ($appUrl !== '') { - $host = parse_url($appUrl, PHP_URL_HOST); - if (is_string($host) && $host !== '') { - return $host; - } - } - - if (! app()->runningInConsole() && request()) { - $requestHost = trim((string) request()->getHost()); - if ($requestHost !== '') { - return $requestHost; - } - } - - $nameEn = trim((string) $config->getString('name_en', '')); - if ($nameEn !== '') { - return $nameEn; - } - - return ''; - } } diff --git a/resources/views/showTransactionReport.blade.php b/resources/views/showTransactionReport.blade.php index 5fdea27..e168b44 100644 --- a/resources/views/showTransactionReport.blade.php +++ b/resources/views/showTransactionReport.blade.php @@ -338,7 +338,7 @@ diff --git a/tests/Unit/ConfigServiceOtpHostTest.php b/tests/Unit/ConfigServiceOtpHostTest.php new file mode 100644 index 0000000..fad741a --- /dev/null +++ b/tests/Unit/ConfigServiceOtpHostTest.php @@ -0,0 +1,55 @@ +app->instance('request', $request); + + $host = app(ConfigService::class)->resolvePublicHost($request); + + $this->assertSame('boursemes.com', $host); + } + + public function test_resolve_public_site_url_from_request(): void + { + $request = Request::create('https://boursemes.com/server/auth/login', 'POST'); + + $url = app(ConfigService::class)->resolvePublicSiteUrl($request); + + $this->assertSame('https://boursemes.com', $url); + } + + public function test_returns_empty_without_request(): void + { + $host = app(ConfigService::class)->resolvePublicHost(); + + $this->assertSame('', $host); + } + + public function test_strips_www_prefix(): void + { + $request = Request::create('https://www.boursemes.com/server/auth/login', 'POST'); + + $host = app(ConfigService::class)->resolvePublicHost($request); + + $this->assertSame('boursemes.com', $host); + } + + public function test_skips_localhost_request_host(): void + { + $request = Request::create('http://localhost/server/auth/login', 'POST'); + $this->app->instance('request', $request); + + $host = app(ConfigService::class)->resolvePublicHost($request); + + $this->assertSame('', $host); + } +}