├── src ├── Exceptions │ ├── ExchangeRateException.php │ ├── InvalidDateException.php │ └── InvalidCurrencyException.php ├── Interfaces │ ├── ResponseContract.php │ ├── RequestSender.php │ └── ExchangeRateDriver.php ├── Drivers │ ├── ExchangeRateHost │ │ ├── Response.php │ │ ├── RequestBuilder.php │ │ └── ExchangeRateHostDriver.php │ ├── ExchangeRatesApiIo │ │ ├── Response.php │ │ ├── RequestBuilder.php │ │ └── ExchangeRatesApiIoDriver.php │ ├── ExchangeRatesDataApi │ │ ├── Response.php │ │ ├── RequestBuilder.php │ │ └── ExchangeRatesDataApiDriver.php │ ├── CurrencyBeacon │ │ ├── Response.php │ │ ├── RequestBuilder.php │ │ └── CurrencyBeaconDriver.php │ └── Support │ │ └── SharedDriverLogicHandler.php ├── Rules │ └── ValidCurrency.php ├── Providers │ └── ExchangeRatesProvider.php ├── Facades │ └── ExchangeRate.php └── Classes │ ├── ExchangeRate.php │ ├── Validation.php │ ├── CacheRepository.php │ └── Currency.php ├── LICENSE.md ├── config └── laravel-exchange-rates.php ├── composer.json └── README.md /src/Exceptions/ExchangeRateException.php: -------------------------------------------------------------------------------- 1 | rawResponse[$key]; 16 | } 17 | 18 | public function rates(): array 19 | { 20 | return $this->get('quotes'); 21 | } 22 | 23 | public function raw(): mixed 24 | { 25 | return $this->rawResponse; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesApiIo/Response.php: -------------------------------------------------------------------------------- 1 | rawResponse[$key]; 16 | } 17 | 18 | public function rates(): array 19 | { 20 | return $this->get('rates'); 21 | } 22 | 23 | public function raw(): mixed 24 | { 25 | return $this->rawResponse; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesDataApi/Response.php: -------------------------------------------------------------------------------- 1 | rawResponse[$key]; 16 | } 17 | 18 | public function rates(): array 19 | { 20 | return $this->get('rates'); 21 | } 22 | 23 | public function raw(): mixed 24 | { 25 | return $this->rawResponse; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Drivers/CurrencyBeacon/Response.php: -------------------------------------------------------------------------------- 1 | rawResponse, $key); 16 | } 17 | 18 | public function rates(): array 19 | { 20 | return $this->get('response.rates'); 21 | } 22 | 23 | public function timeSeries(): array 24 | { 25 | return $this->get('response'); 26 | } 27 | 28 | public function raw(): mixed 29 | { 30 | return $this->rawResponse; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Rules/ValidCurrency.php: -------------------------------------------------------------------------------- 1 | isAllowableCurrency($value); 20 | } 21 | 22 | /** 23 | * Get the validation error message. 24 | * 25 | * @return string 26 | */ 27 | public function message(): string 28 | { 29 | return 'The :attribute must be a valid exchange rates currency.'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Providers/ExchangeRatesProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../../config/laravel-exchange-rates.php', 'laravel-exchange-rates'); 18 | 19 | $this->app->alias(ExchangeRate::class, 'exchange-rate'); 20 | } 21 | 22 | /** 23 | * Bootstrap any application services. 24 | * 25 | * @return void 26 | */ 27 | public function boot(): void 28 | { 29 | $this->publishes([ 30 | __DIR__.'/../../config/laravel-exchange-rates.php' => config_path('laravel-exchange-rates.php'), 31 | ], 'laravel-exchange-rates-config'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ashley Allen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/laravel-exchange-rates.php: -------------------------------------------------------------------------------- 1 | 'exchange-rates-api-io', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | API Key 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Define your API key here. 23 | | 24 | */ 25 | 'api_key' => env('EXCHANGE_RATES_API_KEY'), 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Use HTTPS 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Define if the API should be accessed via HTTPS or HTTP. The free tiers of 33 | | exchangeratesapi.io and exchangerate.host only allow API access via HTTP. 34 | | 35 | */ 36 | 'https' => true, 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /src/Facades/ExchangeRate.php: -------------------------------------------------------------------------------- 1 | apiKey = config('laravel-exchange-rates.api_key'); 19 | } 20 | 21 | /** 22 | * Make an API request to the CurrencyBeacon API. 23 | * 24 | * @param array $queryParams 25 | * 26 | * @throws RequestException 27 | */ 28 | public function makeRequest(string $path, array $queryParams = []): ResponseContract 29 | { 30 | $protocol = config('laravel-exchange-rates.https') ? 'https://' : 'http://'; 31 | 32 | $rawResponse = Http::baseUrl($protocol.self::BASE_URL) 33 | ->get( 34 | $path, 35 | array_merge(['api_key' => $this->apiKey], $queryParams) 36 | ) 37 | ->throw() 38 | ->json(); 39 | 40 | return new Response($rawResponse); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Classes/ExchangeRate.php: -------------------------------------------------------------------------------- 1 | apiKey = config('laravel-exchange-rates.api_key'); 19 | } 20 | 21 | /** 22 | * Make an API request to the ExchangeRatesAPI. 23 | * 24 | * @param string $path 25 | * @param array $queryParams 26 | * @return ResponseContract 27 | * 28 | * @throws RequestException 29 | */ 30 | public function makeRequest(string $path, array $queryParams = []): ResponseContract 31 | { 32 | $protocol = config('laravel-exchange-rates.https') ? 'https://' : 'http://'; 33 | 34 | $rawResponse = Http::baseUrl($protocol.self::BASE_URL) 35 | ->get( 36 | $path, 37 | array_merge(['access_key' => $this->apiKey], $queryParams) 38 | ) 39 | ->throw() 40 | ->json(); 41 | 42 | return new Response($rawResponse); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesApiIo/RequestBuilder.php: -------------------------------------------------------------------------------- 1 | apiKey = config('laravel-exchange-rates.api_key'); 19 | } 20 | 21 | /** 22 | * Make an API request to the ExchangeRatesAPI. 23 | * 24 | * @param string $path 25 | * @param array $queryParams 26 | * @return ResponseContract 27 | * 28 | * @throws RequestException 29 | */ 30 | public function makeRequest(string $path, array $queryParams = []): ResponseContract 31 | { 32 | $protocol = config('laravel-exchange-rates.https') ? 'https://' : 'http://'; 33 | 34 | $rawResponse = Http::baseUrl($protocol.self::BASE_URL) 35 | ->get( 36 | $path, 37 | array_merge(['access_key' => $this->apiKey], $queryParams) 38 | ) 39 | ->throw() 40 | ->json(); 41 | 42 | return new Response($rawResponse); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesDataApi/RequestBuilder.php: -------------------------------------------------------------------------------- 1 | apiKey = config('laravel-exchange-rates.api_key'); 21 | } 22 | 23 | /** 24 | * Make an API request to the Exchange Rates Data API. 25 | * 26 | * @param string $path 27 | * @param array $queryParams 28 | * @return ResponseContract 29 | * 30 | * @throws RequestException 31 | */ 32 | public function makeRequest(string $path, array $queryParams = []): ResponseContract 33 | { 34 | $protocol = config('laravel-exchange-rates.https') ? 'https://' : 'http://'; 35 | 36 | $rawResponse = Http::baseUrl($protocol.self::BASE_URL) 37 | ->withHeaders([ 38 | 'apiKey' => $this->apiKey, 39 | ]) 40 | ->get($path, $queryParams) 41 | ->throw() 42 | ->json(); 43 | 44 | return new Response($rawResponse); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ashallendesign/laravel-exchange-rates", 3 | "description": "A wrapper package for interacting with the exchangeratesapi.io API.", 4 | "type": "library", 5 | "homepage": "https://github.com/ash-jc-allen/laravel-exchange-rates", 6 | "keywords": [ 7 | "ashallendesign", 8 | "exchange-rate", 9 | "exchangeratesapi.io", 10 | "laravel-exchange-rates" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Ash Allen", 16 | "email": "mail@ashallendesign.co.uk" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.0", 21 | "nesbot/carbon": "^2.0|^3.0", 22 | "guzzlehttp/guzzle": "^7.0", 23 | "illuminate/container": "^9.0|^10.0|^11.0|^12.0", 24 | "illuminate/cache": "^9.0|^10.0|^11.0|^12.0", 25 | "ext-json": "*" 26 | }, 27 | "require-dev": { 28 | "mockery/mockery": "^1.0", 29 | "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", 30 | "phpunit/phpunit": "^9.0|^10.5|^11.0", 31 | "larastan/larastan": "^1.0|^2.0|^3.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "AshAllenDesign\\LaravelExchangeRates\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "AshAllenDesign\\LaravelExchangeRates\\Tests\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "AshAllenDesign\\LaravelExchangeRates\\Providers\\ExchangeRatesProvider" 47 | ], 48 | "aliases": { 49 | "ExchangeRate": "AshAllenDesign\\LaravelExchangeRates\\Facades\\ExchangeRate" 50 | } 51 | } 52 | }, 53 | "scripts": { 54 | "test": "vendor/bin/phpunit" 55 | }, 56 | "minimum-stability": "dev", 57 | "prefer-stable": true 58 | } 59 | -------------------------------------------------------------------------------- /src/Classes/Validation.php: -------------------------------------------------------------------------------- 1 | isAllowableCurrency($currencyCode)) { 25 | throw new InvalidCurrencyException($currencyCode.' is not a valid currency code.'); 26 | } 27 | } 28 | 29 | /** 30 | * Validate that the currencies are all supported by the exchange rates API. 31 | * 32 | * @param array $currencyCodes 33 | * 34 | * @throws InvalidCurrencyException 35 | */ 36 | public static function validateCurrencyCodes(array $currencyCodes): void 37 | { 38 | $currencies = new Currency(); 39 | 40 | foreach ($currencyCodes as $currencyCode) { 41 | if (! $currencies->isAllowableCurrency($currencyCode)) { 42 | throw new InvalidCurrencyException($currencyCode.' is not a valid currency code.'); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Validate that both of the dates are in the past. After this, check that 49 | * the 'from' date is not after the 'to' date. 50 | * 51 | * @param Carbon $from 52 | * @param Carbon $to 53 | * 54 | * @throws InvalidDateException 55 | */ 56 | public static function validateStartAndEndDates(Carbon $from, Carbon $to): void 57 | { 58 | self::validateDate($from); 59 | self::validateDate($to); 60 | 61 | if ($from->isAfter($to)) { 62 | throw new InvalidDateException('The \'from\' date must be before the \'to\' date.'); 63 | } 64 | } 65 | 66 | /** 67 | * Validate the date that has been passed is in the past. 68 | * 69 | * @param Carbon $date 70 | * 71 | * @throws InvalidDateException 72 | */ 73 | public static function validateDate(Carbon $date): void 74 | { 75 | if (! $date->isPast()) { 76 | throw new InvalidDateException('The date must be in the past.'); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Classes/CacheRepository.php: -------------------------------------------------------------------------------- 1 | make('cache'); 27 | $config = Container::getInstance()->make('config')->get('cache.default'); 28 | 29 | $this->cache = $cache->store($config); 30 | } 31 | 32 | /** 33 | * Forget the item from the cache. 34 | * 35 | * @param string $key 36 | * @return CacheRepository 37 | */ 38 | public function forget(string $key): self 39 | { 40 | $this->cache->forget($this->cachePrefix.$key); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * Store a new item in the cache. 47 | * 48 | * @param string $key 49 | * @param string|array $value 50 | * @return bool 51 | */ 52 | public function storeInCache(string $key, $value): bool 53 | { 54 | return $this->cache->forever($this->cachePrefix.$key, $value); 55 | } 56 | 57 | /** 58 | * Get an item from the cache if it exists. 59 | * 60 | * @param string $key 61 | * @return mixed 62 | * 63 | * @throws InvalidArgumentException 64 | */ 65 | public function getFromCache(string $key) 66 | { 67 | return $this->cache->get($this->cachePrefix.$key); 68 | } 69 | 70 | /** 71 | * Build the key that can be used for fetching or 72 | * storing items in the cache. We can pass a 73 | * fourth parameter if we are storing 74 | * exchange rates for a given date 75 | * range. 76 | * 77 | * @param string $from 78 | * @param string|string[] $to 79 | * @param Carbon $date 80 | * @param Carbon|null $endDate 81 | * @return string 82 | */ 83 | public function buildCacheKey(string $from, string|array $to, Carbon $date, ?Carbon $endDate = null): string 84 | { 85 | if (is_array($to)) { 86 | asort($to); 87 | $to = implode('_', $to); 88 | } 89 | 90 | $key = $from.'_'.$to.'_'.$date->format('Y-m-d'); 91 | 92 | if ($endDate) { 93 | $key .= '_'.$endDate->format('Y-m-d'); 94 | } 95 | 96 | return $key; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesApiIo/ExchangeRatesApiIoDriver.php: -------------------------------------------------------------------------------- 1 | sharedDriverLogicHandler = new SharedDriverLogicHandler( 25 | $requestBuilder, 26 | $cacheRepository 27 | ); 28 | } 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public function currencies(): array 34 | { 35 | return $this->sharedDriverLogicHandler->currencies(); 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function exchangeRate(string $from, array|string $to, ?Carbon $date = null): float|array 42 | { 43 | return $this->sharedDriverLogicHandler->exchangeRate($from, $to, $date); 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function exchangeRateBetweenDateRange( 50 | string $from, 51 | array|string $to, 52 | Carbon $date, 53 | Carbon $endDate 54 | ): array { 55 | return $this->sharedDriverLogicHandler->exchangeRateBetweenDateRange($from, $to, $date, $endDate); 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | public function convert(int $value, string $from, array|string $to, ?Carbon $date = null): float|array 62 | { 63 | return $this->sharedDriverLogicHandler->convert($value, $from, $to, $date); 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | public function convertBetweenDateRange( 70 | int $value, 71 | string $from, 72 | array|string $to, 73 | Carbon $date, 74 | Carbon $endDate 75 | ): array { 76 | return $this->sharedDriverLogicHandler->convertBetweenDateRange($value, $from, $to, $date, $endDate); 77 | } 78 | 79 | /** 80 | * @inheritDoc 81 | */ 82 | public function shouldCache(bool $shouldCache = true): ExchangeRateDriver 83 | { 84 | $this->sharedDriverLogicHandler->shouldCache($shouldCache); 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * @inheritDoc 91 | */ 92 | public function shouldBustCache(bool $bustCache = true): ExchangeRateDriver 93 | { 94 | $this->sharedDriverLogicHandler->shouldBustCache($bustCache); 95 | 96 | return $this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRatesDataApi/ExchangeRatesDataApiDriver.php: -------------------------------------------------------------------------------- 1 | sharedDriverLogicHandler = new SharedDriverLogicHandler( 25 | $requestBuilder, 26 | $cacheRepository 27 | ); 28 | } 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public function currencies(): array 34 | { 35 | return $this->sharedDriverLogicHandler->currencies(); 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function exchangeRate(string $from, array|string $to, ?Carbon $date = null): float|array 42 | { 43 | return $this->sharedDriverLogicHandler->exchangeRate($from, $to, $date); 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function exchangeRateBetweenDateRange( 50 | string $from, 51 | array|string $to, 52 | Carbon $date, 53 | Carbon $endDate, 54 | ): array { 55 | return $this->sharedDriverLogicHandler->exchangeRateBetweenDateRange($from, $to, $date, $endDate); 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | public function convert(int $value, string $from, array|string $to, ?Carbon $date = null): float|array 62 | { 63 | return $this->sharedDriverLogicHandler->convert($value, $from, $to, $date); 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | public function convertBetweenDateRange( 70 | int $value, 71 | string $from, 72 | array|string $to, 73 | Carbon $date, 74 | Carbon $endDate, 75 | ): array { 76 | return $this->sharedDriverLogicHandler->convertBetweenDateRange($value, $from, $to, $date, $endDate); 77 | } 78 | 79 | /** 80 | * @inheritDoc 81 | */ 82 | public function shouldCache(bool $shouldCache = true): ExchangeRateDriver 83 | { 84 | $this->sharedDriverLogicHandler->shouldCache($shouldCache); 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * @inheritDoc 91 | */ 92 | public function shouldBustCache(bool $bustCache = true): ExchangeRateDriver 93 | { 94 | $this->sharedDriverLogicHandler->shouldBustCache($bustCache); 95 | 96 | return $this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Classes/Currency.php: -------------------------------------------------------------------------------- 1 | allowableCurrencies, true); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Interfaces/ExchangeRateDriver.php: -------------------------------------------------------------------------------- 1 | 35 | * 36 | * @throws InvalidCurrencyException 37 | * @throws InvalidDateException 38 | * @throws RequestException 39 | * @throws InvalidArgumentException 40 | */ 41 | public function exchangeRate(string $from, array|string $to, ?Carbon $date = null): float|array; 42 | 43 | /** 44 | * Return the exchange rates between the given date range. 45 | * 46 | * @param string $from 47 | * @param string|string[] $to 48 | * @param Carbon $date 49 | * @param Carbon $endDate 50 | * @return array|array> 51 | * 52 | * @throws InvalidCurrencyException 53 | * @throws InvalidDateException 54 | * @throws RequestException 55 | * @throws InvalidArgumentException 56 | */ 57 | public function exchangeRateBetweenDateRange( 58 | string $from, 59 | array|string $to, 60 | Carbon $date, 61 | Carbon $endDate 62 | ): array; 63 | 64 | /** 65 | * Return the converted values between the $from and $to parameters. If no $date 66 | * parameter is passed, we use today's date instead. 67 | * 68 | * @param int $value 69 | * @param string $from 70 | * @param string|string[] $to 71 | * @param Carbon|null $date 72 | * @return float|array 73 | * 74 | * @throws InvalidDateException 75 | * @throws InvalidCurrencyException 76 | * @throws RequestException 77 | * @throws InvalidArgumentException 78 | */ 79 | public function convert(int $value, string $from, array|string $to, ?Carbon $date = null): float|array; 80 | 81 | /** 82 | * Return an array of the converted values between the given date range. 83 | * 84 | * @param int $value 85 | * @param string $from 86 | * @param string|string[] $to 87 | * @param Carbon $date 88 | * @param Carbon $endDate 89 | * @return array|array> 90 | * 91 | * @throws InvalidCurrencyException 92 | * @throws InvalidDateException 93 | * @throws RequestException 94 | * @throws InvalidArgumentException 95 | */ 96 | public function convertBetweenDateRange( 97 | int $value, 98 | string $from, 99 | array|string $to, 100 | Carbon $date, 101 | Carbon $endDate 102 | ): array; 103 | 104 | /** 105 | * Determine whether if the exchange rate should be cached after it is fetched 106 | * from the API. 107 | * 108 | * @param bool $shouldCache 109 | * @return $this 110 | */ 111 | public function shouldCache(bool $shouldCache = true): self; 112 | 113 | /** 114 | * Determine whether if the cached result (if it exists) should be deleted. This 115 | * will force a new exchange rate to be fetched from the API. 116 | * 117 | * @param bool $bustCache 118 | * @return $this 119 | */ 120 | public function shouldBustCache(bool $bustCache = true): self; 121 | } 122 | -------------------------------------------------------------------------------- /src/Drivers/CurrencyBeacon/CurrencyBeaconDriver.php: -------------------------------------------------------------------------------- 1 | cacheRepository = $cacheRepository ?? new CacheRepository(); 26 | 27 | $this->sharedDriverLogicHandler = new SharedDriverLogicHandler( 28 | $requestBuilder, 29 | $this->cacheRepository, 30 | ); 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | public function currencies(): array 37 | { 38 | $cacheKey = 'currencies'; 39 | 40 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 41 | return $cachedExchangeRate; 42 | } 43 | 44 | $response = $this->sharedDriverLogicHandler 45 | ->getRequestBuilder() 46 | ->makeRequest('/currencies', ['type' => 'fiat']); 47 | 48 | /** @var array> $currenciesFromResponse */ 49 | $currenciesFromResponse = $response->get('response'); 50 | 51 | $currencies = collect($currenciesFromResponse)->pluck('short_code')->toArray(); 52 | 53 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $currencies); 54 | 55 | return $currencies; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public function exchangeRate(string $from, array|string $to, ?Carbon $date = null): float|array 62 | { 63 | $this->sharedDriverLogicHandler->validateExchangeRateInput($from, $to, $date); 64 | 65 | if ($from === $to) { 66 | return 1.0; 67 | } 68 | 69 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date ?? Carbon::now()); 70 | 71 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 72 | // If the exchange rate has been retrieved from the cache as a 73 | // string (e.g. "1.23"), then cast it to a float (e.g. 1.23). 74 | // If we have retrieved the rates for many currencies, it 75 | // will be an array of floats, so just return it. 76 | return is_string($cachedExchangeRate) 77 | ? (float) $cachedExchangeRate 78 | : $cachedExchangeRate; 79 | } 80 | 81 | $symbols = is_string($to) ? $to : implode(',', $to); 82 | $queryParams = ['base' => $from, 'symbols' => $symbols]; 83 | 84 | if ($date) { 85 | $queryParams['date'] = $date->format('Y-m-d'); 86 | } 87 | 88 | $url = $date ? '/historical' : '/latest'; 89 | 90 | /** @var array $response */ 91 | $response = $this->sharedDriverLogicHandler 92 | ->getRequestBuilder() 93 | ->makeRequest($url, $queryParams) 94 | ->rates(); 95 | 96 | $exchangeRate = is_string($to) 97 | ? $response[$to] 98 | : $response; 99 | 100 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $exchangeRate); 101 | 102 | return $exchangeRate; 103 | } 104 | 105 | /** 106 | * {@inheritDoc} 107 | */ 108 | public function exchangeRateBetweenDateRange( 109 | string $from, 110 | array|string $to, 111 | Carbon $date, 112 | Carbon $endDate 113 | ): array { 114 | $this->sharedDriverLogicHandler->validateExchangeRateBetweenDateRangeInput($from, $to, $date, $endDate); 115 | 116 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date, $endDate); 117 | 118 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 119 | return $cachedExchangeRate; 120 | } 121 | 122 | $conversions = $from === $to 123 | ? $this->sharedDriverLogicHandler->exchangeRateDateRangeResultWithSameCurrency($date, $endDate) 124 | : $this->makeRequestForExchangeRates($from, $to, $date, $endDate); 125 | 126 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $conversions); 127 | 128 | return $conversions; 129 | } 130 | 131 | /** 132 | * {@inheritDoc} 133 | */ 134 | public function convert(int $value, string $from, array|string $to, ?Carbon $date = null): float|array 135 | { 136 | return $this->sharedDriverLogicHandler->convertUsingRates( 137 | $this->exchangeRate($from, $to, $date), 138 | $to, 139 | $value, 140 | ); 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | public function convertBetweenDateRange( 147 | int $value, 148 | string $from, 149 | array|string $to, 150 | Carbon $date, 151 | Carbon $endDate 152 | ): array { 153 | return $this->sharedDriverLogicHandler->convertUsingRatesForDateRange( 154 | $this->exchangeRateBetweenDateRange($from, $to, $date, $endDate), 155 | $to, 156 | $value, 157 | ); 158 | } 159 | 160 | /** 161 | * {@inheritDoc} 162 | */ 163 | public function shouldCache(bool $shouldCache = true): ExchangeRateDriver 164 | { 165 | $this->sharedDriverLogicHandler->shouldCache($shouldCache); 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * {@inheritDoc} 172 | */ 173 | public function shouldBustCache(bool $bustCache = true): ExchangeRateDriver 174 | { 175 | $this->sharedDriverLogicHandler->shouldBustCache($bustCache); 176 | 177 | return $this; 178 | } 179 | 180 | /** 181 | * Make a request to the exchange rates API to get the exchange rates between a 182 | * date range. If only one currency is being used, we flatten the array to 183 | * remove currency symbol before returning it. 184 | * 185 | * @param string|string[] $to 186 | * @return array|array> 187 | * 188 | * @throws RequestException 189 | */ 190 | private function makeRequestForExchangeRates(string $from, string|array $to, Carbon $date, Carbon $endDate): array 191 | { 192 | $symbols = is_string($to) ? $to : implode(',', $to); 193 | 194 | /** @var Response $result */ 195 | $result = $this->sharedDriverLogicHandler 196 | ->getRequestBuilder() 197 | ->makeRequest('/timeseries', [ 198 | 'base' => $from, 199 | 'start_date' => $date->format('Y-m-d'), 200 | 'end_date' => $endDate->format('Y-m-d'), 201 | 'symbols' => $symbols, 202 | ]); 203 | 204 | $conversions = $result->timeSeries(); 205 | 206 | foreach ($conversions as $rateDate => $rates) { 207 | $ratesForDay = is_string($to) 208 | ? $rates[$to] 209 | : $rates; 210 | 211 | $conversions[$rateDate] = $ratesForDay; 212 | } 213 | 214 | ksort($conversions); 215 | 216 | return $conversions; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Drivers/ExchangeRateHost/ExchangeRateHostDriver.php: -------------------------------------------------------------------------------- 1 | cacheRepository = $cacheRepository ?? new CacheRepository(); 26 | 27 | $this->sharedDriverLogicHandler = new SharedDriverLogicHandler( 28 | $requestBuilder, 29 | $this->cacheRepository, 30 | ); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | public function currencies(): array 37 | { 38 | $cacheKey = 'currencies'; 39 | 40 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 41 | return $cachedExchangeRate; 42 | } 43 | 44 | $response = $this->sharedDriverLogicHandler 45 | ->getRequestBuilder() 46 | ->makeRequest('/list'); 47 | 48 | $currencies = array_keys($response->get('currencies')); 49 | 50 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $currencies); 51 | 52 | return $currencies; 53 | } 54 | 55 | /** 56 | * @inheritDoc 57 | */ 58 | public function exchangeRate(string $from, array|string $to, ?Carbon $date = null): float|array 59 | { 60 | $this->sharedDriverLogicHandler->validateExchangeRateInput($from, $to, $date); 61 | 62 | if ($from === $to) { 63 | return 1.0; 64 | } 65 | 66 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date ?? Carbon::now()); 67 | 68 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 69 | // If the exchange rate has been retrieved from the cache as a 70 | // string (e.g. "1.23"), then cast it to a float (e.g. 1.23). 71 | // If we have retrieved the rates for many currencies, it 72 | // will be an array of floats, so just return it. 73 | return is_string($cachedExchangeRate) 74 | ? (float) $cachedExchangeRate 75 | : $cachedExchangeRate; 76 | } 77 | 78 | $symbols = is_string($to) ? $to : implode(',', $to); 79 | $queryParams = ['source' => $from, 'currencies' => $symbols]; 80 | 81 | if ($date) { 82 | $queryParams['date'] = $date->format('Y-m-d'); 83 | } 84 | 85 | $url = $date ? '/historical' : '/live'; 86 | 87 | /** @var array $response */ 88 | $response = $this->sharedDriverLogicHandler 89 | ->getRequestBuilder() 90 | ->makeRequest($url, $queryParams) 91 | ->rates(); 92 | 93 | $exchangeRate = is_string($to) 94 | ? $response[$from.$to] 95 | : $this->removeSourceCurrencyFromKeys($response); 96 | 97 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $exchangeRate); 98 | 99 | return $exchangeRate; 100 | } 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | public function exchangeRateBetweenDateRange( 106 | string $from, 107 | array|string $to, 108 | Carbon $date, 109 | Carbon $endDate 110 | ): array { 111 | $this->sharedDriverLogicHandler->validateExchangeRateBetweenDateRangeInput($from, $to, $date, $endDate); 112 | 113 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date, $endDate); 114 | 115 | if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) { 116 | return $cachedExchangeRate; 117 | } 118 | 119 | $conversions = $from === $to 120 | ? $this->sharedDriverLogicHandler->exchangeRateDateRangeResultWithSameCurrency($date, $endDate) 121 | : $this->makeRequestForExchangeRates($from, $to, $date, $endDate); 122 | 123 | $this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $conversions); 124 | 125 | return $conversions; 126 | } 127 | 128 | /** 129 | * @inheritDoc 130 | */ 131 | public function convert(int $value, string $from, array|string $to, ?Carbon $date = null): float|array 132 | { 133 | return $this->sharedDriverLogicHandler->convertUsingRates( 134 | $this->exchangeRate($from, $to, $date), 135 | $to, 136 | $value, 137 | ); 138 | } 139 | 140 | /** 141 | * @inheritDoc 142 | */ 143 | public function convertBetweenDateRange( 144 | int $value, 145 | string $from, 146 | array|string $to, 147 | Carbon $date, 148 | Carbon $endDate 149 | ): array { 150 | return $this->sharedDriverLogicHandler->convertUsingRatesForDateRange( 151 | $this->exchangeRateBetweenDateRange($from, $to, $date, $endDate), 152 | $to, 153 | $value, 154 | ); 155 | } 156 | 157 | /** 158 | * @inheritDoc 159 | */ 160 | public function shouldCache(bool $shouldCache = true): ExchangeRateDriver 161 | { 162 | $this->sharedDriverLogicHandler->shouldCache($shouldCache); 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * @inheritDoc 169 | */ 170 | public function shouldBustCache(bool $bustCache = true): ExchangeRateDriver 171 | { 172 | $this->sharedDriverLogicHandler->shouldBustCache($bustCache); 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Make a request to the exchange rates API to get the exchange rates between a 179 | * date range. If only one currency is being used, we flatten the array to 180 | * remove currency symbol before returning it. 181 | * 182 | * @param string $from 183 | * @param string|string[] $to 184 | * @param Carbon $date 185 | * @param Carbon $endDate 186 | * @return array|array> 187 | * 188 | * @throws RequestException 189 | */ 190 | private function makeRequestForExchangeRates(string $from, string|array $to, Carbon $date, Carbon $endDate): array 191 | { 192 | $symbols = is_string($to) ? $to : implode(',', $to); 193 | 194 | $result = $this->sharedDriverLogicHandler 195 | ->getRequestBuilder() 196 | ->makeRequest('/timeframe', [ 197 | 'source' => $from, 198 | 'start_date' => $date->format('Y-m-d'), 199 | 'end_date' => $endDate->format('Y-m-d'), 200 | 'currencies' => $symbols, 201 | ]); 202 | 203 | $conversions = $result->rates(); 204 | 205 | foreach ($conversions as $rateDate => $rates) { 206 | $ratesForDay = is_string($to) 207 | ? $rates[$from.$to] 208 | : $this->removeSourceCurrencyFromKeys($rates); 209 | 210 | $conversions[$rateDate] = $ratesForDay; 211 | } 212 | 213 | ksort($conversions); 214 | 215 | return $conversions; 216 | } 217 | 218 | /** 219 | * The quotes are returned in the format of "USDEUR": 0.1234. We only want the 220 | * converted currency's code (e.g. EUR), so we need to remove the source 221 | * currency from the start of the key (e.g. USD). We can do this by 222 | * removing the first three characters from the key. Strip these 223 | * characters from all the rates and then return the array. 224 | * 225 | * @param array $rates 226 | * @return array 227 | */ 228 | private function removeSourceCurrencyFromKeys(array $rates): array 229 | { 230 | return collect($rates) 231 | ->mapWithKeys(static fn (float $value, string $key): array => [substr($key, 3) => $value]) 232 | ->all(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Drivers/Support/SharedDriverLogicHandler.php: -------------------------------------------------------------------------------- 1 | requestBuilder = $requestBuilder; 48 | $this->cacheRepository = $cacheRepository; 49 | } 50 | 51 | /** 52 | * Return an array of available currencies that can be used with this package. 53 | * 54 | * @return string[] 55 | * 56 | * @throws InvalidArgumentException 57 | * @throws RequestException 58 | */ 59 | public function currencies(): array 60 | { 61 | $cacheKey = 'currencies'; 62 | 63 | if ($cachedExchangeRate = $this->attemptToResolveFromCache($cacheKey)) { 64 | return $cachedExchangeRate; 65 | } 66 | 67 | $response = $this->requestBuilder->makeRequest('/latest', []); 68 | 69 | $currencies = [$response->get('base')]; 70 | 71 | foreach ($response->rates() as $currency => $rate) { 72 | $currencies[] = $currency; 73 | } 74 | 75 | if ($this->shouldCache) { 76 | $this->cacheRepository->storeInCache($cacheKey, $currencies); 77 | } 78 | 79 | return $currencies; 80 | } 81 | 82 | /** 83 | * Return the exchange rate between the $from and $to parameters. If no $date 84 | * parameter is passed, we use today's date instead. If $to is a string, 85 | * the exchange rate will be returned as a string. If $to is an array, 86 | * the rates will be returned within an array. 87 | * 88 | * @param string $from 89 | * @param string|string[] $to 90 | * @param Carbon|null $date 91 | * @return float|array 92 | * 93 | * @throws InvalidCurrencyException 94 | * @throws InvalidDateException 95 | * @throws RequestException 96 | * @throws InvalidArgumentException 97 | */ 98 | public function exchangeRate(string $from, string|array $to, ?Carbon $date = null): float|array 99 | { 100 | $this->validateExchangeRateInput($from, $to, $date); 101 | 102 | if ($from === $to) { 103 | return 1.0; 104 | } 105 | 106 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date ?? Carbon::now()); 107 | 108 | if ($cachedExchangeRate = $this->attemptToResolveFromCache($cacheKey)) { 109 | // If the exchange rate has been retrieved from the cache as a 110 | // string (e.g. "1.23"), then cast it to a float (e.g. 1.23). 111 | // If we have retrieved the rates for many currencies, it 112 | // will be an array of floats, so just return it. 113 | return is_string($cachedExchangeRate) 114 | ? (float) $cachedExchangeRate 115 | : $cachedExchangeRate; 116 | } 117 | 118 | $symbols = is_string($to) ? $to : implode(',', $to); 119 | $queryParams = ['base' => $from, 'symbols' => $symbols]; 120 | 121 | $url = $date 122 | ? '/'.$date->format('Y-m-d') 123 | : '/latest'; 124 | 125 | $response = $this->requestBuilder->makeRequest($url, $queryParams)->rates(); 126 | 127 | $exchangeRate = is_string($to) ? $response[$to] : $response; 128 | 129 | if ($this->shouldCache) { 130 | $this->cacheRepository->storeInCache($cacheKey, $exchangeRate); 131 | } 132 | 133 | return $exchangeRate; 134 | } 135 | 136 | /** 137 | * Return the exchange rates between the given date range. 138 | * 139 | * @param string $from 140 | * @param string|string[] $to 141 | * @param Carbon $date 142 | * @param Carbon $endDate 143 | * @return array|array> 144 | * 145 | * @throws InvalidCurrencyException 146 | * @throws InvalidDateException 147 | * @throws RequestException 148 | * @throws InvalidArgumentException 149 | */ 150 | public function exchangeRateBetweenDateRange( 151 | string $from, 152 | string|array $to, 153 | Carbon $date, 154 | Carbon $endDate, 155 | ): array { 156 | $this->validateExchangeRateBetweenDateRangeInput($from, $to, $date, $endDate); 157 | 158 | $cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date, $endDate); 159 | 160 | if ($cachedExchangeRate = $this->attemptToResolveFromCache($cacheKey)) { 161 | return $cachedExchangeRate; 162 | } 163 | 164 | $conversions = $from === $to 165 | ? $this->exchangeRateDateRangeResultWithSameCurrency($date, $endDate) 166 | : $this->makeRequestForExchangeRates($from, $to, $date, $endDate); 167 | 168 | if ($this->shouldCache) { 169 | $this->cacheRepository->storeInCache($cacheKey, $conversions); 170 | } 171 | 172 | return $conversions; 173 | } 174 | 175 | /** 176 | * Make a request to the exchange rates API to get the exchange rates between a 177 | * date range. If only one currency is being used, we flatten the array to 178 | * remove currency symbol before returning it. 179 | * 180 | * @param string $from 181 | * @param string|string[] $to 182 | * @param Carbon $date 183 | * @param Carbon $endDate 184 | * @return array|array> 185 | * 186 | * @throws RequestException 187 | */ 188 | private function makeRequestForExchangeRates(string $from, string|array $to, Carbon $date, Carbon $endDate): array 189 | { 190 | $symbols = is_string($to) ? $to : implode(',', $to); 191 | 192 | $result = $this->requestBuilder->makeRequest('/timeseries', [ 193 | 'base' => $from, 194 | 'start_date' => $date->format('Y-m-d'), 195 | 'end_date' => $endDate->format('Y-m-d'), 196 | 'symbols' => $symbols, 197 | ]); 198 | 199 | $conversions = $result->rates(); 200 | 201 | if (is_string($to)) { 202 | foreach ($conversions as $date => $rate) { 203 | $conversions[$date] = $rate[$to]; 204 | } 205 | } 206 | 207 | ksort($conversions); 208 | 209 | return $conversions; 210 | } 211 | 212 | /** 213 | * Return the converted values between the $from and $to parameters. If no $date 214 | * parameter is passed, we use today's date instead. 215 | * 216 | * @param int $value 217 | * @param string $from 218 | * @param string|string[] $to 219 | * @param Carbon|null $date 220 | * @return float|array 221 | * 222 | * @throws InvalidDateException 223 | * @throws InvalidCurrencyException 224 | * @throws RequestException 225 | * @throws InvalidArgumentException 226 | */ 227 | public function convert(int $value, string $from, string|array $to, ?Carbon $date = null): float|array 228 | { 229 | return $this->convertUsingRates( 230 | $this->exchangeRate($from, $to, $date), 231 | $to, 232 | $value, 233 | ); 234 | } 235 | 236 | /** 237 | * Return an array of the converted values between the given date range. 238 | * 239 | * @param int $value 240 | * @param string $from 241 | * @param string|string[] $to 242 | * @param Carbon $date 243 | * @param Carbon $endDate 244 | * @return array|array> 245 | * 246 | * @throws InvalidCurrencyException 247 | * @throws InvalidDateException 248 | * @throws RequestException 249 | * @throws InvalidArgumentException 250 | */ 251 | public function convertBetweenDateRange( 252 | int $value, 253 | string $from, 254 | string|array $to, 255 | Carbon $date, 256 | Carbon $endDate, 257 | ): array { 258 | return $this->convertUsingRatesForDateRange( 259 | $this->exchangeRateBetweenDateRange($from, $to, $date, $endDate), 260 | $to, 261 | $value, 262 | ); 263 | } 264 | 265 | /** 266 | * If the 'from' and 'to' currencies are the same, we don't need to make a request to 267 | * the API. Instead, we can build the response ourselves to improve the performance. 268 | * 269 | * @param Carbon $startDate 270 | * @param Carbon $endDate 271 | * @return array 272 | */ 273 | public function exchangeRateDateRangeResultWithSameCurrency( 274 | Carbon $startDate, 275 | Carbon $endDate, 276 | ): array { 277 | $conversions = []; 278 | 279 | for ($date = clone $startDate; $date->lte($endDate); $date->addDay()) { 280 | if ($date->isWeekday()) { 281 | $conversions[$date->format('Y-m-d')] = 1.0; 282 | } 283 | } 284 | 285 | return $conversions; 286 | } 287 | 288 | /** 289 | * Determine whether if the exchange rate should be cached after it is fetched 290 | * from the API. 291 | * 292 | * @param bool $shouldCache 293 | * @return $this 294 | */ 295 | public function shouldCache(bool $shouldCache = true): self 296 | { 297 | $this->shouldCache = $shouldCache; 298 | 299 | return $this; 300 | } 301 | 302 | /** 303 | * Determine whether if the cached result (if it exists) should be deleted. This 304 | * will force a new exchange rate to be fetched from the API. 305 | * 306 | * @param bool $bustCache 307 | * @return $this 308 | */ 309 | public function shouldBustCache(bool $bustCache = true): self 310 | { 311 | $this->shouldBustCache = $bustCache; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * Attempt to fetch an item (more than likely an exchange rate) from the cache. 318 | * If it exists, return it. If it has been specified, bust the cache. 319 | * 320 | * @param string $cacheKey 321 | * @return mixed|null 322 | * 323 | * @throws InvalidArgumentException 324 | */ 325 | public function attemptToResolveFromCache(string $cacheKey): mixed 326 | { 327 | if ($this->shouldBustCache) { 328 | $this->cacheRepository->forget($cacheKey); 329 | $this->shouldBustCache = false; 330 | } elseif ($cachedValue = $this->cacheRepository->getFromCache($cacheKey)) { 331 | return $cachedValue; 332 | } 333 | 334 | return null; 335 | } 336 | 337 | public function attemptToStoreInCache(string $cacheKey, mixed $currencies): void 338 | { 339 | if ($this->shouldCache) { 340 | $this->cacheRepository->storeInCache($cacheKey, $currencies); 341 | } 342 | } 343 | 344 | public function getRequestBuilder(): RequestSender 345 | { 346 | return $this->requestBuilder; 347 | } 348 | 349 | /** 350 | * @throws InvalidCurrencyException 351 | * @throws InvalidDateException 352 | */ 353 | public function validateExchangeRateInput(string $from, array|string $to, ?Carbon $date): void 354 | { 355 | if ($date) { 356 | Validation::validateDate($date); 357 | } 358 | 359 | Validation::validateCurrencyCode($from); 360 | 361 | is_string($to) ? Validation::validateCurrencyCode($to) : Validation::validateCurrencyCodes($to); 362 | } 363 | 364 | /** 365 | * @throws InvalidCurrencyException 366 | * @throws InvalidDateException 367 | */ 368 | public function validateExchangeRateBetweenDateRangeInput(string $from, array|string $to, Carbon $date, Carbon $endDate): void 369 | { 370 | Validation::validateCurrencyCode($from); 371 | Validation::validateStartAndEndDates($date, $endDate); 372 | 373 | is_string($to) ? Validation::validateCurrencyCode($to) : Validation::validateCurrencyCodes($to); 374 | } 375 | 376 | /** 377 | * Use the exchange rates we've just retrieved and convert the given value. 378 | * 379 | * @param float|array $exchangeRates 380 | * @param string|string[] $to 381 | * @param int $value 382 | * @return float|array 383 | */ 384 | public function convertUsingRates(float|array $exchangeRates, string|array $to, int $value): float|array 385 | { 386 | if (is_string($to)) { 387 | return (float) $exchangeRates * $value; 388 | } 389 | 390 | foreach ($exchangeRates as $currency => $exchangeRate) { 391 | $exchangeRates[$currency] = (float) $exchangeRate * $value; 392 | } 393 | 394 | return $exchangeRates; 395 | } 396 | 397 | /** 398 | * Use the exchange rates we've just retrieved and convert the given value 399 | * for each date in the date range. 400 | * 401 | * @param array> $exchangeRates 402 | * @param string|string[] $to 403 | * @param int $value 404 | * @return array> 405 | */ 406 | public function convertUsingRatesForDateRange(array $exchangeRates, string|array $to, int $value): array 407 | { 408 | $conversions = []; 409 | 410 | if (is_array($to)) { 411 | foreach ($exchangeRates as $date => $exchangeRate) { 412 | foreach ($exchangeRate as $currency => $rate) { 413 | $conversions[$date][$currency] = (float) $rate * $value; 414 | } 415 | } 416 | 417 | return $conversions; 418 | } 419 | 420 | foreach ($exchangeRates as $date => $exchangeRate) { 421 | $conversions[$date] = (float) $exchangeRate * $value; 422 | } 423 | 424 | return $conversions; 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Laravel Exchange Rates 3 |

4 | 5 |

6 | Latest Version on Packagist 7 | Total Downloads 8 | PHP from Packagist 9 | GitHub license 10 |

11 | 12 | ## Table of Contents 13 | 14 | - [Overview](#overview) 15 | - [Installation](#installation) 16 | - [Supported APIs](#supported-apis) 17 | - [Configuration](#configuration) 18 | - [Usage](#usage) 19 | - [Methods](#methods) 20 | - [Available Currencies](#available-currencies) 21 | - [Exchange Rate](#exchange-rate) 22 | - [Getting the Rate Between Two Currencies](#getting-the-rate-between-two-currencies) 23 | - [Getting the Rate Between More Than Two Currencies](#getting-the-rate-between-more-than-two-currencies) 24 | - [Exchange Rates Between Date Range](#exchange-rates-between-date-range) 25 | - [Getting the Rates Between Two Currencies](#getting-the-rates-between-two-currencies) 26 | - [Getting the Rates Between More Than Two Currencies](#getting-the-rates-between-more-than-two-currencies) 27 | - [Convert Currencies](#convert-currencies) 28 | - [Converting Between Two Currencies](#converting-between-two-currencies) 29 | - [Converting Between More Than Two Currencies](#converting-between-more-than-two-currencies) 30 | - [Convert Currencies Between Date Range](#convert-currencies-between-date-range) 31 | - [Converting Between Two Currencies in a Date Range](#converting-between-two-currencies-in-a-date-range) 32 | - [Converting Between More Than Two Currencies in a Date Range](#converting-between-more-than-two-currencies-in-a-date-range) 33 | - [Facade](#facade) 34 | - [Drivers](#drivers) 35 | - [Available Drivers](#available-drivers) 36 | - [Validation Rule](#validation-rule) 37 | - [Caching](#caching) 38 | - [Busting Cached Exchange Rates](#busting-cached-exchange-rates) 39 | - [Preventing Exchange Rates from Being Cached](#preventing-exchange-rates-from-being-cached) 40 | - [Supported Currencies](#supported-currencies) 41 | - [Testing](#testing) 42 | - [Security](#security) 43 | - [Contribution](#contribution) 44 | - [Credits](#credits) 45 | - [Changelog](#changelog) 46 | - [Upgrading](#upgrading) 47 | - [License](#license) 48 | 49 | ## Overview 50 | 51 | A simple Laravel package used for interacting with exchange rates APIs. Laravel Exchange Rates allows you to get the latest or historical exchange rates and convert monetary values between different currencies. 52 | 53 | ## Installation 54 | 55 | You can install the package via Composer: 56 | 57 | ```bash 58 | composer require ashallendesign/laravel-exchange-rates 59 | ``` 60 | 61 | The package has been developed and tested to work with the following minimum requirements: 62 | 63 | - PHP 8.0 64 | - Laravel 8 65 | 66 | ## Supported APIs 67 | 68 | Laravel Exchange Rates currently supports the following APIs: 69 | 70 | - [Exchange Rates API](https://exchangeratesapi.io/?fpr=ashley37) 71 | - [Exchange Rates Data API](https://apilayer.com/marketplace/exchangerates_data-api?fpr=ashley37) 72 | - [Exchange Rate Host](https://exchangerate.host/) 73 | - [CurrencyBeacon](https://currencybeacon.com/) 74 | 75 | ## Configuration 76 | 77 | ### Publish the Config File 78 | 79 | You can publish the package's config file using the following command: 80 | ```bash 81 | php artisan vendor:publish --provider="AshAllenDesign\LaravelExchangeRates\Providers\ExchangeRatesProvider" 82 | ``` 83 | 84 | If you're using an API that requires an API key, you can put it in your `.env`: 85 | 86 | ``` dotenv 87 | EXCHANGE_RATES_API_KEY={Your-API-Key-Here} 88 | ``` 89 | 90 | If you're using the free tier of `https://exchangeratesapi.io` or `https://exchangerate.host`, you should set the `https` config option in `config/laravel-exchange-rates.php` to `false`, as the free tiers of those APIs don't allow HTTPS requests. 91 | 92 | ## Usage 93 | 94 | ### Methods 95 | 96 | #### Available Currencies 97 | 98 | To get the available currencies supported by the API, you can use the `currencies` method like so: 99 | 100 | ```php 101 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 102 | 103 | $exchangeRates = app(ExchangeRate::class); 104 | 105 | $exchangeRates->currencies(); 106 | ``` 107 | 108 | #### Exchange Rate 109 | 110 | ##### Getting the Rate Between Two Currencies 111 | 112 | To get the exchange rate for one currency to another, you can use the `exchangeRate` method. When doing this, you can pass the currency code as a string as the second parameter. The `exchangeRate` method will then return a float containing the exchange rate. 113 | 114 | The example below shows how to get the exchange rate from 'GBP' to 'EUR' for today. 115 | 116 | ```php 117 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 118 | 119 | $exchangeRates = app(ExchangeRate::class); 120 | 121 | $result = $exchangeRates->exchangeRate('GBP', 'EUR'); 122 | 123 | // $result: 1.10086 124 | ``` 125 | 126 | Note: If a Carbon date is passed as the third parameter, the exchange rate for that day will be returned (if valid). If no date is passed, today's exchange rate will be used. 127 | 128 | ##### Getting the Rate Between More Than Two Currencies 129 | 130 | It's possible to get the exchange rates for multiple currencies in one call. This can be particularly useful if you are needing to get many exchange rates at once and don't want to make multiple API calls. 131 | 132 | To do this, you can use `exchangeRate` method and pass an array of currency code strings as the second parameter. This will return an array containing the exchange rates as floats. 133 | 134 | The example below shows how to get the exchange rates from 'GBP' to 'EUR' and 'USD' for today: 135 | 136 | ```php 137 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 138 | 139 | $exchangeRates = app(ExchangeRate::class); 140 | 141 | $result = $exchangeRates->exchangeRate('GBP', ['EUR', 'USD']); 142 | 143 | // $result: [ 144 | // 'EUR' => 1.10086, 145 | // 'USD' => 1.25622, 146 | // ]; 147 | ``` 148 | 149 | #### Exchange Rates Between Date Range 150 | 151 | ##### Getting the Rates Between Two Currencies 152 | 153 | To get the exchange rates between two currencies between a given date range, you can use the `exchangeRateBetweenDateRange` method. When doing this, you can pass the currency code as a string as the second parameter. The method will then return an array containing the exchange rates. 154 | 155 | The example below shows how to get the exchange rates from 'GBP' to 'EUR' for the past 3 days: 156 | 157 | ```php 158 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 159 | 160 | $exchangeRates = app(ExchangeRate::class); 161 | 162 | $result = $exchangeRates->exchangeRateBetweenDateRange( 163 | 'GBP', 164 | 'EUR', 165 | Carbon::now()->subWeek(), 166 | Carbon::now() 167 | ); 168 | 169 | // $result: [ 170 | // '2020-07-07' => 1.1092623405 171 | // '2020-07-08' => 1.1120625424 172 | // '2020-07-09' => 1.1153867604 173 | // ]; 174 | ``` 175 | 176 | ##### Getting the Rates Between More Than Two Currencies 177 | 178 | To get the exchange rates for multiple currencies in one call, you can pass an array of currency codes strings as the second parameter to the `exchangeRateBetweenDateRange` method. 179 | 180 | The example below shows how to get the exchange rates from 'GBP' to 'EUR' and 'USD' for the past 3 days.: 181 | 182 | ```php 183 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 184 | 185 | $exchangeRates = app(ExchangeRate::class); 186 | 187 | $result = $exchangeRates->exchangeRateBetweenDateRange( 188 | 'GBP', 189 | ['EUR', 'USD'], 190 | Carbon::now()->subDays(3), 191 | Carbon::now() 192 | ); 193 | 194 | // $result: [ 195 | // '2020-07-07' => [ 196 | // 'EUR' => 1.1092623405, 197 | // 'USD' => 1.2523571825, 198 | // ], 199 | // '2020-07-08' => [ 200 | // 'EUR' => 1.1120625424, 201 | // 'USD' => 1.2550737853, 202 | // ], 203 | // '2020-07-09' => [ 204 | // 'EUR' => 1.1153867604, 205 | // 'USD' => 1.2650716636, 206 | // ], 207 | // ]; 208 | ``` 209 | 210 | #### Convert Currencies 211 | 212 | When passing in the monetary value (first parameter) that is to be converted, it's important that you pass it in the lowest denomination of that currency. For example, £1 GBP would be passed in as 100 (as £1 = 100 pence). 213 | 214 | ##### Converting Between Two Currencies 215 | 216 | Similar to how you can get the exchange rate from one currency to another, you can also convert a monetary value from one currency to another. To do this you can use the `convert()` method. 217 | 218 | The example below shows how to convert £1 'GBP' to 'EUR' at today's exchange rate: 219 | 220 | ```php 221 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 222 | 223 | $exchangeRates = app(ExchangeRate::class); 224 | 225 | $result = $exchangeRates->convert(100, 'GBP', 'EUR', Carbon::now()); 226 | 227 | // $result: 110.15884906 228 | ``` 229 | 230 | Note: If a Carbon date is passed as the third parameter, the exchange rate for that day will be returned (if valid). If no date is passed, today's exchange rate will be used. 231 | 232 | ##### Converting Between More Than Two Currencies 233 | 234 | You can also use the `convert()` method to convert a monetary value from one currency to multiple currencies. To do this, you can pass an array of currency code strings as the third parameter. 235 | 236 | The example below show how to convert £1 'GBP' to 'EUR' and 'USD' at today's exchange rate. 237 | 238 | ```php 239 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 240 | 241 | $exchangeRates = app(ExchangeRate::class); 242 | 243 | $result = $exchangeRates->convert( 244 | 100, 245 | 'GBP', 246 | ['EUR', 'USD'], 247 | Carbon::now() 248 | ); 249 | 250 | // $result: [ 251 | // 'EUR' => 110.15884906, 252 | // 'USD' => 125.30569081 253 | // ]; 254 | ``` 255 | 256 | #### Convert Currencies Between Date Range 257 | 258 | When passing in the monetary value (first parameter) that is to be converted, it's important that you pass it in the lowest denomination of that currency. For example, £1 GBP would be passed in as 100 (as £1 = 100 pence). 259 | 260 | ##### Converting Between Two Currencies in a Date Range 261 | 262 | Similar to getting the exchange rates between a date range, you can also get convert monetary values from one currency to another using the exchange rates. To do this you can use the `convertBetweenDateRange` method. 263 | 264 | The example below shows how to convert £1 'GBP' to 'EUR' using the exchange rates for the past 3 days: 265 | 266 | ```php 267 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 268 | 269 | $exchangeRates = app(ExchangeRate::class); 270 | 271 | $exchangeRates->convertBetweenDateRange( 272 | 100, 273 | 'GBP', 274 | 'EUR', 275 | Carbon::now()->subDays(3), 276 | Carbon::now() 277 | ); 278 | 279 | // $result: [ 280 | // '2020-07-07' => 110.92623405, 281 | // '2020-07-08' => 111.20625424, 282 | // '2020-07-09' => 111.53867604, 283 | // ]; 284 | ``` 285 | 286 | ##### Converting Between More Than Two Currencies in a Date Range 287 | 288 | You can also use the `convertBetweenDateRange` method to convert a monetary value from one currency to multiple currencies 289 | using the exchange rates between a date range. To do this, you can pass an array of currency codes strings as the third parameter. 290 | 291 | The example below show how to convert £1 'GBP' to 'EUR' and 'USD' at the past three days' exchange rates. 292 | 293 | ```php 294 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 295 | 296 | $exchangeRates = app(ExchangeRate::class); 297 | 298 | $result = $exchangeRates->exchangeRateBetweenDateRange( 299 | 'GBP', 300 | ['EUR', 'USD'], 301 | Carbon::now()->subDays(3), 302 | Carbon::now() 303 | ); 304 | 305 | // $result: [ 306 | // '2020-07-07' => [ 307 | // 'EUR' => 110.92623405, 308 | // 'USD' => 125.23571825, 309 | // ], 310 | // '2020-07-08' => [ 311 | // 'EUR' => 111.20625424, 312 | // 'USD' => 125.50737853, 313 | // ], 314 | // '2020-07-09' => [ 315 | // 'EUR' => 111.53867604, 316 | // 'USD' => 126.50716636, 317 | // ], 318 | // ]; 319 | ``` 320 | 321 | ### Facade 322 | 323 | If you prefer to use facades in Laravel, you can choose to use the provided `AshAllenDesign\LaravelExchangeRates\Facades\ExchangeRate` facade instead of instantiating the `AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate` class manually. 324 | 325 | The example below shows an example of how you could use the facade to get the available currencies: 326 | 327 | ```php 328 | use AshAllenDesign\LaravelExchangeRates\Facades\ExchangeRate; 329 | 330 | ExchangeRate::currencies(); 331 | ``` 332 | 333 | ### Drivers 334 | 335 | Laravel Exchange Rates provides multiple drivers so that you can interact with different exchange rate APIs. 336 | 337 | You can select the default driver that you want to use by setting the `driver` config field in the `config/laravel-exchange-rates.php` config file. For example, to use the `exchange-rates-api-io` driver, you could set the following: 338 | 339 | ``` php 340 | 'driver' => 'exchange-rates-api-io', 341 | ``` 342 | 343 | This driver will automatically be used when running methods such as: 344 | 345 | ```php 346 | // Using the "ExchangeRate" class: 347 | $exchangeRates = app(ExchangeRate::class); 348 | $result = $exchangeRates->exchangeRate('GBP', ['EUR', 'USD']); 349 | 350 | // Using the "ExchangeRate" facade: 351 | ExchangeRate::exchangeRate('GBP', ['EUR', 'USD']); 352 | ``` 353 | 354 | However, if you wish to use a different driver, you can use the `driver` method to specify the driver that you want to use. For example, to use the `exchange-rates-data-api` driver, you could use the following: 355 | 356 | ```php 357 | // Using the "ExchangeRate" class: 358 | $exchangeRates = app(ExchangeRate::class); 359 | 360 | $result = $exchangeRates 361 | ->driver('exchange-rates-data-api') 362 | ->exchangeRate('GBP', ['EUR', 'USD']); 363 | 364 | // Using the "ExchangeRate" facade: 365 | ExchangeRate::driver('exchange-rates-data-api') 366 | ->exchangeRate('GBP', ['EUR', 'USD']); 367 | ``` 368 | 369 | #### Available Drivers 370 | 371 | The following drivers are available with the package: 372 | 373 | | API Service | Driver name | API URL | 374 | |-------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| 375 | | Exchange Rates API IO | `exchange-rates-api-io` | [https://exchangeratesapi.io](https://exchangeratesapi.io/?fpr=ashley37) | 376 | | Exchange Rates Data API | `exchange-rates-data-api` | [https://apilayer.com/marketplace/exchangerates_data-api](https://apilayer.com/marketplace/exchangerates_data-api?fpr=ashley37) | 377 | | Exchange Rate Host | `exchange-rate-host` | [https://exchangerate.host](https://exchangerate.host) | 378 | | CurrencyBeacon | `currency-beacon` | [https://currencybeacon.com/](https://currencybeacon.com/) | 379 | 380 | ### Validation Rule 381 | 382 | Laravel Exchange Rates comes with its own `ValidCurrency` rule for validating currencies. This can be useful for if you need to be sure that a currency (maybe one provided by the user) is supported by at least one driver. The example below show how you can use the rule for validating the currency. 383 | 384 | ```php 385 | use AshAllenDesign\LaravelExchangeRates\Rules\ValidCurrency; 386 | use Illuminate\Support\Facades\Validator; 387 | 388 | $formData = [ 389 | 'currency' => 'GBP', 390 | ]; 391 | 392 | $rules = [ 393 | 'currency' => new ValidCurrency(), 394 | ]; 395 | 396 | $validator = Validator::make($formData, $rules); 397 | ``` 398 | 399 | This rule doesn't make a call to any APIs and instead checks an array (provided by the package) of all the currencies that are supported by at least one driver. This means that the rule will be much faster and won't cause issues with rate limits. However, it's possible that the rule may "pass" for a currency that is not supported by the driver that you are using. 400 | 401 | If you'd prefer the validation to be stricter and return only the currencies supported by your chosen driver, you may want to write your own validation rule that makes a call to the API to get a list of supported currencies. 402 | 403 | ### Caching 404 | 405 | #### Busting Cached Exchange Rates 406 | 407 | By default, all responses from the exchange rates APIs are cached. This allows for significant performance improvements and reduced bandwidth from your server. 408 | 409 | However, if for any reason you require a fresh result from the API and not a cached result, the `shouldBustCache` method can be used. The example below shows how to ignore the cached value (if one exists) and make a new API request: 410 | 411 | ```php 412 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 413 | 414 | $exchangeRates = app(ExchangeRate::class); 415 | 416 | $exchangeRates->shouldBustCache() 417 | ->convert( 418 | 100, 419 | 'GBP', 420 | 'EUR', 421 | Carbon::now() 422 | ); 423 | ``` 424 | 425 | #### Preventing Exchange Rates from Being Cached 426 | 427 | It's also possible to prevent the exchange rates from being cached at all. To do this, you can use the `shouldCache(false)` 428 | method. The example below shows how to get an exchange rate and not cache it: 429 | 430 | ```php 431 | use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate; 432 | 433 | $exchangeRates = app(ExchangeRate::class); 434 | 435 | $exchangeRates->shouldCache(false) 436 | ->convert( 437 | 100, 438 | 'GBP', 439 | 'EUR', 440 | Carbon::now() 441 | ); 442 | ``` 443 | 444 | Note: The caching works by storing exchange rates after fetching them from the API. As an example, if you were to fetch 445 | the exchange rates for 'GBP' to 'EUR' for 20-11-2019 - 27-11-2019, the rates between these dates will be cached as a single 446 | cache item. This cache item will only be retrieved if you attempt to fetch the same exchange rates on with the exact same 447 | currencies and date range. 448 | 449 | Therefore, if you were to try and get 'GBP' to 'EUR' for 20-11-2019 - 26-11-2019, a new API request would be made because 450 | the date range is different. 451 | 452 | ### Supported Currencies 453 | 454 | Laravel Exchange Rates supports working with the following currencies (sorted in A-Z order): 455 | 456 | | Code | Currency Name | 457 | |------|-------------------------------------| 458 | | AED | United Arab Emirates Dirham | 459 | | AFN | Afghan Afghani | 460 | | ALL | Albanian Lek | 461 | | AMD | Armenian Dram | 462 | | ANG | Netherlands Antillean Guilder | 463 | | AOA | Angolan Kwanza | 464 | | ARS | Argentine Peso | 465 | | AUD | Australian Dollar | 466 | | AWG | Aruban Florin | 467 | | AZN | Azerbaijani Manat | 468 | | BAM | Bosnia-Herzegovina Convertible Mark | 469 | | BBD | Barbadian Dollar | 470 | | BDT | Bangladeshi Taka | 471 | | BGN | Bulgarian Lev | 472 | | BHD | Bahraini Dinar | 473 | | BIF | Burundian Franc | 474 | | BMD | Bermudan Dollar | 475 | | BND | Brunei Dollar | 476 | | BOB | Bolivian Boliviano | 477 | | BRL | Brazilian Real | 478 | | BSD | Bahamian Dollar | 479 | | BTC | Bitcoin | 480 | | BTN | Bhutanese Ngultrum | 481 | | BWP | Botswanan Pula | 482 | | BYR | Belarusian Ruble | 483 | | BZD | Belize Dollar | 484 | | CAD | Canadian Dollar | 485 | | CDF | Congolese Franc | 486 | | CHF | Swiss Franc | 487 | | CLF | Chilean Unit of Account (UF) | 488 | | CLP | Chilean Peso | 489 | | CNY | Chinese Yuan | 490 | | COP | Colombian Peso | 491 | | CRC | Costa Rican Colón | 492 | | CUC | Cuban Convertible Peso | 493 | | CUP | Cuban Peso | 494 | | CVE | Cape Verdean Escudo | 495 | | CZK | Czech Republic Koruna | 496 | | DJF | Djiboutian Franc | 497 | | DKK | Danish Krone | 498 | | DOP | Dominican Peso | 499 | | DZD | Algerian Dinar | 500 | | EGP | Egyptian Pound | 501 | | ERN | Eritrean Nakfa | 502 | | ETB | Ethiopian Birr | 503 | | EUR | Euro | 504 | | FJD | Fijian Dollar | 505 | | FKP | Falkland Islands Pound | 506 | | GBP | British Pound Sterling | 507 | | GEL | Georgian Lari | 508 | | GGP | Guernsey Pound | 509 | | GHS | Ghanaian Cedi | 510 | | GIP | Gibraltar Pound | 511 | | GMD | Gambian Dalasi | 512 | | GNF | Guinean Franc | 513 | | GTQ | Guatemalan Quetzal | 514 | | GYD | Guyanaese Dollar | 515 | | HKD | Hong Kong Dollar | 516 | | HNL | Honduran Lempira | 517 | | HRK | Croatian Kuna | 518 | | HTG | Haitian Gourde | 519 | | HUF | Hungarian Forint | 520 | | IDR | Indonesian Rupiah | 521 | | ILS | Israeli New Sheqel | 522 | | IMP | Manx pound | 523 | | INR | Indian Rupee | 524 | | IQD | Iraqi Dinar | 525 | | IRR | Iranian Rial | 526 | | ISK | Icelandic Króna | 527 | | JEP | Jersey Pound | 528 | | JMD | Jamaican Dollar | 529 | | JOD | Jordanian Dinar | 530 | | JPY | Japanese Yen | 531 | | KES | Kenyan Shilling | 532 | | KGS | Kyrgystani Som | 533 | | KHR | Cambodian Riel | 534 | | KMF | Comorian Franc | 535 | | KPW | North Korean Won | 536 | | KRW | South Korean Won | 537 | | KWD | Kuwaiti Dinar | 538 | | KYD | Cayman Islands Dollar | 539 | | KZT | Kazakhstani Tenge | 540 | | LAK | Laotian Kip | 541 | | LBP | Lebanese Pound | 542 | | LKR | Sri Lankan Rupee | 543 | | LRD | Liberian Dollar | 544 | | LSL | Lesotho Loti | 545 | | LTL | Lithuanian Litas | 546 | | LVL | Latvian Lats | 547 | | LYD | Libyan Dinar | 548 | | MAD | Moroccan Dirham | 549 | | MDL | Moldovan Leu | 550 | | MGA | Malagasy Ariary | 551 | | MKD | Macedonian Denar | 552 | | MMK | Myanma Kyat | 553 | | MNT | Mongolian Tugrik | 554 | | MOP | Macanese Pataca | 555 | | MRO | Mauritanian Ouguiya | 556 | | MUR | Mauritian Rupee | 557 | | MVR | Maldivian Rufiyaa | 558 | | MWK | Malawian Kwacha | 559 | | MXN | Mexican Peso | 560 | | MYR | Malaysian Ringgit | 561 | | MZN | Mozambican Metical | 562 | | NAD | Namibian Dollar | 563 | | NGN | Nigerian Naira | 564 | | NIO | Nicaraguan Córdoba | 565 | | NOK | Norwegian Krone | 566 | | NPR | Nepalese Rupee | 567 | | NZD | New Zealand Dollar | 568 | | OMR | Omani Rial | 569 | | PAB | Panamanian Balboa | 570 | | PEN | Peruvian Nuevo Sol | 571 | | PGK | Papua New Guinean Kina | 572 | | PHP | Philippine Peso | 573 | | PKR | Pakistani Rupee | 574 | | PLN | Polish Zloty | 575 | | PYG | Paraguayan Guarani | 576 | | QAR | Qatari Rial | 577 | | RON | Romanian Leu | 578 | | RSD | Serbian Dinar | 579 | | RUB | Russian Ruble | 580 | | RWF | Rwandan Franc | 581 | | SAR | Saudi Riyal | 582 | | SBD | Solomon Islands Dollar | 583 | | SCR | Seychellois Rupee | 584 | | SDG | Sudanese Pound | 585 | | SEK | Swedish Krona | 586 | | SGD | Singapore Dollar | 587 | | SHP | Saint Helena Pound | 588 | | SLL | Sierra Leonean Leone | 589 | | SOS | Somali Shilling | 590 | | SRD | Surinamese Dollar | 591 | | STD | São Tomé and Príncipe Dobra | 592 | | SVC | Salvadoran Colón | 593 | | SYP | Syrian Pound | 594 | | SZL | Swazi Lilangeni | 595 | | THB | Thai Baht | 596 | | TJS | Tajikistani Somoni | 597 | | TMT | Turkmenistani Manat | 598 | | TND | Tunisian Dinar | 599 | | TOP | Tongan Paʻanga | 600 | | TRY | Turkish Lira | 601 | | TTD | Trinidad and Tobago Dollar | 602 | | TWD | New Taiwan Dollar | 603 | | TZS | Tanzanian Shilling | 604 | | UAH | Ukrainian Hryvnia | 605 | | UGX | Ugandan Shilling | 606 | | USD | United States Dollar | 607 | | UYU | Uruguayan Peso | 608 | | UZS | Uzbekistan Som | 609 | | VEF | Venezuelan Bolívar Fuerte | 610 | | VND | Vietnamese Dong | 611 | | VUV | Vanuatu Vatu | 612 | | WST | Samoan Tala | 613 | | XAF | CFA Franc BEAC | 614 | | XAG | Silver (troy ounce) | 615 | | XAU | Gold (troy ounce) | 616 | | XCD | East Caribbean Dollar | 617 | | XDR | Special Drawing Rights | 618 | | XOF | CFA Franc BCEAO | 619 | | XPF | CFP Franc | 620 | | YER | Yemeni Rial | 621 | | ZAR | South African Rand | 622 | | ZMK | Zambian Kwacha (pre-2013) | 623 | | ZMW | Zambian Kwacha | 624 | | ZWL | Zimbabwean Dollar | 625 | 626 | ## Testing 627 | 628 | If you are contributing new changes to the package, you can run the tests by running the following command from the packages's root folder: 629 | 630 | ```bash 631 | composer test 632 | ``` 633 | 634 | ## Security 635 | 636 | If you find any security related issues, please contact me directly at [mail@ashallendesign.co.uk](mailto:mail@ashallendesign.co.uk) to report it. 637 | 638 | ## Contribution 639 | 640 | If you wish to make any changes or improvements to the package, feel free to make a pull request. 641 | 642 | To contribute to this library, please use the following guidelines before submitting your pull request: 643 | 644 | - Write tests for any new functions that are added. If you are updating existing code, make sure that the existing tests 645 | pass and write more if needed. 646 | - Follow [PSR-2](https://www.php-fig.org/psr/psr-2/) coding standards. 647 | - Make all pull requests to the `master` branch. 648 | 649 | ## Credits 650 | 651 | - [Ash Allen](https://ashallendesign.co.uk) 652 | - [Zak](https://github.com/thugic) 653 | - [Jess Pickup](https://jesspickup.co.uk) (Logo) 654 | - [All Contributors](https://github.com/ash-jc-allen/short-url/graphs/contributors) 655 | 656 | ## Changelog 657 | 658 | Check the [CHANGELOG](CHANGELOG.md) to get more information about the latest changes. 659 | 660 | ## Upgrading 661 | 662 | Check the [UPGRADE](UPGRADE.md) guide to get more information on how to update this library to newer versions. 663 | 664 | ## License 665 | 666 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 667 | 668 | ## Support Me 669 | 670 | If you've found this package useful, please consider buying a copy of [Battle Ready Laravel](https://battle-ready-laravel.com) to support me and my work. 671 | 672 | Every sale makes a huge difference to me and allows me to spend more time working on open-source projects and tutorials. 673 | 674 | To say a huge thanks, you can use the code **BATTLE20** to get a 20% discount on the book. 675 | 676 | [👉 Get Your Copy!](https://battle-ready-laravel.com) 677 | 678 | [![Battle Ready Laravel](https://ashallendesign.co.uk/images/custom/sponsors/battle-ready-laravel-horizontal-banner.png)](https://battle-ready-laravel.com) 679 | --------------------------------------------------------------------------------