From 80b82e0117935785348e8fd2a4df1d1e7c4ec4a0 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 23 Jul 2026 19:41:46 +0000 Subject: [PATCH] Document VAT-ID-validity-vs-exemption decoupling and maxLength fixes Co-Authored-By: Claude Sonnet 5 --- README.md | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 902d7a3..19dd534 100644 --- a/README.md +++ b/README.md @@ -744,18 +744,30 @@ retroactively owes the VAT itself). cost from their normal VAT-inclusive catalog figures to net, since the whole point of the exemption is that the buyer pays less, not that this shop quietly keeps the VAT portion as extra margin. -- **Checkout UX**: `app/api/checkout/validate-vat/route.ts` gives instant - feedback on the USt-IdNr. field's blur (only once the destination - actually qualifies — no point calling VIES for a Deutschland/Schweiz - order) — "✓ Bestätigt" flips the sidebar total to the exempt (de-grossed) - figures live, as a preview. `app/api/checkout/route.ts` re-runs the exact - same VIES check server-side at submit time regardless, as the actual - source of truth — the client-side result is never trusted. **VIES being - unreachable fails closed**: normal VAT applies, never a guessed - exemption — the checkout-time check is the one place in this feature - that fails closed (contrast the Payload backend's own - `company-settings.vatId` VIES check, which fails open, since that one - only needs to catch an admin's data-entry typo, not decide a tax rate). +- **VAT-ID *validity* and the exemption *decision* are two separate + questions (fixed 2026-07-23)** — a first version only ever called VIES + when the destination already qualified for the exemption (Österreich), + so a garbage VAT ID on a domestic order (e.g. `"ED123456789"` — not even + a real country code) sailed through with just a format check, and a + genuinely valid German/Swiss VAT ID got no confirmation either. Now + `app/api/checkout/validate-vat/route.ts`/`app/api/checkout/route.ts` + check any format-valid VAT ID against VIES regardless of destination + (data quality — worth knowing whether it's real at all, same reasoning + as `company-settings.vatId`'s own check below) — the exemption itself + still only applies when the destination is *also* Österreich. A + validated German VAT ID never zero-rates a domestic sale, no matter how + real it is. +- **Checkout UX**: the USt-IdNr. field's blur gives instant feedback for + any country — "✓ USt-IdNr. bestätigt" (plus "— Lieferung wird steuerfrei + berechnet." only when the destination actually qualifies) flips the + sidebar total to the exempt (de-grossed) figures live, as a preview. + `app/api/checkout/route.ts` re-runs the exact same VIES check server-side + at submit time regardless, as the actual source of truth — the + client-side result is never trusted. **VIES being unreachable fails + closed on the exemption**: normal VAT applies, never a guessed exemption + (contrast the Payload backend's own `company-settings.vatId` VIES check, + which fails open, since that one only needs to catch an admin's + data-entry typo, not decide a tax rate). - **Every other checkout field is now blur-validated too** (fixed 2026-07-23, alongside this feature) — inline red error text appears the moment a field loses focus (required fields, email format, PLZ digit @@ -765,9 +777,18 @@ retroactively owes the VAT itself). never blurred (e.g. autofill). - **Persistence**: `Orders.vatExempt`/`vatIdValidatedAt` (Payload backend) record the outcome, decided once server-side, never editable in the - admin — see the Payload README's "B2B checkout & VAT exemption" section + admin. `vatIdValidatedAt` is set for *any* VIES-confirmed VAT ID + (data-quality audit trail), independently of whether `vatExempt` is also + true — see the Payload README's "B2B checkout & VAT exemption" section for the full field/audit-trail reasoning and the invoice PDF/EN16931 XML side of this feature. +- **Every fixed-length numeric field now hard-caps input length too** + (fixed 2026-07-23) — PLZ (`maxLength` = the selected country's own digit + count), USt-IdNr. (`14`), Packstationnummer (`3`)/Postnummer (`10`, on + the backend) all already had `pattern`/`validate` format checks, but + nothing stopped the browser from accepting more characters than could + ever pass. Same "as simple/state-of-the-art as possible" input-quality + pass as the blur-validation fix above. - **`/bestellbestaetigung`** mirrors the same exempt-totals branch from the persisted `OrderSnapshot.vatExempt` flag (it otherwise re-derives totals live from the current catalog, which would show the wrong, VAT-inclusive