Most EU VAT code starts like this:
const vat = price * (standardRate[country] / 100);
const total = price + vat;
It works for the first sale and the demo. It is also wrong in at least five ways that show up later, usually at the worst possible time: during a refund, a correction or an audit.
1. Rates change, and VAT uses the rate on the date of the sale
A table of today's rates answers the wrong question. VAT is due at the rate that applied when the sale happened.
Rates change more often than people expect. Germany cut its standard rate from 19% to 16% for the second half of 2020. Finland went from 24% to 25.5% in September 2024. Each change means:
- a refund for a sale before the change must use the old rate,
- a sale made in December and invoiced in January can straddle a change,
- a correction to an earlier quarter's return uses that quarter's rates.
So the lookup needs a date: rate(country, date), not rate(country). Historical rate data needs checking too: even official sources get past rates wrong.
2. Not everything uses the standard rate
EU countries may apply reduced rates to some products. For digital sellers the big one is electronic publications: e-books, e-newspapers and e-magazines.
| Country | Standard rate | E-books |
|---|---|---|
| Germany | 19% | 7% |
| France | 20% | 5.5% |
| Italy | 22% | 4% |
| Spain | 21% | 4% |
| Netherlands | 21% | 9% |
| Ireland | 23% | 0% |
If you sell e-books and multiply by the standard rate, you overcharge your customers. If you sell e-books and templates and use one rate for both, one of them is wrong.
3. Business customers usually pay no VAT at all
When the customer is a business with a valid VAT number in another EU country, you normally don't charge VAT. The customer accounts for it under the reverse charge, and your invoice says so.
That means the calculation depends on who the customer is, and the VAT number needs checking in VIES (the EU's VAT number lookup) before you rely on it. A number the customer typed into a form proves nothing until it's checked.
4. Where you are based changes the answer
For a business established in one EU country, cross-border sales of digital services to consumers below €10,000 a year can be taxed at the seller's own rate. Above it, the customer's rate applies. The test also looks at the previous year.
For a business outside the EU, there's no threshold: the customer's rate applies from the first sale.
So standardRate[customerCountry] can be wrong for a small EU seller, and right for a US one, for the same sale.
5. Floating-point money and rounding
0.1 + 0.2 === 0.30000000000000004 // true
Money in binary floating point drifts, and VAT amounts need rounding to the cent at a defined point. Rounding each invoice line and then adding gives a different total from adding and then rounding. Use integer cents or a decimal type, and pick one rounding rule on purpose.
What the calculation actually needs
Put together, a correct VAT calculation needs at least:
- the customer's country,
- the date of the sale,
- the product category (standard, or e-book and similar),
- whether the customer is a business, and if so a checked VAT number,
- where you are established, and for an EU seller, your cross-border sales so far this year and last year,
- decimal arithmetic with an explicit rounding rule.
That is a lot more than a multiplication. It's the reason Duty27 exists. A request looks like this:
{
"country_code": "DE",
"date": "2020-08-01",
"amount": "100.00",
"amount_type": "NET",
"b2b": false,
"category": "EBOOK"
}
The response gives you the rate that applied on that date (rate_percent), the net, tax and gross amounts as decimal strings, and whether the reverse charge applies. You can try a calculation on the homepage without signing up, and the API docs cover the rest, including business customers and the €10,000 threshold.
Duty27™