environment('testing')) { // In tests, prefer the services config so test fakes using config('services.backend.base') match // IMPORTANT: If config('services.backend.base') contains /api at the end, test fakes often omit it. // We'll strip /api from the end of the base URL to match standard Http::fake() patterns in this project. $base = (string) ($serviceUrl ?: ($envUrl ?: ($appUrl ?: 'http://casinoapi.test'))); $this->base = rtrim(preg_replace('/\/api\/?$/', '', $base), '/'); } else { // In non-testing, prefer explicit ENV override, then services config, then app config $this->base = rtrim((string) ($envUrl ?: ($serviceUrl ?: ($appUrl ?: 'http://casinoapi.test/api'))), '/'); } $this->token = (string) config('services.backend.token'); $this->timeout = (float) config('services.backend.timeout', 8.0); } /** * Issue a GET request with standard headers and optional retry policy. */ public function get(HttpRequest $request, string $path, array $query = [], bool $retry = true) { $req = $this->baseRequest($request)->acceptJson(); if ($retry) { // Retry 2x on timeout/5xx $req = $req->retry(2, 100, throw: false); } return $req->get($this->url($path), $query); } /** * Issue a POST request with standard headers (no retries by default). */ public function post(HttpRequest $request, string $path, array $data = []) { return $this->baseRequest($request)->acceptJson()->post($this->url($path), $data); } public function patch(HttpRequest $request, string $path, array $data = []) { return $this->baseRequest($request)->acceptJson()->patch($this->url($path), $data); } public function delete(HttpRequest $request, string $path, array $data = []) { return $this->baseRequest($request)->acceptJson()->asForm()->delete($this->url($path), $data); } /** * Multipart POST (file upload) with standard headers. */ public function postMultipart(HttpRequest $request, string $path, array $fields, $file, string $fileField = 'file', ?string $filename = null) { $pending = $this->baseRequest($request); if ($file) { $stream = fopen($file->getRealPath(), 'r'); $pending = $pending->attach($fileField, $stream, $filename ?: $file->getClientOriginalName()); } foreach ($fields as $k => $v) { if ($v === null) continue; $pending = $pending->asMultipart(); } return $pending->post($this->url($path), $fields); } private function url(string $path): string { return $this->base . '/' . ltrim($path, '/'); } private function baseRequest(HttpRequest $request): PendingRequest { $user = $request->user(); $userId = $user?->id ? (string) $user->id : ''; $ua = (string) $request->header('User-Agent', ''); $sid = (string) $request->session()->getId(); $deviceHash = hash('sha256', $ua . '|' . $sid); $ipHash = hash('sha256', (string) $request->ip()); return Http::withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'X-User-Id' => $userId, 'X-User-Device' => $deviceHash, 'X-User-IP' => $ipHash, 'Accept' => 'application/json', ])->timeout($this->timeout); } }