$filePath]); return []; } try { $response = Http::withHeaders(['X-Auth-Key' => $apiKey]) ->timeout(30) ->attach('document', $contents, basename($filePath)) ->post(self::ENDPOINT, ['template' => 'financial_full']); if ($response->failed()) { Log::warning('Klippa OCR request failed.', ['status' => $response->status()]); return []; } $parsed = $response->json('data.parsed', []); return array_filter([ 'title' => $this->extractTitle($parsed), 'expires_at' => $this->extractDate($parsed), ], fn ($value) => $value !== null); } catch (Throwable $e) { Log::warning('Klippa OCR analysis threw an exception.', ['message' => $e->getMessage()]); return []; } } /** * @param array $parsed */ private function extractTitle(array $parsed): ?string { foreach (['document_subject', 'merchant_name', 'title'] as $key) { $value = data_get($parsed, $key); if (filled($value) && is_string($value)) { return trim($value); } } return null; } /** * @param array $parsed */ private function extractDate(array $parsed): ?string { foreach (['expiry_date', 'valid_until', 'date'] as $key) { $value = data_get($parsed, $key); if (filled($value) && is_string($value)) { try { return CarbonImmutable::parse($value)->toDateString(); } catch (Throwable) { continue; } } } return null; } }