Guide
Getting started
npm install @devix-labs/vat-calculator
import { vat, extract, invoice } from '@devix-labs/vat-calculator';
vat(100, { country: 'SA' });
// { net: '100.00', vat: '15.00', gross: '115.00', rate: 0.15, currency: 'SAR', … }
extract(115, { country: 'SA' });
// the same figures, worked backwards out of a gross amount
Every figure comes back as a string, because that is what goes on an invoice
and into a tax return. minor carries the same numbers as whole fils for any
arithmetic that follows.
The three things this gets right
The tax inside a gross amount is not the gross times the rate. 115 × 0.15
is 17.25; the tax actually inside 115 at 15% is 15.00. The correct sum is
gross × rate ÷ (1 + rate), and getting it wrong overstates the tax by the rate
squared. extract() does it properly.
Money is never a float. 0.1 + 0.2 is not 0.3, and an invoice of a hundred
lines drifts by a fils or two — enough for the invoice to disagree with the
return. Every amount becomes an integer number of fils the moment it arrives.
Rates have history. Saudi Arabia went from 5% to 15% on 1 July 2020, so a credit note against a 2019 invoice needs 5%:
vat(100, { country: 'SA', on: '2019-06-01' }).vat; // '5.00'
vat(100, { country: 'SA' }).vat; // '15.00'
A whole invoice
invoice([
{ amount: '10.00', quantity: 2, description: 'Consulting' },
{ amount: '50.00', treatment: 'zero-rated', description: 'Exported goods' },
{ amount: '30.00', treatment: 'exempt', description: 'Financial services' },
], { country: 'SA' });
{
net: '100.00', vat: '3.00', gross: '103.00',
breakdown: [
{ rate: 0.15, treatment: 'standard', net: '20.00', vat: '3.00', gross: '23.00' },
{ rate: 0, treatment: 'zero-rated', net: '50.00', vat: '0.00', gross: '50.00' },
{ rate: 0, treatment: 'exempt', net: '30.00', vat: '0.00', gross: '30.00' },
],
lines: [ … ],
}
The breakdown is one entry per rate and treatment — which is what a return asks for, and why zero-rated and exempt do not collapse into each other even though both are 0%.
Countries
| Standard rate | Since | ||
|---|---|---|---|
AE |
United Arab Emirates | 5% | 2018-01-01 |
SA |
Saudi Arabia | 15% | 2020-07-01 (5% before) |
BH |
Bahrain | 10% | 2022-01-01 (5% before) |
OM |
Oman | 5% | 2021-04-16 |
PK |
Pakistan | 18% | 2023-02-15 (17% before) |
EG |
Egypt | 14% | 2017-07-01 |
JO |
Jordan | 16% | 2018-01-01 |
QA |
Qatar | none yet | |
KW |
Kuwait | none yet |
Qatar and Kuwait return null rather than 0, because an invoice there carries
no tax line at all — which is a different thing from a zero-rated one.
These are a starting point, not tax advice. Rates change, sectors have their own treatment, and Pakistan's provinces tax services at their own rates. Every rate can be overridden:
vat(100, { rate: 0.16 }); // whatever applies to you