5

Magento ver. 2.2.3

Seems today fixer.io stopped working. It was the only service that still works for currency rates. If I try to reach the url from web browser I get this

0   "#################################################################################################################################"
1   "#                                                                                                                               #"
2   "# IMPORTANT - PLEASE UPDATE YOUR API ENDPOINT                                                                                   #"
3   "#                                                                                                                               #"
4   "# This API endpoint is deprecated and has now been shut down. To keep using the Fixer API, please update your integration       #"
5   "# to use the new Fixer API endpoint, designed as a simple drop-in replacement.                                                  #"
6   "# You will be required to create an account at https://fixer.io and obtain an API access key.                                   #"
7   "#                                                                                                                               #"
8   "# For more information on how to upgrade please visit our Github Tutorial at: https://github.com/fixerAPI/fixer#readme          #"
9   "#                                                                                                                               #"
a   "#################################################################################################################################"

You can still get a free account, but code change needed to make it works with new API.

Does anybody know if a fix coming with the next upgrade of Magento?

Alternatively in Magento 1.9.3.8 I'm getting currency rates from here, by custom module.

Does one already exist that use this service?

If there are no answers to these questions, the only solution will be to write a new custom module.

I hope for an escape...

2
  • I guess this will need to be changed to get it to work: github.com/magento/magento2/blob/2.2-develop/app/code/Magento/… For the API_KEY the scope config seems to be already there so shouldn't be too tricky to add a new field in the config, only question is where should this be added in.
    – oirad
    Jun 5, 2018 at 3:54
  • This is the step to write e new module. In this case i prefer to use The Free Currency Converter API service. At list it doesn't need any subscription.
    – krybbio
    Jun 5, 2018 at 4:18

5 Answers 5

11

The Free Currency Converter API module:

app/code/MyCompany/FreeCurrencyConverter/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="MyCompany_FreeCurrencyConverter" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Directory"/>
        </sequence>
    </module>
</config>

app/code/MyCompany/FreeCurrencyConverter/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Directory\Model\Currency\Import\Config">
        <arguments>
            <argument name="servicesConfig" xsi:type="array">
                <item name="fcc" xsi:type="array">
                    <item name="label" xsi:type="string">Free Currency Converter</item>
                    <item name="class" xsi:type="string">MyCompany\FreeCurrencyConverter\Model\Currency\Import\Fcc</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

app/code/MyCompany/FreeCurrencyConverter/etc/config.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <currency>
            <fcc>
                <timeout>100</timeout>
                <delay>1</delay>
            </fcc>
        </currency>
    </default>
</config>

app/code/MyCompany/FreeCurrencyConverter/etc/adminhtml/system.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="currency">
            <group id="fcc" translate="label" sortOrder="41" showInDefault="1" showInWebsite="0" showInStore="0">
                <label>Free Currency Converter</label>
                <field id="timeout" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Connection Timeout in Seconds</label>
                </field>
                <field id="delay" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Connection Delay Time in Seconds</label>
                </field>
            </group>
        </section>
    </system>
</config>

app/code/MyCompany/FreeCurrencyConverter/Model/Currency/Import/Fcc.php

<?php
namespace MyCompany\FreeCurrencyConverter\Model\Currency\Import;
class Fcc extends \Magento\Directory\Model\Currency\Import\AbstractImport
{
    /**
     * @var string
     */
    const CURRENCY_CONVERTER_URL = 'https://free.currencyconverterapi.com/api/v3/convert?q={{CURRENCY_FROM}}_{{CURRENCY_TO}}';
    /**
     * HTTP client
     *
     * @var \Magento\Framework\HTTP\ZendClient
     */
    protected $_httpClient;
    /**
     * Core store config
     *
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $_scopeConfig;
    /**
     * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     */
    protected $jsonHelper;

    public function __construct(
        \Magento\Directory\Model\CurrencyFactory $currencyFactory,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\Json\Helper\Data $jsonHelper
    ) {
        parent::__construct($currencyFactory);
        $this->_scopeConfig = $scopeConfig;
        $this->_httpClient = new \Magento\Framework\HTTP\ZendClient();
        $this->jsonHelper = $jsonHelper;
    }
    /**
     * @param string $currencyFrom
     * @param string $currencyTo
     * @param int $retry
     * @return float|null
     */
    protected function _convert($currencyFrom, $currencyTo, $retry = 0)
    {
        $url = str_replace('{{CURRENCY_FROM}}', $currencyFrom, self::CURRENCY_CONVERTER_URL);
        $url = str_replace('{{CURRENCY_TO}}', $currencyTo, $url);
        try {
            sleep($this->_scopeConfig->getValue(
                'currency/fcc/delay',
                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
            ));
            $response = $this->_httpClient->setUri(
                $url
            )->setConfig(
                [
                    'timeout' => $this->_scopeConfig->getValue(
                        'currency/fcc/timeout',
                        \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                    ),
                ]
            )->request(
                'GET'
            )->getBody();
            $resultKey = $currencyFrom.'_'.$currencyTo;
            $data = $this->jsonHelper->jsonDecode($response);
            $results = $data['results'][$resultKey];
            $queryCount = $data['query']['count'];
            if( !$queryCount &&  !isset($results)) {
                $this->_messages[] = __('We can\'t retrieve a rate from %1.', $url);
                return null;
            }
            return (float)$results['val'];
        } catch (\Exception $e) {
            if ($retry == 0) {
                $this->_convert($currencyFrom, $currencyTo, 1);
            } else {
                $this->_messages[] = __('We can\'t retrieve a rate from %1.', $url);
            }
        }
    }
}

enjoy!

EDIT:

as Bizzy Bee says:

Don't forget the following file to get it all going properly:

app/code/MyCompany/FreeCurrencyConverter/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'MyCompany_FreeCurrencyConverter',
    __DIR__
);
?>
7
  • Hello Patal, can you offer download link for complete modul? Jun 6, 2018 at 6:40
  • @MatthiasJaekle sorry, there is no way to upload this module here as far as i can see, but you got all files needed in this post. You should only add registration.php.
    – krybbio
    Jun 6, 2018 at 10:18
  • @krybbio you could use github, bitbucket, or gitlab with a public repo
    – Rian
    Jun 6, 2018 at 16:26
  • @Rian feel free to do it. ;-)
    – krybbio
    Jun 6, 2018 at 19:55
  • 2
    I've added this code to github - github.com/flancer32/mage2_ext_free_rates Thanks to @krybbio and all other guys.
    – Alex Gusev
    Jun 15, 2018 at 9:05
4

It seems we need to subscribe Basic $10/month plan because Fixer new free plan offers only Euro base currency. If you purchase $10/month plan, you can fix this issue editing:

[root]/vendor/magento/module-directory/Model/Currency/Import/FixerIo.php

line 16 replace

const CURRENCY_CONVERTER_URL = 'http://api.fixer.io/latest?base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}';

with

const CURRENCY_CONVERTER_URL = 'http://data.fixer.io/api/latest?access_key=1234yourkey5678&base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}';

It's better to wait some nice guy will create a new module with free converter API.

4
  • 1
    i am a nice guy
    – krybbio
    Jun 5, 2018 at 10:34
  • Wow! That's quick!
    – lizenial
    Jun 6, 2018 at 2:27
  • @krybbio Yes, your module works perfectly. Thanks a lot!
    – lizenial
    Jun 7, 2018 at 1:04
  • Free Fixer.IO account is working fine, if your base currency is EUR. Converting to lot of different currencies is possible for free. 1000 API request per month. Jun 13, 2018 at 8:34
2

Thanks for the module code!

Don't forget the following file to get it all going properly:

app/code/MyCompany/FreeCurrencyConverter/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'MyCompany_FreeCurrencyConverter',
    __DIR__
);
?>
1

To extend on @lizenial's answer: Modifying core code is never a good idea, that's why you can override the original class, though (as far as I know) it requires overriding the entire class as it mostly consists of private methods, which we cannot modify using plugins.

app/code/[Vendor]/[Module]/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Directory\Model\Currency\Import\FixerIo" type="[Vendor]\[Module]\Model\Currency\Import\FixFixerIo" />
</config>

app/code/[Vendor]/[Module]/etc/adminhtml/system.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="currency" translate="label" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1">
            <tab>general</tab>
            <group id="fixerio" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0">
                <field id="api_key" translate="label,comment" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Api key</label>
                    <comment><![CDATA[New updated API now requires an access key and a new URL.]]></comment>
                </field>
            </group>
        </section>
    </system>
</config>

app/code/[Vendor]/[Module]/Model/Currency/Import/FixFixerIo.php

<?php

namespace [Vendor]\[Module]\Model\Currency\Import;

use \Magento\Store\Model\ScopeInterface;

class FixFixerIo extends \Magento\Directory\Model\Currency\Import\FixerIo
{
    private $scopeConfig;

    const CURRENCY_CONVERTER_URL = 'http://data.fixer.io/api/latest?base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}&access_key='; // changed constant

    const API_KEY_CONFIG_PATH = 'currency/fixerio/api_key';

    public function __construct(
        \Magento\Directory\Model\CurrencyFactory $currencyFactory,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
    ) {
        $this->scopeConfig = $scopeConfig;
        parent::__construct($currencyFactory, $scopeConfig, $httpClientFactory);
    }

    /**
     * @return array
     * @override
     * @see \Magento\Directory\Model\Currency\Import\FixerIo::fetchRates()
     */
    public function fetchRates()
    {
        $data = [];
        $currencies = $this->_getCurrencyCodes();
        $defaultCurrencies = $this->_getDefaultCurrencyCodes();

        foreach ($defaultCurrencies as $currencyFrom) {
            if (!isset($data[$currencyFrom])) {
                $data[$currencyFrom] = [];
            }
            $data = $this->convertBatch($data, $currencyFrom, $currencies);
            ksort($data[$currencyFrom]);
        }
        return $data;
    }

    /**
     * @param array $data
     * @param string $currencyFrom
     * @param array $currenciesTo
     * @return array
     * @override
     * @see \Magento\Directory\Model\Currency\Import\FixerIo::convertBatch()
     */
    private function convertBatch($data, $currencyFrom, $currenciesTo)
    {
        $currenciesStr = implode(',', $currenciesTo);
        $url = str_replace('{{CURRENCY_FROM}}', $currencyFrom, self::CURRENCY_CONVERTER_URL);
        $url = str_replace('{{CURRENCY_TO}}', $currenciesStr, $url);

        set_time_limit(0);
        try {
            $response = $this->getServiceResponse($url);
        } finally {
            ini_restore('max_execution_time');
        }

        foreach ($currenciesTo as $currencyTo) {
            if ($currencyFrom == $currencyTo) {
                $data[$currencyFrom][$currencyTo] = $this->_numberFormat(1);
            } else {
                if (empty($response['rates'][$currencyTo])) {
                    $this->_messages[] = __('We can\'t retrieve a rate from %1 for %2.', $url, $currencyTo);
                    $data[$currencyFrom][$currencyTo] = null;
                } else {
                    $data[$currencyFrom][$currencyTo] = $this->_numberFormat(
                        (double)$response['rates'][$currencyTo]
                    );
                }
            }
        }
        return $data;
    }

    /**
     * @param string $url
     * @param int $retry
     * @return array|mixed
     * @override
     * @see \Magento\Directory\Model\Currency\Import\FixerIo::convertBatch()
     */
    private function getServiceResponse($url, $retry = 0)
    {
        $accessKey = $this->scopeConfig->getValue(self::API_KEY_CONFIG_PATH, ScopeInterface::SCOPE_STORE);
        $url .= $accessKey;
        /** @var \Magento\Framework\HTTP\ZendClient $httpClient */
        $httpClient = $this->httpClientFactory->create();
        $response = [];

        try {
            $jsonResponse = $httpClient->setUri(
                $url
            )->setConfig(
                [
                    'timeout' => $this->scopeConfig->getValue(
                        'currency/fixerio/timeout',
                        \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                    ),
                ]
            )->request(
                'GET'
            )->getBody();

            $response = json_decode($jsonResponse, true);
        } catch (\Exception $e) {
            if ($retry == 0) {
                $response = $this->getServiceResponse($url, 1);
            }
        }
        return $response;
    }

}

I know this (probably) isn't the cleanest solution as it overwrites pretty much the entire class but if any of you have a cleaner solution feel free to comment :)

0

I tried the solution of updating the fixerIo.php file and for the sake of testing I even changed the store base currency to EUR, however, I get the following error after I have amended the code:

Fatal error: Namespace declaration statement has to be the very first statement or after any declare call in the script in /public_html/vendor/magento/module-directory/Model/Currency/Import/FixerIo.php on line 6

Are there any other changes needed besides for those in the fixerio.php file?

3
  • I think this should not be the answer. If you have some query, you can comment on the post or ask your own question by clicking on Ask Question button. Jul 18, 2018 at 13:07
  • 1
    You are right. I'm new to StackExchange, therefore, I cannot comment... Start a new thread is probably also not the right way forward... It's a bit of a chicken and egg situation here.
    – ShragiL
    Jul 18, 2018 at 13:11
  • I think you have made some mistakes in editing the file. Anyway, modify core file is always wrong. Use one of the two modules in the discussion instead.
    – krybbio
    Jul 18, 2018 at 17:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.