apiKey = (string) config('services.betix.key'); $this->baseUrl = rtrim((string) config('services.betix.url', 'https://originals.betix.io'), '/'); $this->timeout = (float) config('services.betix.timeout', 10.0); } // ── Public API methods ────────────────────────────────────────────────── public function games(): array { return $this->request('GET', '/operator/games'); } public function launch(array $payload): array { return $this->request('POST', '/operator/launch', $payload); } public function session(string $token): array { return $this->request('GET', "/operator/session/{$token}"); } public function endSession(string $token): array { return $this->request('POST', "/operator/session/{$token}/end"); } // ── HMAC signing ──────────────────────────────────────────────────────── /** * Verify an inbound webhook from BetiX. * Returns true if the X-Signature header matches the raw body. */ public function verifyWebhook(string $rawBody, string $signature): bool { $secret = (string) config('services.betix.webhook_secret'); $expected = hash_hmac('sha256', $rawBody, $secret); return hash_equals($expected, $signature); } // ── HTTP transport ─────────────────────────────────────────────────────── private function request(string $method, string $path, array $data = []): array { $body = $data ? json_encode($data) : ''; $ts = (string) time(); $nonce = bin2hex(random_bytes(8)); $bodyHex = bin2hex($body); $message = "{$ts}:{$nonce}:{$method}:{$path}:{$bodyHex}"; $sig = hash_hmac('sha256', $message, $this->apiKey); $url = $this->baseUrl . $path; \Illuminate\Support\Facades\Log::debug('[BetiXClient] request', [ 'method' => $method, 'url' => $url, 'body' => $body ?: '{}', 'message' => $message, ]); try { $response = Http::withHeaders([ 'X-API-Key' => $this->apiKey, 'X-Timestamp' => $ts, 'X-Nonce' => $nonce, 'X-Signature' => $sig, 'Accept' => 'application/json', 'Origin' => rtrim((string) config('app.url'), '/'), ]) ->timeout($this->timeout) ->withBody($body ?: '{}', 'application/json') ->send($method, $url); } catch (\Throwable $e) { \Illuminate\Support\Facades\Log::error('[BetiXClient] connection failed', [ 'url' => $url, 'error' => $e->getMessage(), 'exception' => get_class($e), ]); throw new RuntimeException("BetiX unreachable: {$e->getMessage()}", 0, $e); } $json = $response->json() ?? []; \Illuminate\Support\Facades\Log::debug('[BetiXClient] response', [ 'status' => $response->status(), 'body' => $response->body(), 'json' => $json, ]); if ($response->failed()) { \Illuminate\Support\Facades\Log::error('[BetiXClient] API error', [ 'status' => $response->status(), 'body' => $response->body(), ]); throw new RuntimeException($json['error'] ?? "BetiX API error {$response->status()}"); } return $json; } }