config()->Config('montzar_base_url') ?: 'https://montpar.ir', '/' ); } private function endpoint(string $path): string { return $this->baseUrl() . '/' . ltrim($path, '/'); } public function montzarLogin(): string { $response = Http::timeout(10) ->acceptJson() ->post($this->endpoint('/server/auth/login'), [ 'mobile' => $this->config()->Config('montzar_mobile'), 'password' => $this->config()->Config('montzar_password'), 'provider' => 'customer', 'type_app' => 'client', ]) ->json(); $token = $response['access_token'] ?? $response['token'] ?? null; if (!$token) { Log::error('Montzar login failed', [ 'base_url' => $this->baseUrl(), 'mobile' => $this->config()->Config('montzar_mobile'), 'response' => $response, ]); throw new \Exception('Montzar login failed: ' . json_encode($response, JSON_UNESCAPED_UNICODE)); } Setting::updateOrCreate( ['key' => 'montzar_token'], ['value' => $token, 'is_active' => 1] ); return $token; } public function getToken(): string { $token = trim((string) $this->config()->Config('montzar_token')); if (!$token) { $token = $this->montzarLogin(); } return $token; } public function productList(): array { $token = $this->getToken(); $response = Http::timeout(7) ->acceptJson() ->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->montzarLogin(); $response = Http::timeout(7) ->acceptJson() ->withToken($token) ->get($this->endpoint('/server/customer/product/list')) ->json(); } if (!is_array($response)) { throw new \Exception('Montzar product list response is not array'); } return $response; } public function sendOrder(array &$data) { $payload = $this->makeOrderPayload($data); $token = $this->getToken(); $httpResponse = $this->postToMontzar( $this->endpoint('/server/customer/order'), $token, $payload ); $body = $this->decodeMontzarHttpBody($httpResponse); if (is_array($body) && ($body['message'] ?? null) === 'no authorised') { $token = $this->montzarLogin(); $httpResponse = $this->postToMontzar( $this->endpoint('/server/customer/order'), $token, $payload ); $body = $this->decodeMontzarHttpBody($httpResponse); } $this->logMontzarOrderResponse($httpResponse, $body); $parsed = $this->parseMontzarOrderResponse($body, (int) $data['direction']); if (!$parsed['ok']) { Log::warning('Montzar order rejected', [ 'base_url' => $this->baseUrl(), 'status' => $httpResponse->status(), 'body' => $body, 'raw' => $httpResponse->body(), 'payload' => $payload, 'reason' => $parsed['error'], ]); throw new UnprocessableEntityHttpException( $this->formatMontzarErrorMessage($parsed['error'], $httpResponse) ); } $this->syncOrderWithMontzarResponse($data, $parsed); return $body; } private function makeOrderPayload(array $data): array { $mazaneh = ((int) $data['direction'] === 1) ? $this->config()->Config('montzar_truly_buy_mazaneh') : $this->config()->Config('montzar_truly_sell_mazaneh'); $montzarPrice = (float) $this->config()->Config('montzar_live_price'); $cash = (int) round( $data['total_price'] ?? ($montzarPrice * (float) $data['amount']) ); $customerName = auth('customer')->check() ? auth('customer')->user()->name : ($data['customer']['name'] ?? ''); return [ 'mazaneh' => (int) $mazaneh, 'amount' => (float) $data['amount'], 'description' => $this->config()->Config('name_fa') . ' - ' . $customerName, 'direction' => (int) $data['direction'], 'product_id' => (int) ($this->config()->Config('montzar_product_id') ?: 25), 'cash' => $cash, 'input_mode' => 'gold', ]; } private function postToMontzar(string $url, string $token, array $payload) { return Http::timeout(15) ->acceptJson() ->withToken($token) ->post($url, $payload); } private function decodeMontzarHttpBody($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 logMontzarOrderResponse($httpResponse, $body): void { try { $value = $body === null ? $httpResponse->body() : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); // مربوط به منتزر: settings $this->config()->UpdateConfig('montzar_order_response', $value); // عمومی: اگر در const/Cnt داری همان‌جا بماند Cnt::where('key', 'last_order_log')->update(['value' => $value]); } catch (\Exception $e) { Log::warning('Could not persist montzar order response', [ 'error' => $e->getMessage(), ]); } } private function formatMontzarErrorMessage(?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'])) { 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']; } foreach ([1, 2] as $index) { if (isset($body[$index]) && is_array($body[$index])) { if (!empty($body[$index]['id'])) { return (int) $body[$index]['id']; } if (!empty($body[$index]['order']['id'])) { return (int) $body[$index]['order']['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 syncOrderWithMontzarResponse(array &$data, array $parsed): void { $montzarDiff = (float) $this->config()->Config('montzar_diff'); $montzarPrice = (float) $this->config()->Config('montzar_live_price'); $totalPriceMontzar = ((int) $data['direction'] === 1) ? ($montzarPrice + $montzarDiff) : ($montzarPrice - $montzarDiff); $providerOrderId = $parsed['provider_id'] ?? null; if (isset($data['order']) && $data['order'] instanceof Order) { if ($providerOrderId !== null && Schema::hasColumn('orders', 'provider_id')) { $data['order']->provider_id = $providerOrderId; } if (Schema::hasColumn('orders', 'total_price_montzar')) { $data['order']->total_price_montzar = $totalPriceMontzar; } $data['order']->save(); } $data['total_price_montzar'] = $totalPriceMontzar; if ($providerOrderId !== null) { $data['provider_id'] = $providerOrderId; } } }