Adding Openexchangerates Currency Conversion Service to Magento

Most Magento users who have multiple currencies showing on their site would have noticed that the built in Webservicex currency service stopped working some time ago (towards the end of 2013).

After searching around for a while we found an article that shows how to add the Yahoo Finance currency conversion service to Magento, however, it transpires that this violates Yahoo’s T&C so it is really not an option. Likewise, Google’s currency conversion service has been discontinued. Therefore, what options are left?

After some more searching, we discovered Openexchangerates¬†which offers both a free and several levels of premium services. The free option allows for up to 1000 API requests per month which we thought was more than enough for the standard use case in Magento. Let’s say a month has 31 days, this would allow for about 32 requests per day which means that you could have the currency service updating your site every 45 minutes and not hit the monthly access limit. So, sign up for a free account and get your App Id.

All this is good news so we created a quick extension that adds this service to Magento.

Extension Structure

Assuming our audience is comfortable with creating an extension for Magento, we’ll just showcase the relevant code here and leave you to assembling it into an installable module.

First we want to define the module namespace, so we called this Magebase_Openexchangerates. We will just create Helper, Model and etc folders as illustrated in the image to the right.

Our extension will have a couple of system configuration options which are accessible from System > Configuration > General > Currency, the App Id and the connection timeout, so our system.xml looks like this:

<?xml version="1.0"?>
<!--
/**
 * @category    Magebase
 * @package     Magebase_Openexchangerates
 * @copyright   Copyright (c) 2014 Magebase
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
-->
<config>
    <sections>
        <currency translate="label" module="mb_openexchangerates">
            <groups>
                <mb_openexchangerates translate="label">
                    <label>Openexchangerates</label>
                    <sort_order>42</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>0</show_in_website>
                    <show_in_store>0</show_in_store>
                    <fields>
                        <app_id translate="label comment">
                            <label>Openexchangerates App Id</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>0</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>0</show_in_website>
                            <show_in_store>0</show_in_store>
                            <comment><![CDATA[Get your App Id at <a href="http://openexchangerates.org">openexchangerates.org</a>]]></comment>
                        </app_id>
                        <timeout translate="label">
                            <label>Connection Timeout in Seconds</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>5</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>0</show_in_website>
                            <show_in_store>0</show_in_store>
                        </timeout>
                    </fields>
                </mb_openexchangerates>
            </groups>
        </currency>
    </sections>
</config>

Our module config.xml will be simple:

<?xml version="1.0"?>
<!--
/**
 * @category    Magebase
 * @package     Magebase_Openexchangerates
 * @copyright   Copyright (c) 2014 Magebase
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
-->
<config>
    <modules>
        <Magebase_Openexchangerates>
            <version>1.0.0</version>
        </Magebase_Openexchangerates>
    </modules>
    <global>
        <currency>
            <import>
                <services>
                    <openexchangerates>
                        <name>Openexchangerates</name>
                        <model>mb_openexchangerates/currency_import_openexchangerates</model>
                    </openexchangerates>
                </services>
            </import>
        </currency>
        <helpers>
            <mb_openexchangerates>
                <class>Magebase_Openexchangerates_Helper</class>
            </mb_openexchangerates>
        </helpers>
        <models>
            <mb_openexchangerates>
                <class>Magebase_Openexchangerates_Model</class>
            </mb_openexchangerates>
        </models>
    </global>
    <default>
        <currency>
            <mb_openexchangerates>
                <timeout>100</timeout>
            </mb_openexchangerates>
        </currency>
    </default>
</config>

You will notice in the global node we define our new exchange rate service to Magento. The name will show up in the service selection drop-downs and the model will be our new currency exchange class that will handle the retrieval and processing of the currency rates from Openexchangerates’ API.

The Openexchangerates API documentation is pretty clear and there are good examples of the various use cases and premium features, however, we are only going to consider the API call provided by the free access which retrieves all available rates in one hit by simply calling http://openexchangerates.org/api/latest.json?app_id=YOUR_APP_ID. We get data back as JSON so it’s really easy to parse and process further.

To execute our rates retrieval, we have the Model/Currency/Import/Openexchangerates.php file with our class that extends Mage_Directory_Model_Currency_Import_Abstract:

<?php
/**
 * @category    Magebase
 * @package     Magebase_Openexchangerates
 * @author      Robert Popovic
 * @copyright   Copyright (c) 2014 Magebase (http://magebase.com)
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * Currency rate import model (From www.webservicex.net)
 *
 * @category   Magebase
 * @package    Magebase_Openexchangerates
 * @author     Robert Popovic for Magebase
 */
class Magebase_Openexchangerates_Model_Currency_Import_Openexchangerates extends Mage_Directory_Model_Currency_Import_Abstract
{
    /**
     * Openexchangerates API URL
     * @var string
     */
    protected $_url = 'http://openexchangerates.org/api/latest.json?app_id={{APP_ID}}';
    protected $_messages = array();

     /**
     * HTTP client
     *
     * @var Varien_Http_Client
     */
    protected $_httpClient;
    /**
     * Fetched and cached rates array
     * @var array
     */
    protected $_rates;

    /**
     * Initialise our obkject with the full rates retrieved from Openexchangerates as the
     * free version only allows us to get the complete set of rates. But that's ok, we are
     * caching it and then processing the individual rates
     * 
     * @throws Exception
     */
    public function __construct()
    {
        $this->_httpClient = new Varien_Http_Client();
        $app_id = Mage::getStoreConfig('currency/mb_openexchangerates/app_id');
        if (!$app_id) {
            $e = new Exception(Mage::helper('mb_openexchangerates')->__('No Openexchangerates App Id set!'));
            Mage::logException($e);
            throw $e;
        }
        $url = str_replace('{{APP_ID}}', $app_id, $this->_url);

        $response = $this->_httpClient
            ->setUri($url)
            ->setConfig(array('timeout' => Mage::getStoreConfig('currency/mb_openexchangerates/timeout')))
            ->request('GET')
            ->getBody();

        // response is in json format
        if( !$response ) {
            $this->_messages[] = Mage::helper('mb_openexchangerates')->__('Cannot retrieve rate from %s.', $url);
        } else {
            // check response content - returns array
            $response = Zend_Json::decode($response);
            if (array_key_exists('error', $response)) {
                $this->_messages[] = Mage::helper('mb_openexchangerates')->__('API returned error %s: %s', $response['status'], $response['description']);
            } elseif (array_key_exists('base', $response) && array_key_exists('rates', $response)) {
                $this->_rates = $response['rates'];

            } else {
                Mage::log('Openexchangerates API request: %s',$url);
                Mage::log('Openexchangerates API response: '.print_r($response,true));
                $this->_messages[] = Mage::helper('mb_openexchangerates')->__('Unknown response from API, check system.log for details.');
            }
        }
    }

    /**
     * Convert an individual rate
     * Note that the Opexchangerates free service gives all rates based on USD
     * so we do a cross-currency conversion vua USD as the base.
     * 
     * @param string $currencyFrom
     * @param string $currencyTo
     * @param int $retry
     * @return float
     */
    protected function _convert($currencyFrom, $currencyTo, $retry=0)
    {
        if (sizeof($this->_messages) > 0) {
            return null;
        }
        $rate = null;

        if (array_key_exists($currencyFrom, $this->_rates) && array_key_exists($currencyTo, $this->_rates)) {
            // convert via base currency, whatever it is.
            $rate = (float) ($this->_rates[$currencyTo] * (1/$this->_rates[$currencyFrom]));
        } else {
            $this->_messages[] = Mage::helper('mb_openexchangerates')->__('Can\'t convert from '.$currencyFrom.' to '.$currencyTo.'. Rate doesn\'t exist.');
        }

        return $rate;
    }
    /**
     * Trim currency rate to 6 decimals
     * 
     * @param float $number
     * @return float
     */
    protected function _numberFormat($number)
    {
        return number_format($number, 6);
    }
}

There are a couple of idiosyncrasies with this service in comparison to how the old service was operating. First, we get all rates in one request unless we want to pay for the premium service where we can call the service by passing specific currencies to convert between. Second, the returned rates are all based on the exchange rate against the US dollar. This means that we had to re-think the way we handle this.

Firstly, we retrieve the rates in the class constructor as the class is instantiated at the time the “Import Rates” button is clicked. Then we just cache the rates array from the JSON response in our _rates class property.

Magento then goes through all the configured currencies and calls the _convert($currencyFrom, $currencyTo, $retry=0) method for each individual currency. At this stage, our job is easy and we just look up the currency codes in the array keys. The only thing we need to do is cross-convert the from and to currencies since the rates are based on USD, hence the formula.

We also have an empty helper:

<?php
/**
 * Helper class
 *
 * @category   Magebase
 * @package    Magebase_Openexchangerates
 * @author     Robert Popovic for Magebase
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class Magebase_Openexchangerates_Helper_Data extends Mage_Core_Helper_Abstract
{
}

And the last thing is the app/etc/modules/Magebase_Openexchangerates.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <Magebase_Openexchangerates>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Directory />
            </depends>
        </Magebase_Openexchangerates>
    </modules>
</config>

And there you have it, enjoy continued automated exchange rate updates for your multicurrency site!

DISCLAIMER:
As usual, the code here is provided “as is” and Magebase provides no guarantees of accuracy of the conversion service or faultless performance of the code. Test thoroughly and use at your own peril. If you implement this code on a production site, Magebase disclaims any liability for any loss or damages resulting from the use of this code. Support may be provided in the comments depending on our availability but is not guaranteed.

Author - Robert Popovic

Founder and Technical Director of LERO9, a web agency specializing in Magento and WordPress design and development.

I launched Magebase in April, 2010 and am its editor and contributor. My main topics of interest are Magento development, customization and how to get the most out of Magento with the least amount of headache.

More Info » Follow me on Twitter »

Reader Comments (2)

  1. Achim Rosenhagen
    March 6, 2014 at 9:24 am /

    nice tutorial. just wrapped it in a deployable extension at https://github.com/ffuenf/Magebase_Openexchangerates

    Reply

Add a Comment & Join the Discussion

Insert small snippets of code by using [code]{your_code_here}[/code]
For larger code blocks please use http://pastebin.com and paste your link.

You may also use the following HTML in your comment: <a href="" title=""> <abbr title="">
<acronym title=""> <blockquote cite=""> <cite> <em> <strike> <strong>