diff --git a/app/Services/SmsBilling/SuperAdminSmsClient.php b/app/Services/SmsBilling/SuperAdminSmsClient.php index 13a96f1..f2a1a63 100644 --- a/app/Services/SmsBilling/SuperAdminSmsClient.php +++ b/app/Services/SmsBilling/SuperAdminSmsClient.php @@ -3,6 +3,7 @@ namespace App\Services\SmsBilling; use Illuminate\Http\Client\Response; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; @@ -13,14 +14,27 @@ use Illuminate\Support\Facades\Log; * Env: SUPERADMIN_SMS_BASE_URL + SUPERADMIN_SMS_SHARED_SECRET * Optional: SUPERADMIN_SMS_DOMAIN (else host of APP_URL) * + * Failover: tries SUPERADMIN_SMS_BASE_URL first (if set), then the three + * known SuperAdmin hosts. Last successful base is cached and tried first next time. + * * Fallback: SUPERADMIN_REMOTE_TOKEN (per-tenant Bearer) if shared secret missing. */ class SuperAdminSmsClient { + private const SUCCESS_BASE_CACHE_KEY = 'superadmin_sms_success_base'; + + private const SUCCESS_BASE_CACHE_TTL_SECONDS = 60 * 60 * 24 * 7; + + /** @var list */ + private const DEFAULT_BASES = [ + 'https://adm.montzar.ir/api/remote/sms', + 'https://adm.montech.ir/api/remote/sms', + 'https://adm.montest.ir/api/remote/sms', + ]; + public function enabled(): bool { - $base = trim((string) config('services.superadmin.sms_base_url', '')); - if ($base === '') { + if ($this->baseUrls() === []) { return false; } @@ -182,40 +196,158 @@ class SuperAdminSmsClient return null; } - try { - $pending = Http::withHeaders($this->authHeaders($method, $canonicalPath)) - ->acceptJson() - ->timeout($timeout); + $bases = $this->orderedBaseUrls(); + $method = strtoupper($method); + $lastError = null; - $url = $this->url($relativeUrl); - $method = strtoupper($method); + foreach ($bases as $index => $base) { + try { + $pending = Http::withHeaders($this->authHeaders($method, $canonicalPath)) + ->acceptJson() + ->timeout($timeout); - /** @var Response $response */ - $response = match ($method) { - 'GET' => $pending->get($url, $query), - 'POST' => $pending->post($url, $body ?? []), - 'PUT' => $pending->put($url, $body ?? []), - 'PATCH' => $pending->patch($url, $body ?? []), - default => throw new \InvalidArgumentException("Unsupported method {$method}"), - }; + $url = $this->urlForBase($base, $relativeUrl); - if (!$response->successful()) { - Log::warning("SuperAdmin SMS {$label} failed", [ - 'status' => $response->status(), - 'body' => $response->body(), + /** @var Response $response */ + $response = match ($method) { + 'GET' => $pending->get($url, $query), + 'POST' => $pending->post($url, $body ?? []), + 'PUT' => $pending->put($url, $body ?? []), + 'PATCH' => $pending->patch($url, $body ?? []), + default => throw new \InvalidArgumentException("Unsupported method {$method}"), + }; + + if (!$response->successful()) { + $lastError = 'HTTP '.$response->status(); + Log::warning("SuperAdmin SMS {$label} failed", [ + 'base' => $base, + 'candidate' => $index + 1, + 'total' => count($bases), + 'status' => $response->status(), + 'body' => $response->body(), + ]); + + continue; + } + + $data = $response->json(); + if (!is_array($data)) { + $lastError = 'invalid JSON'; + Log::warning("SuperAdmin SMS {$label} invalid JSON", [ + 'base' => $base, + ]); + + continue; + } + + $this->rememberSuccessfulBase($base); + + return $data; + } catch (\Throwable $e) { + $lastError = $e->getMessage(); + Log::warning("SuperAdmin SMS {$label} exception", [ + 'base' => $base, + 'candidate' => $index + 1, + 'total' => count($bases), + 'error' => $e->getMessage(), ]); + } + } + Log::warning("SuperAdmin SMS {$label} unavailable on all bases", [ + 'bases' => $bases, + 'last_error' => $lastError, + ]); + + return null; + } + + /** + * @return list + */ + private function orderedBaseUrls(): array + { + $bases = $this->baseUrls(); + if ($bases === []) { + return []; + } + + $sticky = $this->readSuccessfulBase(); + if ($sticky === null || !in_array($sticky, $bases, true)) { + return $bases; + } + + return array_values(array_unique(array_merge([$sticky], $bases))); + } + + /** + * Env base (if set) first, then the three known SuperAdmin hosts. + * No .env change required when SUPERADMIN_SMS_BASE_URL already points at montzar. + * + * @return list + */ + private function baseUrls(): array + { + $out = []; + + $fromEnv = trim((string) config('services.superadmin.sms_base_url', '')); + if ($fromEnv !== '') { + foreach (preg_split('/[\s,;]+/', $fromEnv) ?: [] as $piece) { + $normalized = $this->normalizeBaseUrl((string) $piece); + if ($normalized !== '') { + $out[] = $normalized; + } + } + } + + foreach (self::DEFAULT_BASES as $default) { + $out[] = $this->normalizeBaseUrl($default); + } + + return array_values(array_unique(array_filter($out))); + } + + private function normalizeBaseUrl(string $value): string + { + $base = rtrim(trim($value), '/'); + if ($base === '') { + return ''; + } + + // Accept origin-only values like https://adm.montzar.ir + if (!str_contains($base, '/api/remote/sms')) { + $base .= '/api/remote/sms'; + } + + return rtrim($base, '/'); + } + + private function urlForBase(string $base, string $path): string + { + return rtrim($base, '/').'/'.ltrim($path, '/'); + } + + private function rememberSuccessfulBase(string $base): void + { + try { + Cache::put(self::SUCCESS_BASE_CACHE_KEY, $base, self::SUCCESS_BASE_CACHE_TTL_SECONDS); + } catch (\Throwable $e) { + Log::debug('SuperAdmin SMS sticky base cache write failed', [ + 'error' => $e->getMessage(), + ]); + } + } + + private function readSuccessfulBase(): ?string + { + try { + $cached = Cache::get(self::SUCCESS_BASE_CACHE_KEY); + if (!is_string($cached) || trim($cached) === '') { return null; } - $data = $response->json(); - - return is_array($data) ? $data : null; + return $this->normalizeBaseUrl($cached); } catch (\Throwable $e) { - Log::warning("SuperAdmin SMS {$label} exception", [ - 'error' => $e->getMessage(), - ]); - return null; } } @@ -280,11 +412,4 @@ class SuperAdminSmsClient return $host; } - - private function url(string $path): string - { - $base = rtrim((string) config('services.superadmin.sms_base_url', ''), '/'); - - return $base.'/'.ltrim($path, '/'); - } } diff --git a/config/services.php b/config/services.php index 573eb8d..ba9ef15 100644 --- a/config/services.php +++ b/config/services.php @@ -42,7 +42,9 @@ return [ ], /* - | SuperAdmin SMS wallet (source of truth when both env vars are set). + | SuperAdmin SMS wallet (source of truth when secret/token is set). + | SUPERADMIN_SMS_BASE_URL is optional preferred host; client also fails over to + | adm.montzar.ir / adm.montech.ir / adm.montest.ir automatically (no .env change needed). | Example: SUPERADMIN_SMS_BASE_URL=https://adm.montzar.ir/api/remote/sms */ 'superadmin' => [