Check VAT ID validity via VIES for any country, not just Österreich
VAT-ID validity and the exemption decision are separate questions.
Previously VIES was only ever called when the destination already
qualified for the cross-border exemption (Österreich), so a garbage
VAT ID on a domestic order (e.g. "ED123456789" — not even a real
country code) sailed through with no feedback at all, and a
Deutschland/Schweiz customer got no confirmation their real VAT ID
was valid either. Now VIES checks any format-valid VAT ID regardless
of destination (data quality, same reasoning as company-settings'
own check) — the exemption itself still only applies when the
destination is also Österreich, a validated German VAT ID never
zero-rates a domestic sale. The status message now always shows
("✓ USt-IdNr. bestätigt", plus the exemption note only when it
actually applies) instead of staying hidden for non-Österreich
orders.
Also added maxLength to PLZ (per-country digit count) and USt-IdNr.
(14) checkout fields — they had pattern validation but nothing
stopping the browser from accepting more characters than could ever
be valid.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -345,14 +345,19 @@ export function CheckoutContent({
|
||||
const displayTotal = exemptTotalsPreview?.total ?? total;
|
||||
|
||||
// USt-IdNr. blur — format-checks first (always), then a live VIES lookup
|
||||
// only once the destination actually qualifies (Österreich) — no point
|
||||
// hitting the EU's API for a Deutschland/Schweiz order, where this
|
||||
// exemption never applies regardless of what VIES says.
|
||||
// for ANY country, not just Österreich. Two genuinely separate concerns:
|
||||
// whether this is a real, currently-registered VAT ID at all (data
|
||||
// quality — worth knowing regardless of destination, same reasoning as
|
||||
// company-settings.vatId's own VIES check on the backend) vs. whether
|
||||
// *this transaction* qualifies for the cross-border exemption (a
|
||||
// narrower legal question, still gated on isExemptionEligibleCountry()
|
||||
// wherever vatExemptPreview/exemptTotalsPreview are computed below — a
|
||||
// validated German VAT ID never zero-rates a domestic sale).
|
||||
async function handleVatIdBlur(e: React.FocusEvent<HTMLInputElement>) {
|
||||
const value = e.target.value;
|
||||
const formatError = validateVatIdFormat(value);
|
||||
setFieldError("vatId", formatError);
|
||||
if (!value.trim() || formatError || !isExemptionEligibleCountry(destinationCountry(country, hasDifferentShippingAddress, shippingCountry))) {
|
||||
if (!value.trim() || formatError) {
|
||||
setVatIdViesStatus("idle");
|
||||
return;
|
||||
}
|
||||
@@ -708,23 +713,28 @@ export function CheckoutContent({
|
||||
placeholder="DE123456789"
|
||||
autoComplete="off"
|
||||
pattern="[A-Za-z]{2}[A-Za-z0-9]{2,12}"
|
||||
maxLength={14}
|
||||
title="EU-Format: 2 Buchstaben Länderpräfix + bis zu 12 alphanumerische Zeichen, z. B. DE123456789."
|
||||
wrapperClassName="w-full"
|
||||
/>
|
||||
{/* Only shown once the destination actually qualifies
|
||||
(Österreich) — a "checking..."/status message for a
|
||||
Deutschland/Schweiz order would be meaningless noise,
|
||||
the exemption never applies there regardless. */}
|
||||
{isExemptionEligibleCountry(buyerDestinationCountry) && (
|
||||
<p className="text-label text-text-muted">
|
||||
{vatIdViesStatus === "checking" && "USt-IdNr. wird geprüft…"}
|
||||
{vatIdViesStatus === "valid" && (
|
||||
<span className="text-success">✓ Bestätigt — Lieferung wird steuerfrei berechnet.</span>
|
||||
)}
|
||||
{vatIdViesStatus === "invalid" && "USt-IdNr. konnte nicht bestätigt werden — reguläre MwSt. wird berechnet."}
|
||||
{vatIdViesStatus === "unavailable" && "Prüfung derzeit nicht möglich — reguläre MwSt. wird berechnet."}
|
||||
</p>
|
||||
)}
|
||||
{/* Shown for any country — validity is worth confirming
|
||||
regardless of destination (data quality: is this VAT ID
|
||||
even real). Only the "valid" message's wording differs
|
||||
by destination: Österreich additionally gets the
|
||||
exemption note, Deutschland/Schweiz just get a plain
|
||||
confirmation, since the exemption never applies there
|
||||
even for a genuinely valid VAT ID. */}
|
||||
<p className="text-label text-text-muted min-h-[1.05rem]">
|
||||
{vatIdViesStatus === "checking" && "USt-IdNr. wird geprüft…"}
|
||||
{vatIdViesStatus === "valid" && (
|
||||
<span className="text-success">
|
||||
✓ USt-IdNr. bestätigt
|
||||
{isExemptionEligibleCountry(buyerDestinationCountry) ? " — Lieferung wird steuerfrei berechnet." : "."}
|
||||
</span>
|
||||
)}
|
||||
{vatIdViesStatus === "invalid" && "USt-IdNr. konnte nicht bestätigt werden."}
|
||||
{vatIdViesStatus === "unavailable" && "USt-IdNr.-Prüfung derzeit nicht möglich."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* w-[calc(50%-0.5rem)] at sm: — exactly matches Vorname's
|
||||
@@ -804,6 +814,7 @@ export function CheckoutContent({
|
||||
autoComplete="postal-code"
|
||||
inputMode="numeric"
|
||||
pattern={plzPattern(country)}
|
||||
maxLength={PLZ_DIGITS[country] ?? 4}
|
||||
title={`PLZ muss aus ${PLZ_DIGITS[country] ?? 4} Ziffern bestehen.`}
|
||||
required
|
||||
/>
|
||||
@@ -962,6 +973,7 @@ export function CheckoutContent({
|
||||
autoComplete="off"
|
||||
inputMode="numeric"
|
||||
pattern={plzPattern(shippingCountry)}
|
||||
maxLength={PLZ_DIGITS[shippingCountry] ?? 4}
|
||||
title={`PLZ muss aus ${PLZ_DIGITS[shippingCountry] ?? 4} Ziffern bestehen.`}
|
||||
required
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user