Gomibo Platform’s software has so far been launched in 30 different European countries. We are building the world’s best cloud-based telecom commerce platform, and for this we are looking to expand even more. We want to provide all our customers with the best experience possible, so that includes the ability to pay for orders in their own currency. Despite the Euro being widespread, it is definitely not the only currency in Europe. Actually, there are 29 different currencies, which is on average more than 1 currency per 2 countries!
Thus, an optimized multi-currency functionality is critical for both customer satisfaction and operational flexibility. Keeping track of different currencies in your code is not hard per se, but all of the implications, however, are a different story.
I am Gijs Hendriks, a 28 year old Software Engineer at Gomibo Platforms. I love cats, cooking, and working on complex challenges like this multi-currency project. This has been a large and important project, and makes for an interesting example of the possibilities of our platform in terms of how features are rolled out. In this blog I will describe the challenges we have faced so far and our approach to solving them.
The first step in this project was to identify all the places in our system where we use prices or amounts in the code. Turns out, when you are a commerce software company, almost all your systems interact with money—from our communication templates to our price calculation engine.
What we discovered is that all our amounts were stored in floats. Floating point imprecision can be circumvented by using the BCMath library, but this is an ugly, round about solution to the underlying problem. While this code has served us well for quite some time, we consistently aim to further modernize the system to meet our future customer needs.
As Churchill said: “Never let a good crisis go to waste”. This crisis is a good opportunity to throw away the old code and build a new solution that is:
We looked at industry standards and best practices and as we expected, the solution is pretty simple. Use a 𝙼𝚘𝚗𝚎𝚢𝙰𝚖𝚘𝚞𝚗𝚝 that has a 𝚖𝚒𝚗𝚘𝚛_𝚞𝚗𝚒𝚝_𝚊𝚖𝚘𝚞𝚗𝚝 and a reference to a 𝚌𝚞𝚛𝚛𝚎𝚗𝚌𝚢 . The 𝚖𝚒𝚗𝚘𝚛_𝚞𝚗𝚒𝚝_𝚊𝚖𝚘𝚞𝚗𝚝 stores the smallest used amount in that 𝚌𝚞𝚛𝚛𝚎𝚗𝚌𝚢 . For euros that would be euro cents, but for the Japanese Yen, that would be whole Yens. This way you can do the calculation with integers instead of floats. We implement the 𝙼𝚘𝚗𝚎𝚢𝙰𝚖𝚘𝚞𝚗𝚝 class and provide services to perform operations on 𝙼𝚘𝚗𝚎𝚢𝙰𝚖𝚘𝚞𝚗𝚝𝚜 like comparison, addition, subtraction, allocation and formatting.
However, we need more: we need to be able to send accurate invoices to companies and private individuals in different countries. So, we have created a 𝙿𝚛𝚒𝚌𝚎𝙰𝚖𝚘𝚞𝚗𝚝 that contains a 𝙼𝚘𝚗𝚎𝚢𝙰𝚖𝚘𝚞𝚗𝚝 and a 𝚅𝙰𝚃𝚝𝚢𝚙𝚎 . We assume that the 𝙿𝚛𝚒𝚌𝚎𝙰𝚖𝚘𝚞𝚗𝚝 always includes VAT and use a service to convert to a different VAT rate. Internally, this uses the allocation we implemented for a 𝙼𝚘𝚗𝚎𝚢𝙰𝚖𝚘𝚞𝚗𝚝.
Sometimes we want to convert a price to a different currency. We need a timestamped conversion rate for that. So, we create a third object: a 𝙲𝚘𝚗𝚟𝚎𝚛𝚝𝚒𝚋𝚕𝚎𝙿𝚛𝚒𝚌𝚎𝙰𝚖𝚘𝚞𝚗𝚝 . In this object we provide a link to a 𝚌𝚘𝚗𝚟𝚎𝚛𝚜𝚒𝚘𝚗_𝚛𝚊𝚝𝚎_𝚌𝚑𝚊𝚗𝚐𝚎 , which contains the conversion rate that applies to that amount. Now a telco customer with a euro-centered customer service can show the amount the customer paid in Danish Crones and the estimated amount in another currency they are familiar with (Euro). Also, a customer will always see the same price they did when they ordered, even if they return to their order some days later when the exchange rate changed.
The structure as described above
So everything is great, I am happy with this solution. But there are still some challenges that remain unresolved. For example: the transaction costs that are charged to a customer’s webshop by one of their payment service providers might not be in whole euro cents. And with this system, sub-cent precision cannot be handled, since the whole point of this system was to not use decimals… Sigh. This difference will add up over time in balance sheets (have you ever seen the movie Office Space?). As imbalance in balance sheets makes accountants uneasy, we cannot ignore this unfortunately. A possible solution is to create an additional class that can handle more precision, something like a 𝙿𝚛𝚎𝚌𝚒𝚜𝚒𝚘𝚗𝙿𝚛𝚒𝚌𝚎𝙰𝚖𝚘𝚞𝚗𝚝, where you specify the amount of Mills and maybe even more decimals as ints. We can also make separate services that you can use when working with sub-cent precision.
Releasing a large feature like multi-currency support is a little precarious. Because you are changing a lot of code at once, the chance you introduced a bug somewhere is bigger. And on top of that, the changes you are making have a big impact if they break. You could suddenly owe the company a lot of money ;).
You can prevent these issues with a couple of things:
This solution is mainly an exercise in system design. And one of our goals was to make our approach unified. We would like to get it right the first time, implement it, and boom, you’re done. This means that the system should be able to handle any possible exceptions that we encounter. We try to account for this by including all requirements and exceptions in the rules that define our system but it is incredibly hard to account for everything. This is a big reason why the waterfall system fails so often and buzzwords like agile and lean are penetrating all kinds of sectors. So, we use an iterative approach (as we always do). As a consequence of this, we have already re-written our core classes a couple of times and I expect to do it a few more. In fact, it’s likely this blog is already outdated by the time you read it.
But I guess that is just the life of a software engineer at a company that wants to consistently improve the platform. And that is part of the fun.
¯\_(ツ)_/¯