<?php
 namespace App\Jobs; use App\Core\Currency\CurrencyDriverManager; use App\Core\Hook\HookManager; use App\Models\Currency; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; class UpdateCurrencyRates implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 3; public $backoff = 60; public function handle(CurrencyDriverManager $manager): void { $defaultCurrency = Currency::where('is_default', true)->first(); if (! $defaultCurrency) { Log::error('UpdateCurrencyRates: no default currency configured'); return; } $base = strtoupper($defaultCurrency->code); $order = $manager->failoverOrder(); if (empty($order)) { Log::error('UpdateCurrencyRates: no fx providers configured'); return; } $rates = null; $usedProvider = null; $failures = []; foreach ($order as $name) { try { $driver = $manager->driver($name); $rates = $driver->fetch($base); $usedProvider = $name; Log::info("UpdateCurrencyRates: provider {$name} succeeded ({$base} base, ".count($rates).' rates)'); break; } catch (\Throwable $e) { $failures[$name] = $e->getMessage(); Log::warning("UpdateCurrencyRates: provider {$name} failed — ".$e->getMessage()); } } if ($rates === null) { Log::error('UpdateCurrencyRates: all providers failed', $failures); $this->fail(new \RuntimeException('All FX providers failed')); return; } $rates = app(HookManager::class)->filter('FetchCurrencyExchangeRates', $rates); $this->persistRates($rates, $usedProvider, $defaultCurrency); Cache::put('currency_rates_last_updated', now()->toDateTimeString(), now()->addDays(30)); Cache::put('currency_rates_last_provider', $usedProvider, now()->addDays(30)); } protected function persistRates(array $rates, string $provider, Currency $defaultCurrency): void { $currencies = Currency::where('status', true) ->where('id', '!=', $defaultCurrency->id) ->get(); $updated = 0; $skipped = 0; $hasLastUpdated = Schema::hasColumn('currencies', 'last_updated_at'); $hasLastProvider = Schema::hasColumn('currencies', 'last_provider'); $hasLogTable = Schema::hasTable('currency_rate_logs'); foreach ($currencies as $currency) { if (! isset($rates[$currency->code])) { $skipped++; continue; } $newRate = (float) $rates[$currency->code]; if ((float) $currency->rate === $newRate) { $skipped++; continue; } $currency->rate = $newRate; if ($hasLastUpdated) { $currency->last_updated_at = now(); } if ($hasLastProvider) { $currency->last_provider = $provider; } $currency->save(); $updated++; if ($hasLogTable) { DB::table('currency_rate_logs')->insert([ 'currency_code' => $currency->code, 'rate' => $newRate, 'provider' => $provider, 'fetched_at' => now(), ]); } } Log::info("UpdateCurrencyRates done: updated={$updated} skipped={$skipped} provider={$provider}"); } public function failed(\Throwable $exception): void { Log::error('UpdateCurrencyRates failed: '.$exception->getMessage()); } } 