From 85b62dfdf839e00dfa856e2c113c392c1ff308ff Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Fri, 3 Apr 2026 20:24:03 +0200 Subject: [PATCH] Refactor exchange rate providers into driver-based architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace duplicated switch/case blocks across 4 methods with a clean abstract driver pattern: - ExchangeRateDriver (abstract): defines getExchangeRate(), getSupportedCurrencies(), validateConnection() - CurrencyFreakDriver, CurrencyLayerDriver, OpenExchangeRateDriver, CurrencyConverterDriver: concrete implementations - ExchangeRateDriverFactory: resolves driver name to class, with register() method for module extensibility Delete ExchangeRateProvidersTrait — all logic now lives in driver classes and ExchangeRateProviderService. Adding a new exchange rate provider only requires implementing ExchangeRateDriver and calling ExchangeRateDriverFactory::register() in a module service provider. --- .../ExchangeRateProviderController.php | 27 ++- .../ExchangeRate/CurrencyConverterDriver.php | 57 +++++ .../ExchangeRate/CurrencyFreakDriver.php | 54 +++++ .../ExchangeRate/CurrencyLayerDriver.php | 50 ++++ .../ExchangeRate/ExchangeRateDriver.php | 34 +++ .../ExchangeRateDriverFactory.php | 43 ++++ .../ExchangeRate/ExchangeRateException.php | 11 + .../ExchangeRate/OpenExchangeRateDriver.php | 52 +++++ app/Services/ExchangeRateProviderService.php | 93 +++----- app/Traits/ExchangeRateProvidersTrait.php | 213 ------------------ 10 files changed, 354 insertions(+), 280 deletions(-) create mode 100644 app/Services/ExchangeRate/CurrencyConverterDriver.php create mode 100644 app/Services/ExchangeRate/CurrencyFreakDriver.php create mode 100644 app/Services/ExchangeRate/CurrencyLayerDriver.php create mode 100644 app/Services/ExchangeRate/ExchangeRateDriver.php create mode 100644 app/Services/ExchangeRate/ExchangeRateDriverFactory.php create mode 100644 app/Services/ExchangeRate/ExchangeRateException.php create mode 100644 app/Services/ExchangeRate/OpenExchangeRateDriver.php delete mode 100644 app/Traits/ExchangeRateProvidersTrait.php diff --git a/app/Http/Controllers/Company/ExchangeRate/ExchangeRateProviderController.php b/app/Http/Controllers/Company/ExchangeRate/ExchangeRateProviderController.php index a2ca92f2..c78f0086 100644 --- a/app/Http/Controllers/Company/ExchangeRate/ExchangeRateProviderController.php +++ b/app/Http/Controllers/Company/ExchangeRate/ExchangeRateProviderController.php @@ -15,15 +15,12 @@ use App\Models\Invoice; use App\Models\Payment; use App\Models\Tax; use App\Services\ExchangeRateProviderService; -use App\Traits\ExchangeRateProvidersTrait; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Arr; class ExchangeRateProviderController extends Controller { - use ExchangeRateProvidersTrait; - public function __construct( private readonly ExchangeRateProviderService $exchangeRateProviderService, ) {} @@ -158,22 +155,28 @@ class ExchangeRateProviderController extends Controller ->get() ->toArray(); - $exchange_rate = ExchangeRateLog::where('base_currency_id', $currency->id) + $exchangeRate = ExchangeRateLog::where('base_currency_id', $currency->id) ->where('currency_id', $baseCurrency->id) ->orderBy('created_at', 'desc') ->value('exchange_rate'); if ($query) { $filter = Arr::only($query[0], ['key', 'driver', 'driver_config']); - $exchange_rate_value = $this->getExchangeRate($filter, $currency->code, $baseCurrency->code); + $result = $this->exchangeRateProviderService->getExchangeRate( + $filter['driver'], + $filter['key'], + $filter['driver_config'] ?? [], + $currency->code, + $baseCurrency->code + ); - if ($exchange_rate_value->status() == 200) { - return $exchange_rate_value; + if ($result->status() == 200) { + return $result; } } - if ($exchange_rate) { + if ($exchangeRate) { return response()->json([ - 'exchangeRate' => [$exchange_rate], + 'exchangeRate' => [$exchangeRate], ], 200); } @@ -186,7 +189,11 @@ class ExchangeRateProviderController extends Controller { $this->authorize('viewAny', ExchangeRateProvider::class); - return $this->getSupportedCurrencies($request); + return $this->exchangeRateProviderService->getSupportedCurrencies( + $request->driver, + $request->key, + $request->driver_config ?? [] + ); } public function usedCurrencies(Request $request) diff --git a/app/Services/ExchangeRate/CurrencyConverterDriver.php b/app/Services/ExchangeRate/CurrencyConverterDriver.php new file mode 100644 index 00000000..cdd9fb36 --- /dev/null +++ b/app/Services/ExchangeRate/CurrencyConverterDriver.php @@ -0,0 +1,57 @@ +getBaseUrl(); + $query = "{$baseCurrency}_{$targetCurrency}"; + $url = "{$baseUrl}/api/v7/convert?apiKey={$this->apiKey}&q={$query}&compact=y"; + $response = Http::get($url)->json(); + + return array_values($response[$query]); + } + + public function getSupportedCurrencies(): array + { + $baseUrl = $this->getBaseUrl(); + $url = "{$baseUrl}/api/v7/currencies?apiKey={$this->apiKey}"; + $response = Http::get($url)->json(); + + if ($response == null) { + throw new ExchangeRateException('Server not responding', 'server_error'); + } + + if (array_key_exists('results', $response)) { + return array_keys($response['results']); + } + + throw new ExchangeRateException('Please Enter Valid Provider Key.', 'invalid_key'); + } + + public function validateConnection(): array + { + $baseUrl = $this->getBaseUrl(); + $query = 'INR_USD'; + $url = "{$baseUrl}/api/v7/convert?apiKey={$this->apiKey}&q={$query}&compact=y"; + $response = Http::get($url)->json(); + + return array_values($response[$query]); + } + + private function getBaseUrl(): string + { + $type = $this->config['type'] ?? 'FREE'; + + return match ($type) { + 'PREMIUM' => 'https://api.currconv.com', + 'PREPAID' => 'https://prepaid.currconv.com', + 'FREE' => 'https://free.currconv.com', + 'DEDICATED' => $this->config['url'] ?? 'https://free.currconv.com', + }; + } +} diff --git a/app/Services/ExchangeRate/CurrencyFreakDriver.php b/app/Services/ExchangeRate/CurrencyFreakDriver.php new file mode 100644 index 00000000..4d5db7b7 --- /dev/null +++ b/app/Services/ExchangeRate/CurrencyFreakDriver.php @@ -0,0 +1,54 @@ +baseUrl}/latest?apikey={$this->apiKey}&symbols={$targetCurrency}&base={$baseCurrency}"; + $response = Http::get($url)->json(); + + if (array_key_exists('success', $response) && $response['success'] == false) { + throw new ExchangeRateException($response['error']['message'], 'provider_error'); + } + + return array_values($response['rates']); + } + + public function getSupportedCurrencies(): array + { + $url = "{$this->baseUrl}/currency-symbols"; + $response = Http::get($url)->json(); + + if ($response == null) { + throw new ExchangeRateException('Server not responding', 'server_error'); + } + + $checkKey = $this->validateConnection(); + + return array_keys($response); + } + + public function validateConnection(): array + { + $url = "{$this->baseUrl}/latest?apikey={$this->apiKey}&symbols=INR&base=USD"; + $response = Http::get($url)->json(); + + if ($response == null) { + throw new ExchangeRateException('Server not responding', 'server_error'); + } + + if (array_key_exists('success', $response) && array_key_exists('error', $response)) { + if ($response['error']['status'] == 404) { + throw new ExchangeRateException('Please Enter Valid Provider Key.', 'invalid_key'); + } + } + + return array_values($response['rates']); + } +} diff --git a/app/Services/ExchangeRate/CurrencyLayerDriver.php b/app/Services/ExchangeRate/CurrencyLayerDriver.php new file mode 100644 index 00000000..89785e5f --- /dev/null +++ b/app/Services/ExchangeRate/CurrencyLayerDriver.php @@ -0,0 +1,50 @@ +baseUrl}/live?access_key={$this->apiKey}&source={$baseCurrency}¤cies={$targetCurrency}"; + $response = Http::get($url)->json(); + + if (array_key_exists('success', $response) && $response['success'] == false) { + throw new ExchangeRateException($response['error']['info'], 'provider_error'); + } + + return array_values($response['quotes']); + } + + public function getSupportedCurrencies(): array + { + $url = "{$this->baseUrl}/list?access_key={$this->apiKey}"; + $response = Http::get($url)->json(); + + if ($response == null) { + throw new ExchangeRateException('Server not responding', 'server_error'); + } + + if (array_key_exists('currencies', $response)) { + return array_keys($response['currencies']); + } + + throw new ExchangeRateException('Please Enter Valid Provider Key.', 'invalid_key'); + } + + public function validateConnection(): array + { + $url = "{$this->baseUrl}/live?access_key={$this->apiKey}&source=INR¤cies=USD"; + $response = Http::get($url)->json(); + + if (array_key_exists('success', $response) && $response['success'] == false) { + throw new ExchangeRateException($response['error']['info'], 'provider_error'); + } + + return array_values($response['quotes']); + } +} diff --git a/app/Services/ExchangeRate/ExchangeRateDriver.php b/app/Services/ExchangeRate/ExchangeRateDriver.php new file mode 100644 index 00000000..737648c7 --- /dev/null +++ b/app/Services/ExchangeRate/ExchangeRateDriver.php @@ -0,0 +1,34 @@ +> */ + protected static array $drivers = [ + 'currency_freak' => CurrencyFreakDriver::class, + 'currency_layer' => CurrencyLayerDriver::class, + 'open_exchange_rate' => OpenExchangeRateDriver::class, + 'currency_converter' => CurrencyConverterDriver::class, + ]; + + /** + * Register a custom exchange rate driver (for module extensibility). + */ + public static function register(string $name, string $driverClass): void + { + static::$drivers[$name] = $driverClass; + } + + public static function make(string $driver, string $apiKey, array $config = []): ExchangeRateDriver + { + $class = static::$drivers[$driver] ?? null; + + if (! $class) { + throw new InvalidArgumentException("Unknown exchange rate driver: {$driver}"); + } + + return new $class($apiKey, $config); + } + + /** + * Get all registered driver names. + */ + public static function availableDrivers(): array + { + return array_keys(static::$drivers); + } +} diff --git a/app/Services/ExchangeRate/ExchangeRateException.php b/app/Services/ExchangeRate/ExchangeRateException.php new file mode 100644 index 00000000..595e8e11 --- /dev/null +++ b/app/Services/ExchangeRate/ExchangeRateException.php @@ -0,0 +1,11 @@ +baseUrl}/latest.json?app_id={$this->apiKey}&base={$baseCurrency}&symbols={$targetCurrency}"; + $response = Http::get($url)->json(); + + if (array_key_exists('error', $response)) { + throw new ExchangeRateException($response['description'], $response['message']); + } + + return array_values($response['rates']); + } + + public function getSupportedCurrencies(): array + { + $url = "{$this->baseUrl}/currencies.json"; + $response = Http::get($url)->json(); + + if ($response == null) { + throw new ExchangeRateException('Server not responding', 'server_error'); + } + + $checkKey = $this->validateConnection(); + + return array_keys($response); + } + + public function validateConnection(): array + { + $url = "{$this->baseUrl}/latest.json?app_id={$this->apiKey}&base=INR&symbols=USD"; + $response = Http::get($url)->json(); + + if (array_key_exists('error', $response)) { + if ($response['status'] == 401) { + throw new ExchangeRateException('Please Enter Valid Provider Key.', 'invalid_key'); + } + + throw new ExchangeRateException($response['description'], $response['message']); + } + + return array_values($response['rates']); + } +} diff --git a/app/Services/ExchangeRateProviderService.php b/app/Services/ExchangeRateProviderService.php index 0234ebde..725386db 100644 --- a/app/Services/ExchangeRateProviderService.php +++ b/app/Services/ExchangeRateProviderService.php @@ -6,7 +6,8 @@ use App\Http\Requests\ExchangeRateProviderRequest; use App\Models\CompanySetting; use App\Models\ExchangeRateLog; use App\Models\ExchangeRateProvider; -use Illuminate\Support\Facades\Http; +use App\Services\ExchangeRate\ExchangeRateDriverFactory; +use App\Services\ExchangeRate\ExchangeRateException; class ExchangeRateProviderService { @@ -39,58 +40,46 @@ class ExchangeRateProviderService public function checkProviderStatus($request) { - switch ($request['driver']) { - case 'currency_freak': - $url = 'https://api.currencyfreaks.com/latest?apikey='.$request['key'].'&symbols=INR&base=USD'; - $response = Http::get($url)->json(); + try { + $driver = ExchangeRateDriverFactory::make( + $request['driver'], + $request['key'], + $request['driver_config'] ?? [] + ); - if (array_key_exists('success', $response)) { - if ($response['success'] == false) { - return respondJson($response['error']['message'], $response['error']['message']); - } - } + $rates = $driver->validateConnection(); - return response()->json([ - 'exchangeRate' => array_values($response['rates']), - ], 200); + return response()->json([ + 'exchangeRate' => $rates, + ], 200); + } catch (ExchangeRateException $e) { + return respondJson($e->errorKey, $e->getMessage()); + } + } - case 'currency_layer': - $url = 'http://api.currencylayer.com/live?access_key='.$request['key'].'&source=INR¤cies=USD'; - $response = Http::get($url)->json(); + public function getExchangeRate(string $driver, string $apiKey, array $driverConfig, string $baseCurrency, string $targetCurrency) + { + try { + $driverInstance = ExchangeRateDriverFactory::make($driver, $apiKey, $driverConfig); - if (array_key_exists('success', $response)) { - if ($response['success'] == false) { - return respondJson($response['error']['info'], $response['error']['info']); - } - } + return response()->json([ + 'exchangeRate' => $driverInstance->getExchangeRate($baseCurrency, $targetCurrency), + ], 200); + } catch (ExchangeRateException $e) { + return respondJson($e->errorKey, $e->getMessage()); + } + } - return response()->json([ - 'exchangeRate' => array_values($response['quotes']), - ], 200); + public function getSupportedCurrencies(string $driver, string $apiKey, array $driverConfig = []) + { + try { + $driverInstance = ExchangeRateDriverFactory::make($driver, $apiKey, $driverConfig); - case 'open_exchange_rate': - $url = 'https://openexchangerates.org/api/latest.json?app_id='.$request['key'].'&base=INR&symbols=USD'; - $response = Http::get($url)->json(); - - if (array_key_exists('error', $response)) { - return respondJson($response['message'], $response['description']); - } - - return response()->json([ - 'exchangeRate' => array_values($response['rates']), - ], 200); - - case 'currency_converter': - $url = $this->getCurrencyConverterUrl($request['driver_config']); - $url = $url.'/api/v7/convert?apiKey='.$request['key']; - - $query = 'INR_USD'; - $url = $url."&q={$query}".'&compact=y'; - $response = Http::get($url)->json(); - - return response()->json([ - 'exchangeRate' => array_values($response[$query]), - ], 200); + return response()->json([ + 'supportedCurrencies' => $driverInstance->getSupportedCurrencies(), + ]); + } catch (ExchangeRateException $e) { + return respondJson($e->errorKey, $e->getMessage()); } } @@ -103,14 +92,4 @@ class ExchangeRateProviderService 'currency_id' => CompanySetting::getSetting('currency', $model->company_id), ]); } - - private function getCurrencyConverterUrl($data): string - { - return match ($data['type']) { - 'PREMIUM' => 'https://api.currconv.com', - 'PREPAID' => 'https://prepaid.currconv.com', - 'FREE' => 'https://free.currconv.com', - 'DEDICATED' => $data['url'], - }; - } } diff --git a/app/Traits/ExchangeRateProvidersTrait.php b/app/Traits/ExchangeRateProvidersTrait.php deleted file mode 100644 index 420f500b..00000000 --- a/app/Traits/ExchangeRateProvidersTrait.php +++ /dev/null @@ -1,213 +0,0 @@ -json(); - - if (array_key_exists('success', $response)) { - if ($response['success'] == false) { - return respondJson($response['error']['message'], $response['error']['message']); - } - } - - return response()->json([ - 'exchangeRate' => array_values($response['rates']), - ], 200); - - break; - - case 'currency_layer': - $url = 'http://api.currencylayer.com/live?access_key='.$filter['key']."&source={$baseCurrencyCode}¤cies={$currencyCode}"; - $response = Http::get($url)->json(); - - if (array_key_exists('success', $response)) { - if ($response['success'] == false) { - return respondJson($response['error']['info'], $response['error']['info']); - } - } - - return response()->json([ - 'exchangeRate' => array_values($response['quotes']), - ], 200); - - break; - - case 'open_exchange_rate': - $url = 'https://openexchangerates.org/api/latest.json?app_id='.$filter['key']."&base={$baseCurrencyCode}&symbols={$currencyCode}"; - $response = Http::get($url)->json(); - - if (array_key_exists('error', $response)) { - return respondJson($response['message'], $response['description']); - } - - return response()->json([ - 'exchangeRate' => array_values($response['rates']), - ], 200); - - break; - - case 'currency_converter': - $url = $this->getCurrencyConverterUrl($filter['driver_config']); - $url = $url.'/api/v7/convert?apiKey='.$filter['key']; - - $query = "{$baseCurrencyCode}_{$currencyCode}"; - $url = $url."&q={$query}".'&compact=y'; - $response = Http::get($url)->json(); - - return response()->json([ - 'exchangeRate' => array_values($response[$query]), - ], 200); - - break; - } - } - - public function getCurrencyConverterUrl($data) - { - switch ($data['type']) { - case 'PREMIUM': - return 'https://api.currconv.com'; - - break; - - case 'PREPAID': - return 'https://prepaid.currconv.com'; - - break; - - case 'FREE': - return 'https://free.currconv.com'; - - break; - - case 'DEDICATED': - return $data['url']; - - break; - } - } - - public function getSupportedCurrencies($request) - { - $message = 'Please Enter Valid Provider Key.'; - $error = 'invalid_key'; - - $server_message = 'Server not responding'; - $error_message = 'server_error'; - - switch ($request->driver) { - case 'currency_freak': - $url = 'https://api.currencyfreaks.com/currency-symbols'; - $response = Http::get($url)->json(); - $checkKey = $this->getUrl($request); - - if ($response == null || $checkKey == null) { - return respondJson($error_message, $server_message); - } - - if (array_key_exists('success', $checkKey) && array_key_exists('error', $checkKey)) { - if ($checkKey['error']['status'] == 404) { - return respondJson($error, $message); - } - } - - return response()->json(['supportedCurrencies' => array_keys($response)]); - - break; - - case 'currency_layer': - $url = 'http://api.currencylayer.com/list?access_key='.$request->key; - $response = Http::get($url)->json(); - - if ($response == null) { - return respondJson($error_message, $server_message); - } - - if (array_key_exists('currencies', $response)) { - return response()->json(['supportedCurrencies' => array_keys($response['currencies'])]); - } - - return respondJson($error, $message); - - break; - - case 'open_exchange_rate': - $url = 'https://openexchangerates.org/api/currencies.json'; - $response = Http::get($url)->json(); - $checkKey = $this->getUrl($request); - - if ($response == null || $checkKey == null) { - return respondJson($error_message, $server_message); - } - - if (array_key_exists('error', $checkKey)) { - if ($checkKey['status'] == 401) { - return respondJson($error, $message); - } - } - - return response()->json(['supportedCurrencies' => array_keys($response)]); - - break; - - case 'currency_converter': - $response = $this->getUrl($request); - - if ($response == null) { - return respondJson($error_message, $server_message); - } - - if (array_key_exists('results', $response)) { - return response()->json(['supportedCurrencies' => array_keys($response['results'])]); - } - - return respondJson($error, $message); - - break; - } - } - - public function getUrl($request) - { - switch ($request->driver) { - case 'currency_freak': - $url = 'https://api.currencyfreaks.com/latest?apikey='.$request->key.'&symbols=INR&base=USD'; - - return Http::get($url)->json(); - - break; - - case 'currency_layer': - $url = 'http://api.currencylayer.com/live?access_key='.$request->key.'&source=INR¤cies=USD'; - - return Http::get($url)->json(); - - break; - - case 'open_exchange_rate': - $url = 'https://openexchangerates.org/api/latest.json?app_id='.$request->key.'&base=INR&symbols=USD'; - - return Http::get($url)->json(); - - break; - - case 'currency_converter': - $url = $this->getCurrencyConverterUrl($request).'/api/v7/currencies?apiKey='.$request->key; - - return Http::get($url)->json(); - - break; - } - } -}