Merge branch 'main' of https://get.feep.ir/khaneghi_abshodeh/khaneghi_server
This commit is contained in:
@@ -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<string> */
|
||||
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<string>
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
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, '/');
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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' => [
|
||||
|
||||
Reference in New Issue
Block a user