v1.0.0
15 Sep 2026First release.
Added
vat(),add()andextract()for one amount, with extraction done asgross × rate ÷ (1 + rate)rather thangross × rate— the commonest mistake in this arithmetic, which overstates the tax by the rate squared.invoice()for many lines, each able to carry its own rate and treatment, with a breakdown by rate and treatment because that is what a tax return asks for.- Integer minor-unit arithmetic throughout: every amount becomes whole fils,
halalas or paisa on arrival, so a thousand lines of 0.07 come to exactly 70.00.
Results carry
minoralongside the strings so the next sum does not start from a parse. rounding_level: 'line' | 'total', because taxing each line and summing gives a different answer from taxing the total — 0.20 against 0.15 on ten lines of 0.30 — and authorities specify which.rounding: 'half-up' | 'half-even' | 'down' | 'up', with half-up going away from zero as tax rules specify.- Rates with effective dates for the UAE, Saudi Arabia, Bahrain, Oman, Pakistan,
Egypt and Jordan, so
rateFor('SA', '2019-06-01')is 5% and today is 15%.historyFor()returns the changes on record. nullrather than0for Qatar and Kuwait, which have announced VAT and not introduced it — an invoice there carries no tax line at all.- Treatments for zero-rated, exempt, out-of-scope and reverse charge, kept distinct through the breakdown even though all four are 0%.
- Per-currency decimals: two for AED, SAR, PKR and EGP, three for BHD, OMR, KWD and JOD.
- Quantities and per-line discounts, applied before tax.
- Amounts refused rather than guessed at: a thousands separator or Arabic-Indic digits throw instead of silently parsing to something wrong.
- 2.0 kB gzip, zero dependencies.