> Keyed by "{currency}:{date}" */ private array $rateCache = []; /** * Convert a quantity from one currency to another on a given date. * * @param string $source Source currency code (e.g., "btc", "eth", "usd") * @param string $target Target currency code (e.g., "eur", "usd") * @param float $quantity Amount to convert * @param string $date Date string (YYYY-MM-DD) or "latest" */ public function convert(string $source, string $target, float $quantity, string $date = 'latest'): float { $source = strtolower($source); $target = strtolower($target); if ($source === $target) { return $quantity; } $rates = $this->getRatesForCurrency($target, $date); if (! isset($rates[$source]) || $rates[$source] == 0) { Log::debug('Currency rate not found', [ 'source' => $source, 'target' => $target, 'date' => $date, ]); return 0.0; } return $quantity / $rates[$source]; } /** * Fetch all rates for a base currency on a given date. * * Returns a map of currency code => rate relative to the base currency. * Results are cached in-memory for the duration of the request. * * @return array */ public function getRatesForCurrency(string $currency, string $date): array { $currency = strtolower($currency); $cacheKey = "{$currency}:{$date}"; if (isset($this->rateCache[$cacheKey])) { return $this->rateCache[$cacheKey]; } $rates = $this->fetchRates($currency, $date); $this->rateCache[$cacheKey] = $rates; return $rates; } /** * Fetch rates from CDN with fallback. * * @return array */ private function fetchRates(string $currency, string $date): array { $primaryUrl = self::PRIMARY_URL."{$date}/v1/currencies/{$currency}.min.json"; $fallbackUrl = self::FALLBACK_URL."{$date}/currencies/{$currency}.min.json"; try { $response = Http::timeout(10)->get($primaryUrl); $response->throw(); return $response->json($currency) ?? []; } catch (\Throwable) { // Primary failed, try fallback } try { $response = Http::timeout(10)->get($fallbackUrl); $response->throw(); return $response->json($currency) ?? []; } catch (\Throwable $e) { throw new RuntimeException("Failed to fetch currency rates for {$currency} on {$date}: {$e->getMessage()}", 0, $e); } } }