diff --git a/README.md b/README.md index 273377a..0946c16 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Here is the complete list of the currently implemented services: | [Xignite](https://www.xignite.com) | * | * | Yes | | [Currency Converter API](https://www.currencyconverterapi.com) | * | * | Yes (free but limited or paid) | | [xChangeApi.com](https://xchangeapi.com) | * | * | Yes | +| [fastFOREX.io](https://www.fastforex.io) | USD (free), * (paid) | * | No | | Array | * | * | Yes | ## Credits diff --git a/src/Service/FastForex.php b/src/Service/FastForex.php new file mode 100644 index 0000000..63a9919 --- /dev/null +++ b/src/Service/FastForex.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Exchanger\Service; + +use Exchanger\Contract\CurrencyPair; +use Exchanger\Contract\ExchangeRateQuery; +use Exchanger\Contract\HistoricalExchangeRateQuery; +use Exchanger\Exception\Exception; +use Exchanger\Exception\UnsupportedCurrencyPairException; +use Exchanger\ExchangeRate; +use Exchanger\StringUtil; +use Exchanger\Contract\ExchangeRate as ExchangeRateContract; + +/** + * fastFOREX.io Service. + * + * @author Tom + */ +final class FastForex extends HttpService +{ + + const FETCH_ONE_URL = 'https://api.fastforex.io/fetch-one?from=%s&to=%s&api_key=%s'; + + /** + * {@inheritdoc} + */ + public function processOptions(array &$options): void + { + if (!isset($options['api_key'])) { + throw new \InvalidArgumentException('The "api_key" option must be provided.'); + } + } + + /** + * {@inheritdoc} + */ + public function supportQuery(ExchangeRateQuery $exchangeQuery): bool + { + return !$exchangeQuery instanceof \Exchanger\HistoricalExchangeRateQuery; + } + + /** + * {@inheritdoc} + */ + public function getExchangeRate(ExchangeRateQuery $exchangeRateQuery): ExchangeRateContract + { + $currencyPair = $exchangeRateQuery->getCurrencyPair(); + $baseCurrency = $currencyPair->getBaseCurrency(); + $quoteCurrency = $currencyPair->getQuoteCurrency(); + $url = sprintf(self::FETCH_ONE_URL, $baseCurrency, $quoteCurrency, $this->options['api_key']); + + $content = $this->request($url); + $result = StringUtil::jsonToArray($content); + + if (!isset($result['error'])) { + try { + $date = new \DateTime($result['updated']); + } catch (\Throwable $thrown) { + $date = new \DateTime(); + } + if (isset($result['base']) && $result['base'] == $baseCurrency) { + if (isset($result['result'][$quoteCurrency])) { + return $this->createRate($currencyPair, (float)($result['result'][$quoteCurrency]), $date); + } + } + } + + throw new UnsupportedCurrencyPairException($currencyPair, $this); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'fastforex'; + } + +} diff --git a/src/Service/Registry.php b/src/Service/Registry.php index 4a0c967..c6d8541 100644 --- a/src/Service/Registry.php +++ b/src/Service/Registry.php @@ -46,6 +46,7 @@ public static function getServices(): array 'national_bank_of_ukraine' => NationalBankOfUkraine::class, 'coin_layer' => CoinLayer::class, 'xchangeapi' => XchangeApi::class, + 'fastforex' => FastForex::class, ]; } } diff --git a/tests/Fixtures/Service/FastForex/error-unsupported.json b/tests/Fixtures/Service/FastForex/error-unsupported.json new file mode 100644 index 0000000..62e7b16 --- /dev/null +++ b/tests/Fixtures/Service/FastForex/error-unsupported.json @@ -0,0 +1,3 @@ +{ + "error": "Invalid/unsupported target currency: [ZZZ]" +} diff --git a/tests/Fixtures/Service/FastForex/one-usd-eur.json b/tests/Fixtures/Service/FastForex/one-usd-eur.json new file mode 100644 index 0000000..607448d --- /dev/null +++ b/tests/Fixtures/Service/FastForex/one-usd-eur.json @@ -0,0 +1,8 @@ +{ + "base": "USD", + "result": { + "EUR": 0.8258 + }, + "updated": "2021-02-16 17:28:56", + "ms": 4 +} diff --git a/tests/Tests/Service/FastForexTest.php b/tests/Tests/Service/FastForexTest.php new file mode 100644 index 0000000..0bcf797 --- /dev/null +++ b/tests/Tests/Service/FastForexTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Exchanger\Tests\Service; + +use Exchanger\Exception\Exception; +use Exchanger\HistoricalExchangeRateQuery; +use Exchanger\CurrencyPair; +use Exchanger\ExchangeRateQuery; +use Exchanger\Service\FastForex; + +class FastForexTest extends ServiceTestCase +{ + /** + * @test + */ + public function it_does_not_support_all_queries() + { + $service = new FastForex($this->createMock('Http\Client\HttpClient'), null, ['api_key' => 'secret']); + + $this->assertTrue($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('EUR/USD')))); + $this->assertFalse($service->supportQuery(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/USD'), new \DateTime()))); + } + + /** + * @test + */ + public function it_throws_an_exception_when_rate_not_supported() + { + $this->expectException(Exception::class); + $url = 'https://api.fastforex.io/fetch-one?from=EUR&to=ZZZ&api_key=secret'; + $content = file_get_contents(__DIR__.'/../../Fixtures/Service/FastForex/error-unsupported.json'); + $service = new FastForex($this->getHttpAdapterMock($url, $content), null, ['api_key' => 'secret']); + + $service->getExchangeRate(new ExchangeRateQuery(CurrencyPair::createFromString('EUR/ZZZ'))); + } + + /** + * @test + */ + public function it_fetches_a_rate_when_response_symbol_matches() + { + $pair = CurrencyPair::createFromString('USD/EUR'); + $url = 'https://api.fastforex.io/fetch-one?from=USD&to=EUR&api_key=secret'; + $content = file_get_contents(__DIR__.'/../../Fixtures/Service/FastForex/one-usd-eur.json'); + $service = new FastForex($this->getHttpAdapterMock($url, $content), null, ['api_key' => 'secret']); + + $rate = $service->getExchangeRate(new ExchangeRateQuery($pair)); + $this->assertSame(0.8258, $rate->getValue()); + $this->assertEquals('2021-02-16', $rate->getDate()->format('Y-m-d')); + $this->assertEquals('fastforex', $rate->getProviderName()); + $this->assertSame($pair, $rate->getCurrencyPair()); + } + + /** + * @test + */ + public function it_throws_an_exception_when_response_symbol_does_not_match() + { + $this->expectException(Exception::class); + $url = 'https://api.fastforex.io/fetch-one?from=USD&to=AED&api_key=secret'; + $content = file_get_contents(__DIR__.'/../../Fixtures/Service/FastForex/one-usd-eur.json'); + $service = new FastForex($this->getHttpAdapterMock($url, $content), null, ['api_key' => 'secret']); + + $service->getExchangeRate(new ExchangeRateQuery(CurrencyPair::createFromString('USD/AED'))); + } + + /** + * @test + */ + public function it_has_a_name() + { + $service = new FastForex($this->createMock('Http\Client\HttpClient'), null, ['api_key' => 'secret']); + + $this->assertSame('fastforex', $service->getName()); + } +}