sharedSecret() !== '' || $this->bearerToken() !== ''; } /** * @return array{ * allowed: bool, * blocked: bool, * reason: ?string, * balanceRials: int, * pricePerPartRials: int, * estimatedCostRials: int, * balanceLevel: string * }|null */ public function status(int $parts = 1, bool $isAlert = false): ?array { if (! $this->enabled()) { return null; } try { $path = '/api/remote/sms/status'; $response = Http::withHeaders($this->authHeaders('GET', $path)) ->acceptJson() ->timeout(4) ->get($this->url('status'), [ 'parts' => max(1, $parts), 'isAlert' => $isAlert ? '1' : '0', ]); if (! $response->successful()) { Log::warning('SuperAdmin SMS status failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); return null; } $data = $response->json(); if (! is_array($data)) { return null; } return [ 'allowed' => (bool) ($data['allowed'] ?? false), 'blocked' => (bool) ($data['blocked'] ?? false), 'reason' => isset($data['reason']) ? (string) $data['reason'] : null, 'balanceRials' => (int) ($data['balanceRials'] ?? 0), 'pricePerPartRials' => (int) ($data['pricePerPartRials'] ?? 0), 'estimatedCostRials' => (int) ($data['estimatedCostRials'] ?? 0), 'balanceLevel' => (string) ($data['balanceLevel'] ?? 'unknown'), ]; } catch (\Throwable $e) { Log::warning('SuperAdmin SMS status exception', [ 'error' => $e->getMessage(), ]); return null; } } /** * @return array{ok: bool, balanceRials?: int, costRials?: int}|null */ public function consume( string $receiver, string $body, int $parts, ?string $provider = null, ?string $source = null, bool $isAlert = false ): ?array { if (! $this->enabled()) { return null; } try { $path = '/api/remote/sms/consume'; $payload = [ 'receiver' => $receiver, 'body' => mb_substr($body, 0, 1000), 'parts' => max(1, $parts), 'provider' => $provider, 'source' => $source, 'isAlert' => $isAlert, ]; $response = Http::withHeaders($this->authHeaders('POST', $path)) ->acceptJson() ->timeout(6) ->post($this->url('consume'), $payload); if (! $response->successful()) { Log::error('SuperAdmin SMS consume failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); return null; } $data = $response->json(); if (! is_array($data) || ! ($data['ok'] ?? false)) { return null; } return [ 'ok' => true, 'balanceRials' => (int) ($data['balanceRials'] ?? 0), 'costRials' => (int) ($data['costRials'] ?? 0), ]; } catch (\Throwable $e) { Log::error('SuperAdmin SMS consume exception', [ 'error' => $e->getMessage(), ]); return null; } } /** * @return array */ private function authHeaders(string $method, string $canonicalPath): array { $secret = $this->sharedSecret(); if ($secret !== '') { $domain = $this->domain(); $timestamp = (string) time(); $payload = $timestamp.'.'.strtoupper($method).'.'.$canonicalPath.'.'.$domain; $signature = hash_hmac('sha256', $payload, $secret); return [ 'X-Abshode-Domain' => $domain, 'X-Abshode-Timestamp' => $timestamp, 'X-Abshode-Signature' => $signature, ]; } $token = $this->bearerToken(); if ($token !== '') { return [ 'Authorization' => 'Bearer '.$token, ]; } return []; } private function sharedSecret(): string { return trim((string) config('services.superadmin.sms_shared_secret', '')); } private function bearerToken(): string { return trim((string) config('services.superadmin.remote_token', '')); } private function domain(): string { $override = trim((string) config('services.superadmin.sms_domain', '')); if ($override !== '') { return $this->normalizeHost($override); } $fromApp = parse_url((string) config('app.url'), PHP_URL_HOST); return $this->normalizeHost(is_string($fromApp) ? $fromApp : ''); } private function normalizeHost(string $value): string { $host = strtolower(trim($value)); $host = preg_replace('#^https?://#', '', $host) ?? $host; $host = explode('/', $host)[0] ?? $host; $host = explode(':', $host)[0] ?? $host; return $host; } private function url(string $path): string { $base = rtrim((string) config('services.superadmin.sms_base_url', ''), '/'); return $base.'/'.ltrim($path, '/'); } }