Tax: VAT, GST, Sales Tax, and Withholding
Tax is where “just add a percentage” goes to die. It is the most localisation-heavy area of an ERP and the one most likely to be outsourced to a specialist engine.
The tax families
| Family | How it works | Where |
|---|---|---|
| VAT / GST | Charged at every stage; businesses reclaim input tax; the consumer bears it | EU, UK, India, Australia, Canada (GST/HST), Bangladesh (Mushak), most of the world |
| US Sales & Use Tax | Charged only at final retail sale; no input credit; thousands of jurisdictions | USA |
| Withholding tax | The payer deducts and remits on behalf of the payee | India (TDS), Bangladesh, most of Asia/Africa/LatAm, US 1099 backup |
| Excise / duty | Per-unit on specific goods (alcohol, fuel, tobacco) | Global |
| Customs duty | On imports, based on HS code, origin, and value | Global |
| Digital services tax | On cross-border digital sales | Growing |
VAT mechanics
Buy raw material 1,000 + 150 input VAT → you pay supplier 1,150
Sell finished goods 2,000 + 300 output VAT → customer pays you 2,300
Remit to tax authority: 300 output − 150 input = 150
Postings:
Purchase:
Inventory Dr 1,000
Input VAT receivable Dr 150 ← an asset, you'll reclaim it
Accounts payable Cr 1,150
Sale:
Accounts receivable Dr 2,300
Revenue Cr 2,000
Output VAT payable Cr 300 ← a liability, you owe it
Period end:
Output VAT payable Dr 300
Input VAT receivable Cr 150
VAT payable to authority Cr 150
The key insight: VAT is never revenue or expense for a registered business. It flows through the balance sheet. If VAT ever lands in your P&L, something is wrong — except where input VAT is non-recoverable (entertainment, some vehicles, exempt activities), in which case it becomes part of the cost.
Tax codes
The central configuration object. A tax code bundles: rate, GL accounts, reporting box, and rules.
Code Description Rate Type Deductible Reporting box
V0 Domestic purchase 0% 0% Input Yes B7
V1 Domestic purchase standard 15% Input Yes B7 / B4
V2 Domestic purchase reduced 7.5% Input Yes B7
VE EU acquisition (reverse) 20% Both Yes B2 / B4
VN Non-deductible 15% Input No —
A0 Domestic sale exempt 0% Output — B6
A1 Domestic sale standard 15% Output — B1
AE Export 0-rated 0% Output — B6 / B8
Every document line carries a tax code. Getting it right is 90% of tax compliance.
Tax determination
The engine picks the code from a matrix. Inputs:
Ship-from country/region (where goods leave)
Ship-to country/region (where goods arrive)
Bill-to country (who is invoiced)
Customer tax classification (registered / exempt / government / export)
Material tax classification (standard / reduced / exempt / excise)
Transaction type (sale / purchase / service / asset)
Registration status of both parties
Date (rates change)
─────────────────────────────────────────────────
→ Tax code, rate, and reporting treatment
Never hardcode a rate. Rates change, sometimes with weeks’ notice, and always with an effective date. The rate must be a dated master data lookup.
The hard cases
Reverse charge
The buyer accounts for the tax instead of the seller. Used for cross-border B2B services and some domestic anti-fraud regimes (construction, electronics, carbon credits).
Reverse charge purchase, 10,000 @ 20%:
Expense Dr 10,000
Input VAT Dr 2,000 ← you claim it
Output VAT Cr 2,000 ← and you owe it
Accounts payable Cr 10,000
Net cash effect: zero. Both sides must appear on the return.
Your posting logic must generate both tax lines from a single tax code. This is a distinct code type, not a rate of zero.
Place of supply
Which country’s tax applies? Rules differ for goods vs services, B2B vs B2C, digital vs physical. EU B2C digital services are taxed where the customer is, which is why the OSS/MOSS scheme exists. Getting this wrong means registering and paying in the wrong country.
Triangulation / chain transactions
A sells to B sells to C, goods move directly A→C, across three countries. Which leg is the zero-rated intra-community supply? This has its own simplification rules and its own invoice wording requirements.
US sales tax nexus
After South Dakota v. Wayfair (2018), economic nexus means you owe sales tax in a state once you cross a revenue or transaction threshold there — no physical presence needed. With 13,000+ taxing jurisdictions (state, county, city, special districts) and address-level rate determination, nobody builds this themselves. Use Avalara, Vertex, or Sovos.
Partial exemption
If a business makes both taxable and exempt supplies (banks, insurers, healthcare, education), only a proportion of input VAT is recoverable. The proportion is calculated annually with an adjustment. This is genuinely complex and requires dedicated functionality.
Tax point / time of supply
When is the tax due? Options by jurisdiction: invoice date, delivery date, payment date, or the earlier/later of these. Cash accounting schemes shift the tax point to payment. This is why document_date matters separately from posting_date.
E-invoicing and real-time reporting
The biggest change in tax technology. Governments increasingly require invoices to be transmitted to (and often cleared by) a government platform before or as they are issued.
| Country | System | Model |
|---|---|---|
| Brazil | NF-e / NFS-e | Clearance — government authorises before goods can ship |
| Mexico | CFDI | Clearance via authorised providers (PAC) |
| Italy | SdI | Clearance for all domestic B2B/B2C |
| India | IRP + e-way bill | Clearance — Invoice Registration Number + QR code required |
| Saudi Arabia | ZATCA Fatoora | Phased clearance with cryptographic stamps |
| Poland | KSeF | Clearance |
| Hungary | RTIR | Real-time reporting |
| Turkey | e-Fatura | Clearance |
| Chile, Peru, Colombia | Various | Clearance |
| EU | ViDA | Digital Reporting Requirements rolling out through the late 2020s |
Implications for developers:
- Invoice issuance becomes a synchronous call to a government API that can fail, time out, or reject.
- You need retry logic, a queue, and a status machine (
pending → submitted → cleared → rejected → cancelled). - The invoice may not be legally valid until cleared — so you can’t print or send it yet.
- Cryptographic signing, QR codes, and specific XML schemas per country.
- Cancellation windows are limited (often 24–72 hours); after that you must issue a credit note.
- Design rule: never make document posting depend on the external call succeeding. Post, then submit asynchronously, then track clearance status. Blocking the ledger on a government API’s uptime is a bad day waiting to happen.
Withholding tax
The payer deducts tax from the payment and remits it.
Vendor invoice 100,000, WHT 10%:
Expense Dr 100,000
Accounts payable Cr 90,000
WHT payable Cr 10,000
Complexity:
- Rate depends on vendor type (company/individual/non-resident), service type, and treaty status
- Thresholds — no withholding below X per transaction or per year, then retroactive
- Certificates must be issued to the vendor (they claim credit for it)
- Separate periodic returns
- Double-tax treaty rates require documentation (tax residency certificate)
India’s TDS, Bangladesh’s AIT/VDS, and similar regimes are deep localisation. Do not underestimate them.
Build vs buy
| Build in ERP | Buy a tax engine | |
|---|---|---|
| Single country, VAT, simple products | ✅ Build | Overkill |
| US sales tax | ❌ Never build | ✅ Avalara / Vertex / Sovos |
| Multi-country VAT with e-invoicing | Possible but heavy | ✅ Usually buy |
| Withholding tax, one country | ✅ Build (localisation) | — |
Tax engines are called synchronously during pricing to return the tax amount per line. Latency matters — cache aggressively for repeated address/product combinations, and always have a fallback rate so an engine outage doesn’t stop order entry.
Reporting
Standard outputs the ERP must produce:
- VAT/GST return — box-by-box, per registration, per period
- EC Sales List / recapitulative statement (EU)
- Intrastat — statistical declaration of intra-EU goods movements, by commodity code and weight
- SAF-T — Standard Audit File for Tax; a prescribed XML dump of the full ledger. Required in Portugal, Poland, Norway, Lithuania, Romania and spreading
- Withholding certificates and returns
- Reconciliation: tax GL account balance = sum of tax line items = return total. Any mismatch is an audit finding.
Developer checklist
- Tax code on every line, no exceptions (0% is a code, not a blank)
- Rates are dated master data, never constants
- Tax is computed at line level and stored, never recalculated on display
- Tax base amount stored alongside the tax amount
- Rounding rule per jurisdiction (line-level vs invoice-level rounding differs by country)
- Reverse charge produces two tax lines
- Non-deductible input tax is added to the cost, not the tax account
- Tax registration number of both parties captured on the document
- E-invoicing submission is asynchronous with a retry queue and a status machine
- Credit notes reference the original invoice number and reverse the same tax codes
- Tax GL accounts reconcile to the return, automatically, every period