From 82691cdf2c149fbbb2bf54df487d39a2a117dea5 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 12 Jul 2026 22:13:46 +0330 Subject: [PATCH] added zarbox link alternate --- app/Services/ZaarboxPriceService.php | 128 ++++++++++++++++++++----- database/sql/deploy_settings_const.sql | 2 +- 2 files changed, 105 insertions(+), 25 deletions(-) diff --git a/app/Services/ZaarboxPriceService.php b/app/Services/ZaarboxPriceService.php index e242766..3e18912 100644 --- a/app/Services/ZaarboxPriceService.php +++ b/app/Services/ZaarboxPriceService.php @@ -46,17 +46,99 @@ class ZaarboxPriceService public function apiUrl(): string { - $url = trim((string) $this->config->getString( - 'zaarbox_api_url', - (string) config('price_providers.zaarbox_api_url', 'https://price.zaarbox.ir/api/free') + $candidates = $this->feedUrlCandidates(); + + return $candidates[0] ?? 'https://price.zaarbox.ir/api/free'; + } + + /** + * آدرس‌های فید `/api/free` به ترتیب failover — از کلید zaarbox_api_url. + * مقدار می‌تواند تک‌URL (legacy) یا JSON آرایه/لیست جداشده با کاما/خط جدید باشد (تا ۵ مورد). + * هر مورد به مسیر /api/free نرمال می‌شود. + * + * @return array + */ + public function feedUrlCandidates(): array + { + $raw = trim((string) $this->config->getString('zaarbox_api_url', '')); + + $urls = []; + + if ($raw !== '') { + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + foreach ($decoded as $item) { + $value = is_array($item) ? ($item['url'] ?? '') : $item; + $urls[] = trim((string) $value); + } + } else { + foreach (preg_split('/[\r\n,]+/', $raw) ?: [] as $item) { + $urls[] = trim((string) $item); + } + } + } + + $candidates = array_values(array_filter( + array_map(fn (string $u) => $this->normalizeFeedUrl($u), $urls), + fn (string $u) => $u !== '' )); - return $url !== '' ? $url : 'https://price.zaarbox.ir/api/free'; + $candidates = array_values(array_unique($candidates)); + + if ($candidates === []) { + $fallback = trim((string) config('price_providers.zaarbox_api_url', 'https://price.zaarbox.ir/api/free')); + $candidates[] = $this->normalizeFeedUrl($fallback !== '' ? $fallback : 'https://price.zaarbox.ir/api/free'); + } + + return array_slice($candidates, 0, 5); + } + + /** نرمال‌سازی به آدرس فید — اگر مسیر /api/free نداشت اضافه می‌شود */ + private function normalizeFeedUrl(string $url): string + { + $url = rtrim(trim($url), '/'); + if ($url === '') { + return ''; + } + + if (preg_match('~/api/free$~i', $url)) { + return $url; + } + + return $url . '/api/free'; + } + + /** + * دریافت فید با failover — اولین سروری که آرایهٔ معتبر برگرداند. + * + * @return array>|null + */ + private function fetchFeedWithFailover(): ?array + { + foreach ($this->feedUrlCandidates() as $url) { + try { + $prices = Http::withoutVerifying() + ->timeout(12) + ->get($url) + ->json(); + + if (is_array($prices) && $prices !== []) { + return $prices; + } + } catch (\Throwable $e) { + Log::warning('Partner feed url failed, trying next', [ + 'url' => $url, + 'message' => $e->getMessage(), + ]); + } + } + + return null; } public function hasApiUrlConfigured(): bool { - return trim($this->config->getString('zaarbox_api_url', '')) !== ''; + return $this->feedUrlCandidates() !== []; } /** کلید یا id ردیف انس در JSON تابلو (settings: zaarbox_ounce_key) */ @@ -305,10 +387,19 @@ class ZaarboxPriceService return [ 'price' => round((float) $rawPrice, 2), - 'label' => trim((string) ($ounce['name'] ?? 'اونس طلا')) ?: 'اونس طلا', + 'label' => self::formatOunceDisplayLabel((string) ($ounce['name'] ?? '')), ]; } + private static function formatOunceDisplayLabel(string $name): string + { + $label = trim($name); + $label = preg_replace('/\s+tala\.ir\s*$/iu', '', $label) ?? $label; + $label = trim($label); + + return $label !== '' ? $label : 'اونس طلا'; + } + /** * @param array $payload * @return array @@ -332,10 +423,7 @@ class ZaarboxPriceService } try { - $prices = Http::withoutVerifying() - ->timeout(20) - ->get($this->apiUrl()) - ->json(); + $prices = $this->fetchFeedWithFailover(); if (!is_array($prices)) { return $empty; @@ -364,18 +452,7 @@ class ZaarboxPriceService /** @return array>|null */ public function fetchPriceList(): ?array { - try { - $prices = Http::withoutVerifying() - ->timeout(20) - ->get($this->apiUrl()) - ->json(); - - return is_array($prices) ? $prices : null; - } catch (\Throwable $e) { - Log::warning('Zaarbox price list fetch failed', ['message' => $e->getMessage()]); - - return null; - } + return $this->fetchFeedWithFailover(); } /** @@ -457,8 +534,11 @@ class ZaarboxPriceService } try { - $url = $apiUrl ?: $this->apiUrl(); - $prices = Http::withoutVerifying()->timeout(20)->get($url)->json(); + if ($apiUrl !== null && trim($apiUrl) !== '') { + $prices = Http::withoutVerifying()->timeout(12)->get($apiUrl)->json(); + } else { + $prices = $this->fetchFeedWithFailover(); + } if (!is_array($prices)) { return $empty; } diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index 7eb6bfb..bc8ef02 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -1126,7 +1126,7 @@ UPDATE `settings` SET `description` = 'شناسه یکتا (UUID) نمودار UPDATE `settings` SET `description` = 'منبع دریافت قیمت لحظه‌ای برای اپ کاربر و همگام‌سازی محصولات|montzar,zaarbox,main_product' WHERE `key` = 'price_provider'; UPDATE `settings` SET `description` = 'شناسه عددی یا نام reading_method محصول آبشده مرجع. وقتی منبع قیمت روی main_product باشد برای محاسبه استفاده می‌شود.' WHERE `key` = 'main_product'; -UPDATE `settings` SET `description` = 'آدرس پایه API سرویس تابلو آنلاین (بدون مسیر انتهایی). مثال: https://example.com/api' WHERE `key` = 'zaarbox_api_url'; +UPDATE `settings` SET `description` = 'آدرس سرور(های) فید قیمت (/api/free). تا ۵ آدرس (JSON آرایه) پشتیبانی می‌شود؛ اگر آدرس اول پاسخ ندهد به‌ترتیب سراغ بعدی می‌رود. مقدار تکی قبلی حفظ می‌شود.' WHERE `key` = 'zaarbox_api_url'; UPDATE `settings` SET `description` = 'کلید یا UUID ردیف قیمت در پاسخ API تابلو. باید با محصول/ردیف موردنظر در سرویس خارجی یکسان باشد.' WHERE `key` = 'zaarbox_price_key'; UPDATE `settings` SET `description` = 'روشن: اگر API قیمت در دسترس نباشد، آخرین قیمت ذخیره‌شده در دیتابیس به کاربر نشان داده می‌شود. خاموش: در قطعی API ممکن است قیمت صفر نمایش داده شود.' WHERE `key` = 'show_last_price'; UPDATE `settings` SET `description` = 'روشن: اگر آدرس API تابلو (zaarbox_api_url) تنظیم شده باشد، قیمت انس طلا در اپ مشتری نمایش داده می‌شود. خاموش: نمایش داده نمی‌شود.' WHERE `key` = 'show_gold_ounce_price';