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 { $data = $this->requestJson('GET', '/api/remote/sms/status', 'status', [ 'parts' => max(1, $parts), 'isAlert' => $isAlert ? '1' : '0', ], null, 4, 'status'); if ($data === null) { 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'), ]; } /** * @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 { $data = $this->requestJson('POST', '/api/remote/sms/consume', 'consume', [], [ 'receiver' => $receiver, 'body' => mb_substr($body, 0, 1000), 'parts' => max(1, $parts), 'provider' => $provider, 'source' => $source, 'isAlert' => $isAlert, ], 6, 'consume'); if ($data === null || !($data['ok'] ?? false)) { return null; } return [ 'ok' => true, 'balanceRials' => (int) ($data['balanceRials'] ?? 0), 'costRials' => (int) ($data['costRials'] ?? 0), 'pricePerPartRials' => (int) ($data['pricePerPartRials'] ?? 0), ]; } public function overview(): ?array { return $this->requestJson('GET', '/api/remote/sms/overview', 'overview', [], null, 6, 'overview'); } public function balance(): ?array { return $this->requestJson('GET', '/api/remote/sms/balance', 'balance', [], null, 4, 'balance'); } public function chargeInfo(): ?array { return $this->requestJson('GET', '/api/remote/sms/charge-info', 'charge-info', [], null, 4, 'charge-info'); } public function updateChargeInfo(array $payload): ?array { return $this->requestJson('PUT', '/api/remote/sms/charge-info', 'charge-info', [], $payload, 6, 'charge-info-update'); } public function charge(int $amount, ?string $note = null): ?array { return $this->requestJson('POST', '/api/remote/sms/charge', 'charge', [], [ 'amount' => $amount, 'note' => $note, ], 8, 'charge'); } public function updatePrice(int $pricePerPart): ?array { return $this->requestJson('PUT', '/api/remote/sms/price', 'price', [], [ 'price_per_part' => max(0, $pricePerPart), ], 6, 'price'); } public function setBlocked(bool $isBlocked): ?array { return $this->requestJson('PUT', '/api/remote/sms/block', 'block', [], [ 'is_blocked' => $isBlocked, ], 6, 'block'); } public function logsTable(array $payload): ?array { return $this->requestJson('POST', '/api/remote/sms/logs/table', 'logs/table', [], $payload, 10, 'logs-table'); } public function ledgerTable(array $payload): ?array { return $this->requestJson('POST', '/api/remote/sms/ledger/table', 'ledger/table', [], $payload, 10, 'ledger-table'); } public function report(?string $fromDate = null, ?string $toDate = null): ?array { $query = array_filter([ 'from_date' => $fromDate, 'to_date' => $toDate, ], static fn ($v) => $v !== null && $v !== ''); return $this->requestJson('GET', '/api/remote/sms/report', 'report', $query, null, 8, 'report'); } public function chargeReport(?string $fromDate = null, ?string $toDate = null): ?array { $query = array_filter([ 'from_date' => $fromDate, 'to_date' => $toDate, ], static fn ($v) => $v !== null && $v !== ''); return $this->requestJson('GET', '/api/remote/sms/report/charges', 'report/charges', $query, null, 8, 'charge-report'); } /** * @param array $query * @param array|null $body * @return array|null */ private function requestJson( string $method, string $canonicalPath, string $relativeUrl, array $query = [], ?array $body = null, int $timeout = 6, string $label = 'request' ): ?array { if (!$this->enabled()) { return null; } try { $pending = Http::withHeaders($this->authHeaders($method, $canonicalPath)) ->acceptJson() ->timeout($timeout); $url = $this->url($relativeUrl); $method = strtoupper($method); /** @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()) { Log::warning("SuperAdmin SMS {$label} failed", [ 'status' => $response->status(), 'body' => $response->body(), ]); return null; } $data = $response->json(); return is_array($data) ? $data : null; } catch (\Throwable $e) { Log::warning("SuperAdmin SMS {$label} 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, '/'); } }