We've just completed our first Craft Commerce website at Base Design. Overall everything went well but we encountered issues with taxes and the way VAT works on online sales in the EU. Craft Commerce didn't seem to integrate as well as expected with those taxes and we eventually had to use a few tricks we're not proud of to make it work as expected (or so we thought).
As we are about to start a new e-commerce project in a few months I took a bit of time to see what changed in Craft Commerce since then and what we could have done better... but stumbled on more issues.
Disclaimer: it's a long one, sorry about that.
A bit of context
As you know, the EU is made of a bunch of different countries. Every country has its own laws and, for this reason, taxes vary from country to country in the EU.
For example, the standard VAT rate (VAT is for "Value-Added Tax", the most popular tax in the EU) is 20% in France and the Netherlands, 21% in Belgium and Latvia, 27% in Hungary and 17% in Luxembourg. But of course lots of exceptions exist and these exceptions are country-based.
All that to help you understand that when talking about VAT in the EU, we're actually talking about different VATs in every country. Luckily for us, the VAT that needs to be used on an e-commerce website is the one of the country where the company selling the products is established, not the one of the country where the goods are being bought from. It means that the same VAT can be applied to all sales on a website... where VAT is relevant (actually this isn't always true but let's ignore that for now). Good!
Where it gets complex is that VAT is not always due. For example, if a French company buys from an company established in a different EU country they won't be asked to pay any VAT. This rule only applies to companies; individuals can't benefit from this tax exemption. Be careful though: if a company is buying from another company in the same country this exemption won't apply and the VAT will still need to be paid.
Also, when some goods are being sold to individuals or companies outside of the EU, VAT shouldn't be applied either.
Example
I recently created this list that sums up most cases. The assumption made is that the subject company is Belgium-based and selling a 100€-worth product.
- If customer is a BE 🇧🇪 citizen
⇒ 121€ with 21% VAT line - If customer is a BE 🇧🇪 company
⇒ 121€ with 21% VAT line - If customer is a EU 🇪🇺 (not BE) citizen
⇒ 121€ with 21% VAT line - If customer is a EU 🇪🇺 (not BE) company
⇒ 100€ with "Intra-EU VAT" VAT line - If customer is a ROW* 🌍 citizen
⇒ 100€ with no mention of VAT - If customer is a ROW* 🌍 company
⇒ 100€ with no mention of VAT
ROW means Rest of the World.
How we applied this logic to Craft Commerce
In our minds, there were two different ways we could apply this logic to Craft Commerce.
1. Deduce VAT when not needed
The first way to make this work would have been to input all product prices with a VAT-included price. If we use the above example, the product price would have been 121€.
The price displayed on the website by default would have been that price and we could have remove the VAT taxes when the customer was either a EU company (not BE) or a ROW citizen or company.
To make this work, we created the following Tax Rate:
Up until then, everything worked as expected on orders that would be shipped to Belgium. Company or individual will always be billed 121€.
Then we needed to take care of the rest of the EU and created the following Tax Rate:
By doing this, when an order from a EU citizen (not BE) was created, we were asking Craft Commerce to:
- remove the original BE Tax Rate and condition
- apply the EU Tax Rate and condition
- if it's a EU company, don't add the tax
- output the total
But this is where we had to stop. Indeed, the taxes calculation there wasn't correct. This is what we got:
Notice the two "Tax" lines. They both have a value of 21% but only the first one is calculated the way we expect it to be (121€ ÷ 1.21). The second one should equal to 121€ ÷ 1.21 × 0.21 = 21€ but it is actually equal to 25.41€ (121€ × 0.21). Basically it just doesn't take into account the previous tax adjustments when calculating the new adjustment.
We realised that this way of calculating taxes might be relevant in some regions and contexts and Craft Commerce was probably developed in that optic. So at this point we were only bummed that there wasn't any option to make it possible for a tax adjustment to be aware of all previous tax adjustments.
But we still had another option in mind.
2. Add VAT when needed
The alternative we thought of was to input all product prices without VAT. If we use the example above, the product price would have been 100€.
To display the default price on the website, we would create a helper that would show that default price × 1.21 => 121€. It made us work way more than expected but still, it worked quite well.
Creating the different tax rules for Belgium, the EU and the ROW was then easier and we didn't expect any issue anymore.
But an annoying one eventually appeared: rounding errors. Let's take the example of a product that must appear at 55€ on the website.
Following the previous logic, this product should be added to Craft at a price of 45.4545€ (55€ ÷ 1.21). When we show the price on the website, we use the helper and the resulting price is 45.4545€ × 1.21 = 54,9999€ which we round to 55€. All good.
Now when we enter the checkout process it was too dangerous to use our helper. So we started relying on the Craft Commerce engine. But that engine calculates prices in another way as what we had anticipated. Basically, it will only use the first 2 decimals. So 45.45€ (our base price with 2 decimals) × 1.21 is now worth 54.99€. It looks inconsistent.
You'll say we could just adapt our base price but that's actually not a good idea. First, 45.46€ × 1.21 will amount to 55.01€, not 55€.
On top of that, when we add the same product multiple times in our basket, the price becomes:
- 45.45€ × 1.21 × 2: 109.99€
- 45.45€ × 1.21 × 3: 164.98€
- 45.45€ × 1.21 × 4: 219.98€
- 45.45€ × 1.21 × 5: 274.97€
- 45.45€ × 1.21 × 6: 329.97€
So what the customer would actually see now looks like this: 54,99€ × 6 equals to 329,97€. Remember, what we wanted was: 55€ × 6 equals to 330€.
It's not professional.
The solution
Well, this is where we need help. As you can see, none of these options are viable: the first one outputs wrong totals and the second one doesn't output nicely-rounded numbers.
To us, the solution to the first option could be that we would be allowed to check the "The tax is already included in the taxable subject" in multiple Tax rates. I understand that this would change a few things in the way Craft Commerce works but it is how I expected it to work from the beginning.
The solution to the second option would be that Craft Commerce doesn't round numbers to 2 decimals when processing them. It might be simpler but that option still looks a bit dirty in my opinion.
We're probably not the first ones encountering this issue so we were wondering how others handled the situation. Maybe another solution exists? In any case, I'd be very interested to discuss it with anyone who is developing a Craft Commerce website for a EU-based company.
Thanks in advance!
round()
. I also wonder what else is available to you in there. Also, FWIW, Twig has round and number_formant filters.