From 7f0f91701e17083aa30c81ae2d267db968a8893c Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 24 May 2026 22:29:43 +0330 Subject: [PATCH] completed liman for using in sell and buy --- .../Single/SingleOrderController.php | 69 +++- .../Controllers/Site/ReportController.php | 257 ++++++++----- app/Services/ConfigService.php | 5 +- app/Services/LimanService.php | 359 +++++++++++++++++- 4 files changed, 591 insertions(+), 99 deletions(-) diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 9595198..31d2021 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -23,6 +23,7 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Illuminate\Support\Facades\Http; use App\Models\Setting; use App\Models\Cnt; +use App\Services\LimanService; class SingleOrderController extends Controller @@ -31,11 +32,21 @@ class SingleOrderController extends Controller { $data = $request->all(); - if($this->Config('goldir_price') == 'true'){ + + if ($this->Config('liman_price') == 'true') { + $liman_closed = Cnt::where('key', 'liman_closed')->value('value') == "true"; + $liman_buy = Cnt::where('key', 'liman_open_buy')->value('value') == "false" && $data['direction'] == 1; + $liman_sell = Cnt::where('key', 'liman_open_sell')->value('value') == "false" && $data['direction'] == 2; + + if ($liman_closed || $liman_buy || $liman_sell) { + throw new UnprocessableEntityHttpException('عدم ارائه سرویس.'); + } + } elseif ($this->Config('goldir_price') == 'true') { $goldir_closed = Cnt::where('key', 'goldir_closed')->first()['value'] == "true"; $goldir_buy = Cnt::where('key', 'goldir_open_buy')->first()['value'] == "false" && $data['direction'] == 1; $goldir_sell = Cnt::where('key', 'goldir_open_sell')->first()['value'] == "false" && $data['direction'] == 2; - if($goldir_closed || $goldir_buy || $goldir_sell){ + + if ($goldir_closed || $goldir_buy || $goldir_sell) { throw new UnprocessableEntityHttpException('عدم ارائه سرویس.'); } } @@ -180,7 +191,7 @@ class SingleOrderController extends Controller ], 1, true, false); $response['1'] = 2; //send to Goldir - $this->sendGoldir($data); + $this->sendProviderOrder($data, $wallet_interface, (int) $data['direction']); return $response; } @@ -291,7 +302,7 @@ class SingleOrderController extends Controller ], 2, false, true); //send to Goldir - $this->sendGoldir($data); + $this->sendProviderOrder($data, $wallet_interface, (int) $data['direction']); } @@ -459,6 +470,56 @@ class SingleOrderController extends Controller $this->send($customer->mobile, $customerMessage); } + private function sendProviderOrder(array &$data, $wallet_interface, int $direction) + { + if ($this->Config('liman_price') == "true") { + try { + return app(LimanService::class)->sendOrder($data); + } catch (UnprocessableEntityHttpException $e) { + $this->rollbackProviderFailedOrder($data, $wallet_interface, $direction); + throw $e; + } catch (\Throwable $e) { + $this->rollbackProviderFailedOrder($data, $wallet_interface, $direction); + + throw new UnprocessableEntityHttpException( + 'خطا در ثبت سفارش نزد لیمان: ' . $e->getMessage() + ); + } + } + + return $this->sendGoldir($data); + } + + private function rollbackProviderFailedOrder(array $data, $wallet_interface, int $direction): void + { + $order = $data['order'] ?? null; + + if (!$order instanceof Order) { + return; + } + + if ($direction === 1) { + new TransactionInterface($wallet_interface, [ + 'cash' => $data['payment_amount'], + 'gold' => $data['amount'], + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + ], 2, false, true); + } else { + new TransactionInterface($wallet_interface, [ + 'gold' => $data['amount'], + 'cash' => $data['total_price'], + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + ], 1, true, false); + } + + $order->delete(); + } + private function sendGoldir($data) { if ($this->Config('send_to_goldir') == "true") { diff --git a/app/Http/Controllers/Site/ReportController.php b/app/Http/Controllers/Site/ReportController.php index ee339d5..f0da1e2 100644 --- a/app/Http/Controllers/Site/ReportController.php +++ b/app/Http/Controllers/Site/ReportController.php @@ -126,6 +126,7 @@ class ReportController extends Controller $liman_open_sell = false; $priceSource = null; + $priceValue = 0; $toBool = function ($value) { return filter_var($value, FILTER_VALIDATE_BOOLEAN); @@ -133,7 +134,7 @@ class ReportController extends Controller /* |-------------------------------------------------------------------------- - | بستن قیمت طبق منطق تابع اول + | بستن قیمت طبق منطق قبلی |-------------------------------------------------------------------------- */ if ($this->Config('close_price') == 'true') { @@ -144,8 +145,8 @@ class ReportController extends Controller [$endHour, $endMinute] = explode(':', $endDay); $current = Carbon::now(); - $start = Carbon::today()->setTime((int)$startHour, (int)$startMinute); - $end = Carbon::today()->setTime((int)$endHour, (int)$endMinute); + $start = Carbon::today()->setTime((int) $startHour, (int) $startMinute); + $end = Carbon::today()->setTime((int) $endHour, (int) $endMinute); if (!$current->between($start, $end)) { Cnt::where('key', 'app_closed')->update(['value' => "true"]); @@ -164,16 +165,16 @@ class ReportController extends Controller 'closed' => true, 'time' => now()->format('H:i:s'), ]; - } else { - Cnt::where('key', 'app_closed')->update(['value' => "false"]); - Cnt::where('key', 'goldir_closed')->update(['value' => "false"]); - Cnt::where('key', 'liman_closed')->update(['value' => "false"]); } + + Cnt::where('key', 'app_closed')->update(['value' => "false"]); + Cnt::where('key', 'goldir_closed')->update(['value' => "false"]); + Cnt::where('key', 'liman_closed')->update(['value' => "false"]); } /* |-------------------------------------------------------------------------- - | تنظیمات + | Settings |-------------------------------------------------------------------------- */ $settings = Setting::whereIn('key', [ @@ -192,97 +193,144 @@ class ReportController extends Controller $gold_ir_price = $settingsMap->get('goldir_price'); $main_product_price = $settingsMap->get('main_product_price'); - $diff_buy = (float) $settingsMap->get('buy_fee', 0); - $diff_sell = (float) $settingsMap->get('sell_fee', 0); + $diff_buy = (float) $settingsMap->get('buy_fee', $difference); + $diff_sell = (float) $settingsMap->get('sell_fee', $difference); - // این همون کلیدیه که تو عکس فرستادی $liman_price_enabled = $toBool($this->Config('liman_price')); + \Log::info('LAST_PRICE DEBUG START', [ + 'file' => __FILE__, + 'close_price' => $this->Config('close_price'), + 'liman_price_raw' => $this->Config('liman_price'), + 'liman_price_enabled' => $liman_price_enabled, + 'liman_product_id' => $this->Config('liman_product_id'), + 'liman_base_url' => $this->Config('liman_base_url'), + 'liman_token_exists' => (bool) Setting::where('key', 'liman_token')->value('value'), + ]); + /* |-------------------------------------------------------------------------- - | Liman - اولویت اول + | Montpar / Liman - اولویت اول |-------------------------------------------------------------------------- */ if ($liman_price_enabled && !$closed) { try { - $liman = Http::timeout(7) - ->withOptions([ - 'curl' => [ - CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, - CURLOPT_TCP_FASTOPEN => 1, - ] - ]) - ->withToken(Setting::where('key', 'liman_token')->value('value')) - ->get('http://212.23.201.173/server/customer/product/list') - ->json(); + $limanBaseUrl = rtrim($this->Config('liman_base_url') ?: 'https://montpar.ir', '/'); + $limanProductId = (int) ($this->Config('liman_product_id') ?: 25); - if ((isset($liman['message']) && $liman['message'] == 'no authorised') || $liman == null) { - $newToken = app(LimanService::class)->liman_login(); + $liman = app(LimanService::class)->productList(); - $liman = Http::timeout(7) - ->withToken($newToken) - ->get('http://212.23.201.173/server/customer/product/list') - ->json(); + \Log::info('PROVIDER FIRST RESPONSE', [ + 'base_url' => $limanBaseUrl, + 'product_id' => $limanProductId, + 'type' => gettype($liman), + 'message' => is_array($liman) ? ($liman['message'] ?? null) : null, + 'available_ids' => is_array($liman) ? collect($liman)->pluck('id')->values()->all() : null, + ]); + + if (isset($liman['message'])) { + throw new \Exception('Provider message: ' . $liman['message']); } $collection = collect($liman); - $lpLiman = $collection->firstWhere('id', 25); + $lpLiman = $collection->firstWhere('id', $limanProductId); - if ($lpLiman && isset($lpLiman['sub_agent'])) { - $buy = (float) $lpLiman['sub_agent']['rate_buy_gram_price']; - $sell = (float) $lpLiman['sub_agent']['rate_sell_gram_price']; + \Log::info('PROVIDER PRODUCT PICKED', [ + 'base_url' => $limanBaseUrl, + 'liman_product_id' => $limanProductId, + 'found' => (bool) $lpLiman, + 'has_sub_agent' => $lpLiman && isset($lpLiman['sub_agent']), + 'open_buy' => $lpLiman['open_buy'] ?? null, + 'open_sell' => $lpLiman['open_sell'] ?? null, + 'buy_gram' => $lpLiman['sub_agent']['rate_buy_gram_price'] ?? null, + 'sell_gram' => $lpLiman['sub_agent']['rate_sell_gram_price'] ?? null, + ]); - $customPrice = []; - $customPrice['price'] = ($buy + $sell) / 2; - $customPrice['diff'] = ($buy - $sell) / 2; + if (!$lpLiman) { + throw new \Exception("Provider product not found. product_id={$limanProductId}"); + } - Cnt::where('key', 'liman_price')->update(['value' => (float) $customPrice['price']]); - Cnt::where('key', 'liman_diff')->update(['value' => (float) $customPrice['diff']]); + if (!isset($lpLiman['sub_agent'])) { + throw new \Exception("Provider product has no sub_agent. product_id={$limanProductId}"); + } - Cnt::where('key', 'liman_mazaneh')->update([ - 'value' => ((float) $customPrice['price'] * 4.331802) - ]); + $buy = (float) ($lpLiman['sub_agent']['rate_buy_gram_price'] ?? 0); + $sell = (float) ($lpLiman['sub_agent']['rate_sell_gram_price'] ?? 0); - Cnt::where('key', 'liman_truly_buy_mazaneh')->update([ - 'value' => $lpLiman['sub_agent']['rate_buy_price'] - ]); + if ($buy <= 0 || $sell <= 0) { + throw new \Exception("Provider invalid gram price. buy={$buy}, sell={$sell}"); + } - Cnt::where('key', 'liman_truly_sell_mazaneh')->update([ - 'value' => $lpLiman['sub_agent']['rate_sell_price'] - ]); + $customPrice = []; + $customPrice['price'] = ($buy + $sell) / 2; + $customPrice['diff'] = ($buy - $sell) / 2; - Cnt::where('key', 'log')->update([ - 'value' => json_encode($lpLiman, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) - ]); + Cnt::where('key', 'liman_price')->update([ + 'value' => (float) $customPrice['price'] + ]); - $liman_open_sell = $toBool($lpLiman['open_sell'] ?? false); - $liman_open_buy = $toBool($lpLiman['open_buy'] ?? false); + Cnt::where('key', 'liman_diff')->update([ + 'value' => (float) $customPrice['diff'] + ]); - Cnt::where('key', 'liman_open_sell')->update([ - 'value' => $liman_open_sell ? "true" : "false" - ]); + Cnt::where('key', 'liman_mazaneh')->update([ + 'value' => ((float) $customPrice['price'] * 4.331802) + ]); - Cnt::where('key', 'liman_open_buy')->update([ - 'value' => $liman_open_buy ? "true" : "false" - ]); + Cnt::where('key', 'liman_truly_buy_mazaneh')->update([ + 'value' => $lpLiman['sub_agent']['rate_buy_price'] ?? 0 + ]); - if ($liman_open_sell || $liman_open_buy) { - $liman_price = (int) $customPrice['price']; - } + Cnt::where('key', 'liman_truly_sell_mazaneh')->update([ + 'value' => $lpLiman['sub_agent']['rate_sell_price'] ?? 0 + ]); + + Cnt::where('key', 'log')->update([ + 'value' => json_encode($lpLiman, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) + ]); + + $liman_open_sell = $toBool($lpLiman['open_sell'] ?? false); + $liman_open_buy = $toBool($lpLiman['open_buy'] ?? false); + + Cnt::where('key', 'liman_open_sell')->update([ + 'value' => $liman_open_sell ? "true" : "false" + ]); + + Cnt::where('key', 'liman_open_buy')->update([ + 'value' => $liman_open_buy ? "true" : "false" + ]); + + if ($liman_open_sell || $liman_open_buy) { + $liman_price = (int) $customPrice['price']; } Cnt::where('key', 'liman_closed')->update([ 'value' => $liman_price == 0 ? "true" : "false" ]); - } catch (\Exception $e) { + + \Log::info('PROVIDER PRICE SUCCESS', [ + 'base_url' => $limanBaseUrl, + 'liman_product_id' => $limanProductId, + 'liman_price' => $liman_price, + 'liman_diff' => $customPrice['diff'], + 'open_buy' => $liman_open_buy, + 'open_sell' => $liman_open_sell, + ]); + } catch (\Throwable $e) { $liman_price = 0; + \Log::error('PROVIDER PRICE ERROR', [ + 'base_url' => $limanBaseUrl ?? null, + 'message' => $e->getMessage(), + 'liman_product_id' => $limanProductId ?? null, + 'liman_response' => $liman ?? null, + ]); + Cnt::where('key', 'liman_closed')->update([ 'value' => "true" ]); } } - /* |-------------------------------------------------------------------------- | Goldir @@ -322,8 +370,13 @@ class ReportController extends Controller $customPrice['price'] = (ceil($buy) + ceil($sell)) / 2; $customPrice['diff'] = (ceil($buy) - ceil($sell)) / 2; - Cnt::where('key', 'goldir_price')->update(['value' => (float) $customPrice['price']]); - Cnt::where('key', 'goldir_diff')->update(['value' => (float) $customPrice['diff']]); + Cnt::where('key', 'goldir_price')->update([ + 'value' => (float) $customPrice['price'] + ]); + + Cnt::where('key', 'goldir_diff')->update([ + 'value' => (float) $customPrice['diff'] + ]); Cnt::where('key', 'goldir_mazaneh')->update([ 'value' => ((float) $customPrice['price'] * 4.331802) @@ -360,7 +413,7 @@ class ReportController extends Controller Cnt::where('key', 'goldir_closed')->update([ 'value' => $goldir_price == 0 ? "true" : "false" ]); - } catch (\Exception $e) { + } catch (\Throwable $e) { $goldir_price = 0; Cnt::where('key', 'goldir_closed')->update([ @@ -402,7 +455,7 @@ class ReportController extends Controller ->json(); $tgn = (int) $taban_gohar['c'] * 10; - } catch (\Exception $e) { + } catch (\Throwable $e) { $tgn = 0; } } @@ -410,8 +463,8 @@ class ReportController extends Controller /* |-------------------------------------------------------------------------- | انتخاب قیمت اصلی - | اولویت جدید: - | Liman -> Goldir -> Taban Gohar -> Main Product + | اولویت: + | Liman/Montpar -> Goldir -> Taban Gohar -> Main Product |-------------------------------------------------------------------------- */ if ($liman_price_enabled && ($liman_price ?? 0) !== 0 && !$closed) { @@ -428,6 +481,35 @@ class ReportController extends Controller $priceSource = 'main_product'; } else { $priceValue = 0; + $priceSource = null; + } + + /* + |-------------------------------------------------------------------------- + | اگر هیچ منبعی قیمت نداد، قیمت قبلی دیتابیس را برنگردان + |-------------------------------------------------------------------------- + */ + if ($priceValue <= 0) { + \Log::warning('LAST_PRICE NO SOURCE PRICE', [ + 'liman_price' => $liman_price, + 'goldir_price' => $goldir_price, + 'taban_gohar' => $tgn, + 'main_product' => $pr_price, + 'price_source' => $priceSource, + ]); + + return [ + 'sell' => 0, + 'buy' => 0, + 'price_last' => 0, + 'price' => 0, + 'main_product' => $pr_price ?? 0, + 'taban_gohar' => $tgn ?? 0, + 'goldir' => $goldir_price ?? 0, + 'liman' => $liman_price ?? 0, + 'closed' => true, + 'time' => now(), + ]; } /* @@ -473,53 +555,41 @@ class ReportController extends Controller } } - $price = Price::orderBy('id', 'desc')->first(); - - if (!$price) { - return []; - } - - $priceValue = $price->price; - /* |-------------------------------------------------------------------------- - | محاسبه sell / buy با حفظ ساختار خروجی تابع اول + | محاسبه sell / buy با همان priceValue همین اجرای فعلی |-------------------------------------------------------------------------- */ - if ($priceSource === 'liman' && $liman_price !== 0) { + if ($priceSource === 'liman') { $liman_profit = (float) Cnt::where('key', 'liman_diff')->value('value'); $final_buy_diff = $diff_buy + $liman_profit; $final_sell_diff = $diff_sell + $liman_profit; - $sell = $priceValue !== 0 - ? ($liman_open_sell ? ceil($price->price - $final_sell_diff) : 0) + $sell = $liman_open_sell + ? ceil($priceValue - $final_sell_diff) : 0; - $buy = $priceValue !== 0 - ? ($liman_open_buy ? ceil($price->price + $final_buy_diff) : 0) + $buy = $liman_open_buy + ? ceil($priceValue + $final_buy_diff) : 0; - $priceLast = $priceValue !== 0 - ? ceil($price->price + $final_buy_diff) - : 0; + $priceLast = ceil($priceValue + $final_buy_diff); } else { if ($priceSource === 'goldir' && $goldir_price !== 0) { $goldir_profit = (float) Cnt::where('key', 'goldir_diff')->value('value'); $difference = $goldir_profit; } - $sell = $priceValue !== 0 - ? ($open_sell ? ceil($price->price - $difference) : 0) + $sell = $open_sell + ? ceil($priceValue - $difference) : 0; - $buy = $priceValue !== 0 - ? ($open_buy ? ceil($price->price + $difference) : 0) + $buy = $open_buy + ? ceil($priceValue + $difference) : 0; - $priceLast = $priceValue !== 0 - ? ceil($price->price + $difference) - : 0; + $priceLast = ceil($priceValue + $difference); } return [ @@ -531,11 +601,12 @@ class ReportController extends Controller 'taban_gohar' => $tgn ?? 0, 'goldir' => $goldir_price ?? 0, 'liman' => $liman_price ?? 0, - 'closed' => $closed ?? null, + 'closed' => false, 'time' => now(), ]; } + public function check() { $service = Http:: diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index 64cd63b..5f9bcae 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -13,6 +13,9 @@ class ConfigService public function UpdateConfig($name, $value) { - return Setting::where('key', $name)->update(['value' => $value]); + return Setting::updateOrCreate( + ['key' => $name], + ['value' => $value] + ); } } \ No newline at end of file diff --git a/app/Services/LimanService.php b/app/Services/LimanService.php index 6dbeaf4..e219b99 100644 --- a/app/Services/LimanService.php +++ b/app/Services/LimanService.php @@ -2,10 +2,27 @@ namespace App\Services; +use App\Models\Cnt; +use App\Models\Order; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; class LimanService { + + private function baseUrl(): string + { + return rtrim( + app(ConfigService::class)->Config('liman_base_url') ?: 'https://montpar.ir', + '/' + ); + } + + private function endpoint(string $path): string + { + return $this->baseUrl() . '/' . ltrim($path, '/'); + } public function liman_login() { $config = app(ConfigService::class); @@ -13,7 +30,7 @@ class LimanService $mobile = $config->Config('liman_mobile'); $password = $config->Config('liman_password'); - $liman = Http::timeout(10)->post('http://212.23.201.173/server/auth/login', [ + $liman = Http::timeout(10)->post($this->endpoint('/server/auth/login'), [ 'mobile' => $mobile, 'password' => $password, 'provider' => 'customer', @@ -30,4 +47,344 @@ class LimanService return $token; } + + public function getToken(): string + { + $token = trim((string) app(ConfigService::class)->Config('liman_token')); + + if (!$token) { + $token = $this->liman_login(); + } + + return $token; + } + + public function productList(): array + { + $token = $this->getToken(); + + $response = Http::timeout(7) + ->withOptions([ + 'curl' => [ + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + CURLOPT_TCP_FASTOPEN => 1, + ] + ]) + ->withToken($token) + ->get($this->endpoint('/server/customer/product/list')) + ->json(); + + if ((isset($response['message']) && $response['message'] === 'no authorised') || $response === null) { + $token = $this->liman_login(); + + $response = Http::timeout(7) + ->withToken($token) + ->get($this->endpoint('/server/customer/product/list')) + ->json(); + } + + if (!is_array($response)) { + throw new \Exception('Provider product list response is not array'); + } + + return $response; + } + + public function sendOrder(array &$data) + { + $payload = $this->makeOrderPayload($data); + + $token = $this->getToken(); + + $httpResponse = $this->postToLiman( + $this->endpoint('/server/customer/order'), + $token, + $payload + ); + + $body = $this->decodeLimanHttpBody($httpResponse); + + if (is_array($body) && ($body['message'] ?? null) === 'no authorised') { + $token = $this->liman_login(); + + $httpResponse = $this->postToLiman( + $this->endpoint('/server/customer/order'), + $token, + $payload + ); + + $body = $this->decodeLimanHttpBody($httpResponse); + } + + $this->logLimanOrderResponse($httpResponse, $body); + + $parsed = $this->parseLimanOrderResponse($body, (int) $data['direction']); + + if (!$parsed['ok']) { + Log::warning('Liman order rejected', [ + 'base_url' => $this->baseUrl(), + 'status' => $httpResponse->status(), + 'body' => $body, + 'raw' => $httpResponse->body(), + 'payload' => $payload, + 'reason' => $parsed['error'], + ]); + + throw new UnprocessableEntityHttpException( + $this->formatLimanErrorMessage($parsed['error'], $httpResponse) + ); + } + + $this->syncOrderWithLimanResponse($data, $parsed); + + return $body; + } + + private function makeOrderPayload(array $data): array + { + $mazaneh = ((int) $data['direction'] === 1) + ? Cnt::where('key', 'liman_truly_buy_mazaneh')->value('value') + : Cnt::where('key', 'liman_truly_sell_mazaneh')->value('value'); + + $limanPrice = (float) Cnt::where('key', 'liman_price')->value('value'); + + $cash = (int) round( + $data['total_price'] ?? ($limanPrice * (float) $data['amount']) + ); + + $customerName = auth('customer')->check() + ? auth('customer')->user()->name + : ($data['customer']['name'] ?? ''); + + return [ + 'mazaneh' => (int) $mazaneh, + 'amount' => (float) $data['amount'], + 'description' => app(ConfigService::class)->Config('name_fa') . ' - ' . $customerName, + 'direction' => (int) $data['direction'], + 'product_id' => (int) (app(ConfigService::class)->Config('liman_product_id') ?: 25), + 'cash' => $cash, + 'input_mode' => 'gold', + ]; + } + + private function postToLiman(string $url, string $token, array $payload) + { + return Http::timeout(15) + ->acceptJson() + ->withToken($token) + ->post($url, $payload); + } + + private function decodeLimanHttpBody($httpResponse): ?array + { + $body = $httpResponse->json(); + + if (is_array($body)) { + return $body; + } + + $raw = trim((string) $httpResponse->body()); + + if ($raw === '') { + return null; + } + + $decoded = json_decode($raw, true); + + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { + return $decoded; + } + + return null; + } + + private function logLimanOrderResponse($httpResponse, $body): void + { + try { + $value = $body === null + ? $httpResponse->body() + : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + + Cnt::where('key', 'liman_order_response')->update(['value' => $value]); + Cnt::where('key', 'last_order_log')->update(['value' => $value]); + } catch (\Exception $e) { + Log::warning('Could not persist liman order response', [ + 'error' => $e->getMessage(), + ]); + } + } + + private function formatLimanErrorMessage(?string $error, $httpResponse): string + { + if ($error && $error !== 'پاسخ نامعتبر از سرور لیمان (اتصال یا فرمت JSON)') { + return $error; + } + + $status = $httpResponse->status(); + $raw = trim((string) $httpResponse->body()); + + if ($raw === '') { + return "خطا در ارتباط با لیمان (HTTP {$status})"; + } + + if (stripos($raw, ' false, + 'provider_id' => null, + 'error' => 'پاسخ نامعتبر از سرور لیمان (اتصال یا فرمت JSON)', + ]; + } + + if (!empty($body['message']) && is_string($body['message'])) { + if ($body['message'] === 'no authorised') { + return [ + 'ok' => false, + 'provider_id' => null, + 'error' => 'no authorised', + ]; + } + + return [ + 'ok' => false, + 'provider_id' => null, + 'error' => $body['message'], + ]; + } + + $providerId = $this->extractProviderOrderId($body); + + if ($providerId !== null) { + return [ + 'ok' => true, + 'provider_id' => $providerId, + 'error' => null, + ]; + } + + $sideKey = $direction === 1 ? 'buy' : 'sell'; + + if (($body[$sideKey] ?? null) === true) { + return [ + 'ok' => true, + 'provider_id' => null, + 'error' => null, + ]; + } + + foreach ($body as $item) { + if (!is_array($item)) { + continue; + } + + if (!empty($item['id']) && is_numeric($item['id'])) { + return [ + 'ok' => true, + 'provider_id' => (int) $item['id'], + 'error' => null, + ]; + } + + if (!empty($item['order']['id'])) { + return [ + 'ok' => true, + 'provider_id' => (int) $item['order']['id'], + 'error' => null, + ]; + } + + if (isset($item['sts']) && in_array((int) $item['sts'], [1, 2], true)) { + return [ + 'ok' => true, + 'provider_id' => !empty($item['id']) ? (int) $item['id'] : null, + 'error' => null, + ]; + } + } + + return [ + 'ok' => false, + 'provider_id' => null, + 'error' => 'سفارشات توسط ارائه دهنده بسته شده است.', + ]; + } + + private function extractProviderOrderId(array $body): ?int + { + if (!empty($body['id'])) { + return (int) $body['id']; + } + + if (!empty($body['order']['id'])) { + return (int) $body['order']['id']; + } + + if (isset($body[1]) && is_array($body[1])) { + if (!empty($body[1]['id'])) { + return (int) $body[1]['id']; + } + + if (!empty($body[1]['order']['id'])) { + return (int) $body[1]['order']['id']; + } + } + + if (isset($body[2]) && is_array($body[2]) && !empty($body[2]['id'])) { + return (int) $body[2]['id']; + } + + foreach (['buy', 'sell'] as $sideKey) { + if (!isset($body[$sideKey]) || !is_array($body[$sideKey])) { + continue; + } + + if (!empty($body[$sideKey]['id'])) { + return (int) $body[$sideKey]['id']; + } + + if (!empty($body[$sideKey]['order']['id'])) { + return (int) $body[$sideKey]['order']['id']; + } + } + + return null; + } + + private function syncOrderWithLimanResponse(array &$data, array $parsed): void + { + $limanDiff = (float) Cnt::where('key', 'liman_diff')->value('value'); + $limanPrice = (float) Cnt::where('key', 'liman_price')->value('value'); + + $totalPriceLiman = ((int) $data['direction'] === 1) + ? ($limanPrice + $limanDiff) + : ($limanPrice - $limanDiff); + + $providerOrderId = $parsed['provider_id'] ?? null; + + if (isset($data['order']) && $data['order'] instanceof Order) { + if ($providerOrderId !== null) { + $data['order']->provider_id = $providerOrderId; + } + + $data['order']->total_price_liman = $totalPriceLiman; + $data['order']->save(); + } + + $data['total_price_liman'] = $totalPriceLiman; + + if ($providerOrderId !== null) { + $data['provider_id'] = $providerOrderId; + } + } } \ No newline at end of file