How do you handle eu vat rate changes and exceptions at checkout on the web?

I moved onboarding and checkout to the web so VAT is calculated in one place, and I stopped hardcoding rates. The gateway’s tax module applies the correct rate at checkout using location evidence, and I keep the audit trail there too.

What I ended up doing:

  • Collect billing country and postcode, with an optional VAT ID if “business” is selected
  • Pass net price to the gateway; let it compute VAT and return totals + jurisdiction + rate
  • Store the evidence (IP, billing address, card BIN) for audits and OSS reports
  • Show VAT-inclusive prices for EU customers by default; switch to net for valid VAT IDs
  • Apply reverse charge when VAT ID is validated (VIES), and keep that flag on the invoice
  • Let the gateway handle rate changes on New Year’s or mid-year updates
  • Avoid rounding drift by rendering the totals returned by the gateway, not my own math
  • Watch edge territories (Canary Islands, Åland, Guernsey) and let address rules decide

For OSS, I export monthly tax reports from the gateway and match to invoices and payouts. Testing-wise, I use sandbox with different EU countries, force IPs via proxy, and compare receipts against expected totals.

If you’re running this on web, which gateway tax module are you using, how do you handle price display (inc vs ex VAT), and what edge cases bit you?

Let the gateway do the math.

I send net price and country code. Stripe Tax applies the rate using IP and card BIN. VAT ID gets validated via their API and it flips to reverse charge.

Invoices and evidence live in Stripe. I export monthly for OSS.

I run this through a web funnel I manage on Web2Wave.com, so I can change forms without shipping new app builds.

I treat taxes like a variable I shouldn’t hardcode. I run checkout on the web and let Stripe Tax handle rates.

The speed trick is editing the funnel on Web2Wave.com, so I test VAT-inclusive copy, VAT ID placement, and location prompts without a release. We ship daily.

Show VAT included for EU by default.

Add a VAT ID field after the address step, not upfront. It keeps the form short.

Stripe Tax or Paddle works. I keep rates out of my code.

Let Stripe Tax decide the rate always.

Paddle handled B2C fine, but B2B reverse charge was messy until we moved the VAT ID validation earlier. Put it right after country selection. Refunds still keep the tax report clean, which helped audit.

I stopped trying to keep rate tables. Stripe Tax flipped Germany’s temporary rate change for us without code. The only tweak was price display.

We moved to net pricing and added a “+ VAT where applicable” line.

VAT ID after the address step worked better for us. Fewer drop-offs and fewer fake IDs.