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); $mobile = $config->Config('liman_mobile'); $password = $config->Config('liman_password'); $liman = Http::timeout(10)->post($this->endpoint('/server/auth/login'), [ 'mobile' => $mobile, 'password' => $password, 'provider' => 'customer', 'type_app' => 'client', ])->json(); $token = $liman['access_token'] ?? $liman['token'] ?? null; if (!$token) { throw new \Exception('Liman login failed: ' . json_encode($liman, JSON_UNESCAPED_UNICODE)); } $config->UpdateConfig('liman_token', $token); 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; } } }