Skip to content

Commit

Permalink
Merge pull request #19 from Saifallak/main
Browse files Browse the repository at this point in the history
Support api.getgeoapi.com.
  • Loading branch information
owenvoke authored Mar 22, 2024
2 parents 30b2f74 + 1bb1cf4 commit 2a1a9e2
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
# Fixer and ExchangeRate.host use the same API token
FIXER_ACCESS_KEY: ${{ secrets.FIXER_ACCESS_KEY }}
EXCHANGE_RATE_ACCESS_KEY: ${{ secrets.FIXER_ACCESS_KEY }}
CURRENCY_GEO_ACCESS_KEY: ${{ secrets.CURRENCY_GEO_ACCESS_KEY }}
strategy:
fail-fast: true
matrix:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ Set `EXCHANGE_RATE_ACCESS_KEY` to your provided access key from exchangerate.hos
With that task completed, you're ready to start using [exchangerate.host](https://exchangerate.host) for retrieving up-to-date
exchange rates.

### Currency.GetGeoApi.com

[Currency.GetGeoApi.com](https://currency.getgeoapi.com) is an alternative option you can use with a free quota.

In your `exchange.php` config file, set `default` to `currency_geo`, or set `EXCHANGE_DRIVER` to `currency_geo` in your `.env` file.
Set `CURRENCY_GEO_ACCESS_KEY` to your provided access key from currency.getgeoapi.com.

With that task completed, you're ready to start using [Currency.GetGeoApi.com](https://currency.getgeoapi.com) for retrieving up-to-date
exchange rates.

### Frankfurter.app

[frankfurter.app](https://frankfurter.app) is an open-source API for current and historical foreign exchange rates published by the European Central Bank, which can be used without an API key.
Expand Down
17 changes: 16 additions & 1 deletion config/exchange.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Go ahead and select a default exchange driver to be used when
* looking up exchange rates.
*
* Supported: 'null', 'fixer', 'exchange_rate', 'frankfurter', 'cache'
* Supported: 'null', 'fixer', 'exchange_rate', 'frankfurter', 'currency_geo', 'cache'
*/

'default' => env('EXCHANGE_DRIVER', 'frankfurter'),
Expand Down Expand Up @@ -45,6 +45,21 @@
'access_key' => env('EXCHANGE_RATE_ACCESS_KEY'),
],

/*
|--------------------------------------------------------------------------
| CurrencyGeo.com
|--------------------------------------------------------------------------
|
| CurrencyGeo is a paid service for converting currency codes. To use CurrencyGeo, you'll
| need an API Access Key from the CurrencyGeo dashboard. Set that here, and then change
| the 'default' to 'currency_geo' or set EXCHANGE_DRIVER to 'currency_geo'.
|
*/

'currency_geo' => [
'access_key' => env('CURRENCY_GEO_ACCESS_KEY'),
],

/*
|--------------------------------------------------------------------------
| Cache
Expand Down
73 changes: 73 additions & 0 deletions src/ExchangeRateProviders/CurrencyGEOProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Worksome\Exchange\ExchangeRateProviders;

use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Collection;
use Worksome\Exchange\Contracts\ExchangeRateProvider;
use Worksome\Exchange\Support\Rates;

final class CurrencyGEOProvider implements ExchangeRateProvider
{
public function __construct(
private Factory $client,
private string $accessKey,
private string $baseUrl = 'https://api.getgeoapi.com/v2',
) {
}

/**
* @throws RequestException
*/
public function getRates(string $baseCurrency, array $currencies): Rates
{
// If it's the same currency return it.
if (count($currencies) === 1 && $baseCurrency === $currencies[0]) {
return new Rates(
$baseCurrency,
[$baseCurrency => 1],
now()->startOfDay(),
);
}

$data = $this->makeRequest($baseCurrency, $currencies);

return new Rates(
$baseCurrency,
// @phpstan-ignore-next-line
collect($data->get('rates'))->map(fn (mixed $value) => floatval($value['rate']))->all(),
now()->startOfDay(),
);
}

/**
* @param array<int, string> $currencies
*
* @return Collection<string, mixed>
*
* @throws RequestException
*/
private function makeRequest(string $baseCurrency, array $currencies): Collection
{
return $this->client()
->get('/currency/convert', [
'api_key' => $this->accessKey,
'from' => $baseCurrency,
'to' => implode(',', $currencies),
])
->throw()
->collect();
}

private function client(): PendingRequest
{
return $this->client
->baseUrl($this->baseUrl)
->asJson()
->acceptJson();
}
}
15 changes: 15 additions & 0 deletions src/Support/ExchangeRateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Support\Manager;
use Worksome\Exchange\Exceptions\InvalidConfigurationException;
use Worksome\Exchange\ExchangeRateProviders\CachedProvider;
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
use Worksome\Exchange\ExchangeRateProviders\ExchangeRateHostProvider;
use Worksome\Exchange\ExchangeRateProviders\FixerProvider;
use Worksome\Exchange\ExchangeRateProviders\FrankfurterProvider;
Expand Down Expand Up @@ -54,6 +55,20 @@ public function createExchangeRateDriver(): ExchangeRateHostProvider
);
}

public function createCurrencyGeoDriver(): CurrencyGEOProvider
{
$apiKey = $this->config->get('exchange.services.currency_geo.access_key');

throw_unless(is_string($apiKey), new InvalidConfigurationException(
'You haven\'t set up an API key for CurrencyGEO!'
));

return new CurrencyGEOProvider(
$this->container->make(Factory::class),
$apiKey,
);
}

public function createFrankfurterDriver(): FrankfurterProvider
{
return new FrankfurterProvider(
Expand Down
10 changes: 10 additions & 0 deletions tests/Feature/Support/ExchangeRateManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Worksome\Exchange\Contracts\ExchangeRateProvider;
use Worksome\Exchange\Exceptions\InvalidConfigurationException;
use Worksome\Exchange\ExchangeRateProviders\CachedProvider;
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
use Worksome\Exchange\ExchangeRateProviders\ExchangeRateHostProvider;
use Worksome\Exchange\ExchangeRateProviders\FixerProvider;
use Worksome\Exchange\ExchangeRateProviders\FrankfurterProvider;
Expand All @@ -12,6 +13,7 @@
beforeEach(function () {
config()->set('exchange.services.fixer.access_key', 'password');
config()->set('exchange.services.exchange_rate.access_key', 'password');
config()->set('exchange.services.currency_geo.access_key', 'password');
});

it('can instantiate all drivers', function (string $driver, string $expectedClass) {
Expand All @@ -25,6 +27,7 @@
['fixer', FixerProvider::class],
['exchange_rate', ExchangeRateHostProvider::class],
['frankfurter', FrankfurterProvider::class],
['currency_geo', CurrencyGEOProvider::class],
['cache', CachedProvider::class],
]);

Expand All @@ -43,3 +46,10 @@
$manager = new ExchangeRateManager($this->app);
$manager->driver('fixer');
})->throws(InvalidConfigurationException::class, 'You haven\'t set up an API key for Fixer!');

it('will throw the right exception if no geo currency API key has been configured', function () {
config()->set('exchange.services.currency_geo.access_key', null);

$manager = new ExchangeRateManager($this->app);
$manager->driver('currency_geo');
})->throws(InvalidConfigurationException::class, 'You haven\'t set up an API key for CurrencyGEO!');
93 changes: 93 additions & 0 deletions tests/Unit/ExchangeRateProviders/CurrencyGEOProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

use Carbon\Carbon;
use GuzzleHttp\Promise\Create;
use GuzzleHttp\Psr7\Response;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\RequestException;
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
use Worksome\Exchange\Support\Rates;

it('is able to make a real call to the API', function () {
$client = new Factory();
$currencyGeoProvider = new CurrencyGEOProvider($client, getenv('CURRENCY_GEO_ACCESS_KEY'));
$rates = $currencyGeoProvider->getRates('EUR', currencies());

expect($rates)->toBeInstanceOf(Rates::class);
})
->skip(getenv('CURRENCY_GEO_ACCESS_KEY') === false, 'No CURRENCY_GEO_ACCESS_KEY was defined.')
->group('integration');

it('makes a HTTP request to the correct endpoint', function () {
$client = new Factory();
$client->fake(['*' => [
'timestamp' => now()->subDay()->timestamp,
'rates' => [
'EUR' => [
'currency_name' => 'Euro',
'rate' => 1,
'rate_for_amount' => 1,
],
'GBP' => [
'currency_name' => 'Pound sterling',
'rate' => 2.5,
'rate_for_amount' => 2.5,
],
],
]]);

$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
$currencyGeoProvider->getRates('EUR', currencies());

$client->assertSent(function (Request $request) {
return str_starts_with($request->url(), 'https://api.getgeoapi.com/v2/currency/convert');
});
});

it('returns floats for all rates', function () {
$client = new Factory();
$client->fake(['*' => [
'timestamp' => now()->subDay()->timestamp,
'rates' => [
'EUR' => [
'currency_name' => 'Euro',
'rate' => 1,
'rate_for_amount' => 1,
],
'GBP' => [
'currency_name' => 'Pound sterling',
'rate' => 2.5,
'rate_for_amount' => 2.5,
],
],
]]);

$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
$rates = $currencyGeoProvider->getRates('EUR', currencies());

expect($rates->getRates())->each->toBeFloat();
});

it('sets the returned timestamp as the retrievedAt timestamp', function () {
Carbon::setTestNow(now());

$client = new Factory();
$client->fake(['*' => [
'timestamp' => now()->subDay()->timestamp,
'rates' => [],
]]);

$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
$rates = $currencyGeoProvider->getRates('EUR', currencies());

expect($rates->getRetrievedAt()->timestamp)->toBe(now()->startOfDay()->timestamp);
});

it('throws a RequestException if a 500 error occurs', function () {
$client = new Factory();
$client->fake(['*' => Create::promiseFor(new Response(500))]);

$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
$currencyGeoProvider->getRates('EUR', currencies());
})->throws(RequestException::class);

0 comments on commit 2a1a9e2

Please sign in to comment.