addMinutes(self::CACHE_TTL_MINUTES), function () { return $this->fetchRateFromBase('USD') ?? self::FALLBACK_RATE; }); } public function getEurToGhsRate(): float { return Cache::remember(self::CACHE_KEY_EUR, now()->addMinutes(self::CACHE_TTL_MINUTES), function () { return $this->fetchRateFromBase('EUR') ?? self::FALLBACK_EUR_RATE; }); } /** * Convert USD amount to GHS. */ public function convertUsdToGhs(float $usdAmount): float { return $usdAmount * $this->getUsdToGhsRate(); } /** * Convert USD to GHS with markup percentage. */ public function convertWithMarkup(float $usdAmount, float $markupPercent = 35): float { $ghsAmount = $this->convertUsdToGhs($usdAmount); return $ghsAmount * (1 + ($markupPercent / 100)); } /** * Convert USD cents to GHS pesewas with markup. */ public function convertCentsTopesewaswithMarkup(int $usdCents, float $markupPercent = 35): int { $usdAmount = $usdCents / 100; $ghsAmount = $this->convertWithMarkup($usdAmount, $markupPercent); return (int) round($ghsAmount * 100); } private function fetchRateFromBase(string $base): ?float { $rate = $this->fetchFromExchangeRateApi($base) ?? $this->fetchFromFreeCurrencyApi($base) ?? $this->fetchFromOpenExchangeRates($base); if ($rate !== null) { Log::info('Currency rate fetched', ['base' => $base, 'ghs' => $rate]); } return $rate; } private function fetchFromExchangeRateApi(string $base): ?float { try { $response = Http::timeout(5)->get("https://api.exchangerate-api.com/v4/latest/{$base}"); if ($response->successful()) { $data = $response->json(); return (float) ($data['rates']['GHS'] ?? null) ?: null; } } catch (\Throwable $e) { Log::warning('ExchangeRateApi fetch failed', ['base' => $base, 'error' => $e->getMessage()]); } return null; } private function fetchFromFreeCurrencyApi(string $base): ?float { $apiKey = config('services.freecurrencyapi.key'); if (empty($apiKey)) { return null; } try { $response = Http::timeout(5)->get('https://api.freecurrencyapi.com/v1/latest', [ 'apikey' => $apiKey, 'base_currency' => $base, 'currencies' => 'GHS', ]); if ($response->successful()) { $data = $response->json(); return (float) ($data['data']['GHS'] ?? null) ?: null; } } catch (\Throwable $e) { Log::warning('FreeCurrencyApi fetch failed', ['base' => $base, 'error' => $e->getMessage()]); } return null; } private function fetchFromOpenExchangeRates(string $base): ?float { $appId = config('services.openexchangerates.app_id'); if (empty($appId)) { return null; } // Free tier only supports USD as base if ($base !== 'USD') { return null; } try { $response = Http::timeout(5)->get('https://openexchangerates.org/api/latest.json', [ 'app_id' => $appId, 'symbols' => 'GHS', ]); if ($response->successful()) { $data = $response->json(); return (float) ($data['rates']['GHS'] ?? null) ?: null; } } catch (\Throwable $e) { Log::warning('OpenExchangeRates fetch failed', ['base' => $base, 'error' => $e->getMessage()]); } return null; } /** * Force refresh the cached rate. */ public function refreshRate(): float { Cache::forget(self::CACHE_KEY); Cache::forget(self::CACHE_KEY_EUR); return $this->getUsdToGhsRate(); } /** * Get the current cached rate info. */ public function getRateInfo(): array { return [ 'rate' => $this->getUsdToGhsRate(), 'from' => 'USD', 'to' => 'GHS', 'cached' => Cache::has(self::CACHE_KEY), 'fallback' => self::FALLBACK_RATE, ]; } }