Utilizing PayPal's Currency Conversion API for fun and profit

By Peter Georgeson

Overview

PayPal include currency conversion functionality as part of their Adaptive Payments API.

This article introduces the currency conversion API by first demonstrating how the API works.

We then show an application of the currency conversion API by improving a Google App Engine shopping cart application.

Finally, there is some discussion of the issues and caveats associated with this particular API call.

Getting Started

This article extends an existing sample Google App Engine web application that is hosted on GitHub - it is available at https://github.com/supernifty/appengine-shopping-cart. Download the app and follow along.

The shopping cart is extended by improving the Buy Now page to include an item's price in a number of currencies, based on PayPal's currency conversion. This allows a buyer to see the price in their own currency before proceeding to PayPal. Hypothetically, this will please any international buyer and lead to greatly improved conversion rates1.

To run the application, you will need to download and install Google's App Engine Runtime. Once the runtime is installed, elect to "Add Existing Application" and choose the "app" directory in the example project.

The application also requires PayPal credentials to be specified in settings.py. Sign up to PayPal's sandbox site at https://developer.paypal.com/ and create buyer and seller test accounts.

The Currency Conversion API

This API call is simple once you have the format right. It follows the typical PayPal API call format so adding support for the method is a matter of slightly modifying existing code from another API call.

A new class was added to paypal.py to support the currency API:


class Currency( object ):
  def __init__( self, amount, currencies, remote_address ):
    headers = {
      'X-PAYPAL-SECURITY-USERID': settings.PAYPAL_USERID,
      'X-PAYPAL-SECURITY-PASSWORD': settings.PAYPAL_PASSWORD,
      'X-PAYPAL-SECURITY-SIGNATURE': settings.PAYPAL_SIGNATURE,
      'X-PAYPAL-REQUEST-DATA-FORMAT': 'JSON',
      'X-PAYPAL-RESPONSE-DATA-FORMAT': 'JSON',
      'X-PAYPAL-APPLICATION-ID': settings.PAYPAL_APPLICATION_ID,
      'X-PAYPAL-DEVICE-IPADDRESS': remote_address,
    }

    data = {
      'requestEnvelope': { 'errorLanguage': 'en_US' },
      'baseAmountList': [ { 'currency': { 'code': 'USD', 'amount': amount } } ],
      'convertToCurrencyList': { 'currencyCode': currencies },
    }

    self.raw_request = json.dumps(data)
    self.raw_response = url_request( "%s%s" % ( settings.PAYPAL_ENDPOINT, "ConvertCurrency" ), data=self.raw_request, headers=headers ).content()
    logging.info( "response was: %s" % self.raw_response )
    self.response = json.loads( self.raw_response )

  def success( self ):
    return self.raw_response.find( "Success" ) != -1

The constructor for the Currency class does all the work in setting up the request, making the request and parsing the response.

The headers dictionary contains the authentication details associated with all Adaptive Payments API calls. We also specify here that we wish to use JSON as the exchange format.

The specific data associated with this call is specified in the data dictionary. baseAmountList contains the amount and currency to convert from. In this code, the source currency is assumed to be US dollars. convertToCurrencyList contains an array of currency codes to convert to. The full list of supported currencies is included in PayPal's documentation.

We also provide a simple test for "success", by looking for the string "Success" in the response. This appears in the "ACK" field of PayPal's response. In a more robust implementation, this test would be more rigorous, however, this method is reasonably accurate.

The next step is for the actual request to be made. The actual API call is ConvertCurrency. We convert the raw response to a JSON structure and make this available to the caller.

Using the convenience class

The intention in this example is to request the currency conversion service directly from a web page using JavaScript. To facilitate this, the application will provide a simple access point that can be easily called using an AJAX request.

In main.py the sample application already has a URL pattern and class configured for this purpose. The URL pattern /api/([^/]*)/(.*) is handled by the API class, and allows a command and arbitrary data to be passed to the class instance. We extend this class to include currency conversion:


class API(RequestHandler):
  @login_required
  def get(self, cmd, data):
    ...
    elif cmd == 'ccy':
      conversion = paypal.Currency(
        data,
        [ 'AUD', 'CAD', 'GBP', 'EUR' ],
        self.request.remote_addr
      )
      if conversion.success():
        self.response.out.write( simplejson.dumps( conversion.response ) )
      else:
        self.error(501)
    else:
      self.error(501)

When the specified command is ccy, the method expects data to be the amount in US dollars to convert. In this example, the target currencies are hard-coded but could easily be extended to be more flexible.

An advantage of this setup is that it is easy to test the endpoint directly in the browser. With the web application running locally, and all going well, a URL in the format http://localhost:8080/api/ccy/1.2 should ellicit a response similar to:


{ "responseEnvelope": {
    "timestamp":"2012-02-02T03:42:01.952-08:00",
    "ack":"Success",
    "correlationId":"90d8703ddcd2e",
    "build":"2486531"
  },
  "estimatedAmountTable": {
    "currencyConversionList":[ {
      "baseAmount": {
        "code":"USD",
        "amount":"1.2"
      },
      "currencyList": {
        "currency":[
          {"code":"AUD","amount":"1.40"},
          {"code":"CAD","amount":"1.30"},
          {"code":"GBP","amount":"0.58"},
          {"code":"EUR","amount":"0.85"}
        ]
      }
    } ]
  }
}

We now have a simple API call that will do the currency conversion using PayPal's API as the back-end.

Converting currency directly from JavaScript

We could have done the currency conversion as part of the rendering of the Buy page, implemented in main.Buy. However, this involves another request to PayPal and would further slow down a page that already makes an external request to PayPal while rendering. It's faster and more flexible to render the page, then retrieve the currency conversions separately using JavaScript. This means the currency conversion will not slow down the initial rendering of the page.

jQuery provides the functionality to make an AJAX request and parse the returned JSON. Hence the jQuery library and local JavaScript is included in the Buy page at templates/buy.htm:


{% block head %}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="/static/main.js"></script>
{% endblock %}

Next, the mechanism is added for the user to request the currency conversions. In this instance the user initiates the conversion with a click; it could be configured to run on page load, or even conditionally load based on the user's location.


<div id="ccy_data"><a onclick="supernifty.currencies(); return false" href="/nojs">Click to see this converted to other currencies!</a></div>


Buy now page with currency conversion option

The JavaScript to handle the click and initiate the request is added to static/main.js.


currencies: function() {
  var amount = document.getElementById("amount").innerHTML,
    url = '/api/ccy/' + encodeURIComponent(amount),
    target = document.getElementById("ccy_data");
  target.innerHTML = 'Please wait...';
  $.get( url, ccy_success );
}

In the currencies function, the amount to be converted is extracted from the HTML and used to build the request URL. The web request is made and ccy_success is registered for the callback once the request completes.

Just note that as this API call is a GET, it could potentially be cached - by proxy servers, or the user's browser - and the user could see stale data. In a real application you should add a random parameter to the URL to ensure that the request reaches the server and is processed.


  function ccy_success(data) {
    var
      target = document.getElementById("ccy_data"),
      currencies = $.parseJSON( data ),
      list = currencies['estimatedAmountTable']['currencyConversionList'][0]['currencyList']['currency'],
      map = { 'AUD': 'au', 'CAD': 'ca', 'GBP': 'gb', 'EUR': 'europeanunion' },
      result = 'Expect to pay...<br/>';
    for ( var i = 0; i < list.length; i++ ) {
      if ( list[i]['code'] in map ) {
        result += '<img alt="' + list[i]['code'] + '" src="/static/flags/' + map[list[i]['code']] + '.png"/> ';
      }
      result += list[i]['amount'] + ' ' + list[i]['code'] + '&nbsp;   ';
    }
    target.innerHTML = result;
  }

In the ccy_success callback, the code parses the JSON response and extracts the list of converted currencies. These are then formatted and displayed on the ccy_data HTML element.


Buy now page with converted currencies

To improve the look of the widget, country flags were added to the project and are appropriately displayed along with the currency conversion.

Next steps

PayPal's currency conversion API is relatively simple. This tutorial has demonstrated how easy it is to add a currency conversion widget to an existing Buy Now page, using PayPal's currency conversion API. If your shopping cart already uses PayPal as its payment provider, it makes sense to use their currency exchange service to get the most accurate result for the consumer.

Note that PayPal do not guarantee the accuracy of their currency exchange service. It is not updated continuously so should only be used as a guide. PayPal's documentation states: This is an estimated rate, which may be different from the FX rate used at the time of the transaction.

This example has demonstrated the basic mechanism, however, the project could be expanded in a number of interesting areas. For instance, it would be relatively straightforward to guess the consumer's currency based on their location, which can be obtained through a number of simple techniques. This currency could be highlighted, further simplifying the purchase process for the international consumer.

Conclusion

An increasing percentage of online e-commerce is conducted by consumers using non-US currencies, a trend that is set to continue. By taking advantage of PayPal's currency conversion API, we can improve the shopping cart experience and in turn build a more successful web application.

This article demonstrated the mechanism for implementing this.

Note that the sample application is not ready for production use and is a simple demonstration of how the currency conversion API works and how it can be integrated into an existing application.

Further Details

1The "profit" mentioned in the title of this article.