Skip to content

Commit 2a1a9e2

Browse files
authored
Merge pull request #19 from Saifallak/main
Support api.getgeoapi.com.
2 parents 30b2f74 + 1bb1cf4 commit 2a1a9e2

File tree

7 files changed

+218
-1
lines changed

7 files changed

+218
-1
lines changed

.github/workflows/tests.yml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
# Fixer and ExchangeRate.host use the same API token
1414
FIXER_ACCESS_KEY: ${{ secrets.FIXER_ACCESS_KEY }}
1515
EXCHANGE_RATE_ACCESS_KEY: ${{ secrets.FIXER_ACCESS_KEY }}
16+
CURRENCY_GEO_ACCESS_KEY: ${{ secrets.CURRENCY_GEO_ACCESS_KEY }}
1617
strategy:
1718
fail-fast: true
1819
matrix:

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ Set `EXCHANGE_RATE_ACCESS_KEY` to your provided access key from exchangerate.hos
7575
With that task completed, you're ready to start using [exchangerate.host](https://exchangerate.host) for retrieving up-to-date
7676
exchange rates.
7777

78+
### Currency.GetGeoApi.com
79+
80+
[Currency.GetGeoApi.com](https://currency.getgeoapi.com) is an alternative option you can use with a free quota.
81+
82+
In your `exchange.php` config file, set `default` to `currency_geo`, or set `EXCHANGE_DRIVER` to `currency_geo` in your `.env` file.
83+
Set `CURRENCY_GEO_ACCESS_KEY` to your provided access key from currency.getgeoapi.com.
84+
85+
With that task completed, you're ready to start using [Currency.GetGeoApi.com](https://currency.getgeoapi.com) for retrieving up-to-date
86+
exchange rates.
87+
7888
### Frankfurter.app
7989

8090
[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.

config/exchange.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Go ahead and select a default exchange driver to be used when
99
* looking up exchange rates.
1010
*
11-
* Supported: 'null', 'fixer', 'exchange_rate', 'frankfurter', 'cache'
11+
* Supported: 'null', 'fixer', 'exchange_rate', 'frankfurter', 'currency_geo', 'cache'
1212
*/
1313

1414
'default' => env('EXCHANGE_DRIVER', 'frankfurter'),
@@ -45,6 +45,21 @@
4545
'access_key' => env('EXCHANGE_RATE_ACCESS_KEY'),
4646
],
4747

48+
/*
49+
|--------------------------------------------------------------------------
50+
| CurrencyGeo.com
51+
|--------------------------------------------------------------------------
52+
|
53+
| CurrencyGeo is a paid service for converting currency codes. To use CurrencyGeo, you'll
54+
| need an API Access Key from the CurrencyGeo dashboard. Set that here, and then change
55+
| the 'default' to 'currency_geo' or set EXCHANGE_DRIVER to 'currency_geo'.
56+
|
57+
*/
58+
59+
'currency_geo' => [
60+
'access_key' => env('CURRENCY_GEO_ACCESS_KEY'),
61+
],
62+
4863
/*
4964
|--------------------------------------------------------------------------
5065
| Cache
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Worksome\Exchange\ExchangeRateProviders;
6+
7+
use Illuminate\Http\Client\Factory;
8+
use Illuminate\Http\Client\PendingRequest;
9+
use Illuminate\Http\Client\RequestException;
10+
use Illuminate\Support\Collection;
11+
use Worksome\Exchange\Contracts\ExchangeRateProvider;
12+
use Worksome\Exchange\Support\Rates;
13+
14+
final class CurrencyGEOProvider implements ExchangeRateProvider
15+
{
16+
public function __construct(
17+
private Factory $client,
18+
private string $accessKey,
19+
private string $baseUrl = 'https://api.getgeoapi.com/v2',
20+
) {
21+
}
22+
23+
/**
24+
* @throws RequestException
25+
*/
26+
public function getRates(string $baseCurrency, array $currencies): Rates
27+
{
28+
// If it's the same currency return it.
29+
if (count($currencies) === 1 && $baseCurrency === $currencies[0]) {
30+
return new Rates(
31+
$baseCurrency,
32+
[$baseCurrency => 1],
33+
now()->startOfDay(),
34+
);
35+
}
36+
37+
$data = $this->makeRequest($baseCurrency, $currencies);
38+
39+
return new Rates(
40+
$baseCurrency,
41+
// @phpstan-ignore-next-line
42+
collect($data->get('rates'))->map(fn (mixed $value) => floatval($value['rate']))->all(),
43+
now()->startOfDay(),
44+
);
45+
}
46+
47+
/**
48+
* @param array<int, string> $currencies
49+
*
50+
* @return Collection<string, mixed>
51+
*
52+
* @throws RequestException
53+
*/
54+
private function makeRequest(string $baseCurrency, array $currencies): Collection
55+
{
56+
return $this->client()
57+
->get('/currency/convert', [
58+
'api_key' => $this->accessKey,
59+
'from' => $baseCurrency,
60+
'to' => implode(',', $currencies),
61+
])
62+
->throw()
63+
->collect();
64+
}
65+
66+
private function client(): PendingRequest
67+
{
68+
return $this->client
69+
->baseUrl($this->baseUrl)
70+
->asJson()
71+
->acceptJson();
72+
}
73+
}

src/Support/ExchangeRateManager.php

+15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Support\Manager;
1010
use Worksome\Exchange\Exceptions\InvalidConfigurationException;
1111
use Worksome\Exchange\ExchangeRateProviders\CachedProvider;
12+
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
1213
use Worksome\Exchange\ExchangeRateProviders\ExchangeRateHostProvider;
1314
use Worksome\Exchange\ExchangeRateProviders\FixerProvider;
1415
use Worksome\Exchange\ExchangeRateProviders\FrankfurterProvider;
@@ -54,6 +55,20 @@ public function createExchangeRateDriver(): ExchangeRateHostProvider
5455
);
5556
}
5657

58+
public function createCurrencyGeoDriver(): CurrencyGEOProvider
59+
{
60+
$apiKey = $this->config->get('exchange.services.currency_geo.access_key');
61+
62+
throw_unless(is_string($apiKey), new InvalidConfigurationException(
63+
'You haven\'t set up an API key for CurrencyGEO!'
64+
));
65+
66+
return new CurrencyGEOProvider(
67+
$this->container->make(Factory::class),
68+
$apiKey,
69+
);
70+
}
71+
5772
public function createFrankfurterDriver(): FrankfurterProvider
5873
{
5974
return new FrankfurterProvider(

tests/Feature/Support/ExchangeRateManagerTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use Worksome\Exchange\Contracts\ExchangeRateProvider;
44
use Worksome\Exchange\Exceptions\InvalidConfigurationException;
55
use Worksome\Exchange\ExchangeRateProviders\CachedProvider;
6+
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
67
use Worksome\Exchange\ExchangeRateProviders\ExchangeRateHostProvider;
78
use Worksome\Exchange\ExchangeRateProviders\FixerProvider;
89
use Worksome\Exchange\ExchangeRateProviders\FrankfurterProvider;
@@ -12,6 +13,7 @@
1213
beforeEach(function () {
1314
config()->set('exchange.services.fixer.access_key', 'password');
1415
config()->set('exchange.services.exchange_rate.access_key', 'password');
16+
config()->set('exchange.services.currency_geo.access_key', 'password');
1517
});
1618

1719
it('can instantiate all drivers', function (string $driver, string $expectedClass) {
@@ -25,6 +27,7 @@
2527
['fixer', FixerProvider::class],
2628
['exchange_rate', ExchangeRateHostProvider::class],
2729
['frankfurter', FrankfurterProvider::class],
30+
['currency_geo', CurrencyGEOProvider::class],
2831
['cache', CachedProvider::class],
2932
]);
3033

@@ -43,3 +46,10 @@
4346
$manager = new ExchangeRateManager($this->app);
4447
$manager->driver('fixer');
4548
})->throws(InvalidConfigurationException::class, 'You haven\'t set up an API key for Fixer!');
49+
50+
it('will throw the right exception if no geo currency API key has been configured', function () {
51+
config()->set('exchange.services.currency_geo.access_key', null);
52+
53+
$manager = new ExchangeRateManager($this->app);
54+
$manager->driver('currency_geo');
55+
})->throws(InvalidConfigurationException::class, 'You haven\'t set up an API key for CurrencyGEO!');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
use Carbon\Carbon;
4+
use GuzzleHttp\Promise\Create;
5+
use GuzzleHttp\Psr7\Response;
6+
use Illuminate\Http\Client\Factory;
7+
use Illuminate\Http\Client\Request;
8+
use Illuminate\Http\Client\RequestException;
9+
use Worksome\Exchange\ExchangeRateProviders\CurrencyGEOProvider;
10+
use Worksome\Exchange\Support\Rates;
11+
12+
it('is able to make a real call to the API', function () {
13+
$client = new Factory();
14+
$currencyGeoProvider = new CurrencyGEOProvider($client, getenv('CURRENCY_GEO_ACCESS_KEY'));
15+
$rates = $currencyGeoProvider->getRates('EUR', currencies());
16+
17+
expect($rates)->toBeInstanceOf(Rates::class);
18+
})
19+
->skip(getenv('CURRENCY_GEO_ACCESS_KEY') === false, 'No CURRENCY_GEO_ACCESS_KEY was defined.')
20+
->group('integration');
21+
22+
it('makes a HTTP request to the correct endpoint', function () {
23+
$client = new Factory();
24+
$client->fake(['*' => [
25+
'timestamp' => now()->subDay()->timestamp,
26+
'rates' => [
27+
'EUR' => [
28+
'currency_name' => 'Euro',
29+
'rate' => 1,
30+
'rate_for_amount' => 1,
31+
],
32+
'GBP' => [
33+
'currency_name' => 'Pound sterling',
34+
'rate' => 2.5,
35+
'rate_for_amount' => 2.5,
36+
],
37+
],
38+
]]);
39+
40+
$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
41+
$currencyGeoProvider->getRates('EUR', currencies());
42+
43+
$client->assertSent(function (Request $request) {
44+
return str_starts_with($request->url(), 'https://api.getgeoapi.com/v2/currency/convert');
45+
});
46+
});
47+
48+
it('returns floats for all rates', function () {
49+
$client = new Factory();
50+
$client->fake(['*' => [
51+
'timestamp' => now()->subDay()->timestamp,
52+
'rates' => [
53+
'EUR' => [
54+
'currency_name' => 'Euro',
55+
'rate' => 1,
56+
'rate_for_amount' => 1,
57+
],
58+
'GBP' => [
59+
'currency_name' => 'Pound sterling',
60+
'rate' => 2.5,
61+
'rate_for_amount' => 2.5,
62+
],
63+
],
64+
]]);
65+
66+
$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
67+
$rates = $currencyGeoProvider->getRates('EUR', currencies());
68+
69+
expect($rates->getRates())->each->toBeFloat();
70+
});
71+
72+
it('sets the returned timestamp as the retrievedAt timestamp', function () {
73+
Carbon::setTestNow(now());
74+
75+
$client = new Factory();
76+
$client->fake(['*' => [
77+
'timestamp' => now()->subDay()->timestamp,
78+
'rates' => [],
79+
]]);
80+
81+
$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
82+
$rates = $currencyGeoProvider->getRates('EUR', currencies());
83+
84+
expect($rates->getRetrievedAt()->timestamp)->toBe(now()->startOfDay()->timestamp);
85+
});
86+
87+
it('throws a RequestException if a 500 error occurs', function () {
88+
$client = new Factory();
89+
$client->fake(['*' => Create::promiseFor(new Response(500))]);
90+
91+
$currencyGeoProvider = new CurrencyGEOProvider($client, 'password');
92+
$currencyGeoProvider->getRates('EUR', currencies());
93+
})->throws(RequestException::class);

0 commit comments

Comments
 (0)