6 Validation Surfaces

Multi-Surface
VAT Collection

From cart to customer account, VAT validation is embedded at every touchpoint of the buyer journey. Each surface is purpose-built for its context.

🛒 Cart Page All Plans Cart Drawer All Plans 💳 Checkout Plus Only Thank You All Plans 👤 Account All Plans Function All Plans
1

Cart Page (App Block)

Primary VAT collection point — Theme App Extension
Extension
vat-cart-widget
Authentication
App Proxy HMAC Verification
API Endpoint
/apps/vat-validator/*
Bundle Size
~8KB vanilla JavaScript

Features

  • VAT input field with real-time format validation
  • Debounced VIES validation (500ms delay)
  • Company info display on successful validation
  • Cart attributes update (vat_number, status, company, timestamp)
  • Checkout button blocking (JS-side, configurable)
  • Auto-populate from customer metafield (logged-in users)
  • Configurable required/optional per country

Settings Loaded From

GET /api/proxy/settings
cartEnabled, cartRequired, cartBlockCheckout, showCompanyInfo, requiredCountries
GET /api/proxy/customer-vat
Auto-populate VAT for logged-in customers

Cart Attributes Written

vat_number
The validated VAT number
vat_validation_status
valid / invalid / format_only
vat_company_name
Company name from VIES
vat_validated_at
ISO 8601 timestamp
Cart Page — /cart
VAT Number optional
VAT number is valid. Tax exemption applied.
Registered Company
ACME GmbH
Musterstrasse 42, 10115 Berlin
VAT Number required
Invalid VAT number. Please check and try again.
VAT Number optional
Validating VAT number with VIES...

Cart Validation Flow

User types
VAT number
🔎
Client-side
format check
Debounce
500ms
🚀
POST /api/
proxy/validate
🌐
VIES API
validation
Show result
+ update cart

2

Cart Drawer (App Embed)

Automatic drawer detection and widget injection
Extension
vat-cart-widget (same extension, embed target)
Type
App Embed (body target, all pages)
Detection
Known selectors + MutationObserver
Conflict Prevention
Disabled on /cart page (App Block active)

Features

  • Automatic drawer detection using known CSS selectors
  • Widget injection into drawer footer area
  • MutationObserver for drawer open/close/re-render events
  • Fetch intercept for Section Rendering API
  • State preservation across drawer re-renders
  • Conflict prevention with App Block on /cart page
  • Custom selector fallback for unsupported themes

Intercepted Fetch Requests

/cart/change /cart/update /cart/add

Theme Compatibility

Dawn
Debut
Impulse
Prestige
Refresh
Generic
Cart Drawer — Slide-out Panel

Your Cart (2)

Premium Widget Pro
$49.99 x 1
Starter Pack Bundle
$29.99 x 2

Known Drawer Selectors

cart-drawer
#CartDrawer
[data-cart-drawer]
.drawer--cart
.mini-cart
.cart-drawer
.side-cart
.slide-cart
[data-ajax-cart]
.ajaxcart
[class*="cart-drawer"]
[id*="cart-drawer"]
[id*="CartDrawer"]

Injection Target Selectors

.cart-drawer__footer
.drawer__footer
.mini-cart__footer
[data-cart-footer]
.cart-drawer__subtotal
.totals
.drawer__contents
.cart__contents
form[action="/cart"]
#CartDrawer-Form

3

Checkout (Plus Only)

Checkout UI Extension with journey intercept and guest exemption
Extension
vat-checkout-ui
Target
purchase.checkout.contact.render-after
Authentication
Session Token JWT
Capabilities
api_access network_access block_progress

Features

  • Auto-populate from customer metafield (useAppMetafields)
  • Pre-fill from cart attributes (useAttributeValues)
  • useBuyerJourneyIntercept for checkout blocking
  • Guest exemption flow (customer create + taxExempt)
  • Session token expire handling (401 refresh + retry)
  • Reverse Charge evaluation on valid VAT
  • Real-time country detection from shipping address

🔒 Guest Exemption Flow (Plus Only)

1 Get guest email from checkout session
2 Search existing customer by email (Admin API)
3 If not found, create new customer with tag vat-auto-created
4 Set customer.taxExempt = true
5 Shopify recalculates tax automatically (VAT removed)
Checkout — Contact Information
💳 Checkout — yourstore.myshopify.com
⚖ VAT Validation
✔ Valid — ACME GmbH, Berlin
ⓘ Reverse Charge applied. VAT will be removed from your order.
Blocking State Example:
⚠ A valid VAT number is required to proceed to payment. Please enter your VAT number above.

4

Thank You Page

Post-checkout VAT collection with race condition handling
Extension
vat-thank-you
Target
purchase.thank-you.block.render
Authentication
Session Token JWT
Polling
3 attempts, 3s intervals (max 9s)

Features

  • Check existing VAT on order metafield
  • Race condition handling (webhook vs extension timing)
  • Cart attributes polling (3 attempts, 3s intervals)
  • VAT input form if no existing VAT found
  • Order metafield writing on valid VAT
  • Reverse Charge evaluation and tagging
  • Future order tax exemption (customer.taxExempt)

UI States

State Condition Display
hasExistingVat=true Metafield filled Info banner (VAT already recorded)
status='processing' Cart VAT exists, webhook pending "Your VAT is being processed..." spinner
status='idle' No VAT anywhere Input form for VAT entry

⚠ Race Condition Solution

1 Check order metafield (webhook completed?)
2 Check cart note_attributes (VAT from cart?)
3 If cart VAT exists, poll /api/thankyou/check-vat every 3s, max 3 times
4 If still no metafield after 9s, show input form
Thank You — Order Confirmed

Thank you for your order!

Order #1042 confirmed

Processing VAT
Your VAT number is being verified...
Add VAT Number to This Order
You can still add a VAT number for tax exemption on future orders.
@keyframes spin { to { transform: rotate(360deg); } }

5

Customer Account

VAT profile management with cross-surface auto-populate
Extension
vat-customer-account
Target
customer-account.profile.block.render
Authentication
Session Token JWT
Storage
Customer metafield + customer_vat_profiles table

Features

  • Display mode: saved VAT + company with edit/remove
  • Edit mode: VAT input with real-time validation
  • Save to customer metafield + local DB
  • Remove VAT (clear metafield + DB record)
  • Auto-populate integration (saved VAT pre-fills cart/checkout)
  • Tax exemption on save (customer.taxExempt = true)
  • Customer tagged with vat-exempt

On Save Actions

  • Write customer metafield (vat_number, company_name)
  • Insert/update customer_vat_profiles table
  • Set customer.taxExempt = true
  • Add tag: vat-exempt

On Remove Actions

  • Clear customer metafield
  • Mark DB record as removed
  • Set customer.taxExempt = false
  • Remove tag: vat-exempt
Customer Account — Profile

6

Shopify Function (Cart Validation)

Server-side checkout blocking via purchase.validation.run
Extension
vat-cart-validation
Type
Shopify Function (purchase.validation.run)
Plans
All Plans
Config Source
Shop metafield cart_blocking_config

Features

  • Read cart attributes (vat_number, vat_validation_status)
  • Read shop metafield (cart_blocking_config)
  • Block checkout if VAT required and none provided
  • Block checkout if blockCheckout enabled and VAT invalid
  • Format re-validation (security against cart attribute manipulation)

Config from Shop Metafield

{
  "blockCheckout": false,
  "required": false
}
Synced from admin settings via syncSettingsToMetafield()

Validation Logic

  • If required=true and no VAT number: block with error
  • If blockCheckout=true and status is invalid: block with error
  • Re-validate format even if status says valid (anti-tampering)
  • If neither condition met: allow checkout to proceed
purchase.validation.run — Logic
vat-cart-validation/src/run.ts
// Read cart attributes
const vatNumber = getAttribute("vat_number")
const status = getAttribute("vat_validation_status")

// Read shop config
const config = getMetafield("cart_blocking_config")

// Check: required but missing
if (config.required && !vatNumber) {
  return { errors: [{
    localizedMessage:
      "VAT number is required",
    target: "$.cart"
  }] }
}

// Check: block invalid
if (config.blockCheckout
  && status === "invalid") {
  return { errors: [{
    localizedMessage:
      "Invalid VAT number",
    target: "$.cart"
  }] }
}

// Format re-validation
if (vatNumber && !isValidFormat(vatNumber)) {
  return { errors: [...] }
}

// All clear
return { errors: [] }

📩 ORDERS_CREATE Webhook

Triggered when checkout completes. Bridges cart attributes to order metafields and handles post-purchase processing.
Read Cart Attributes
Reads VAT data from order.note_attributes (vat_number, vat_validation_status, vat_company_name, vat_validated_at)
Write Order Metafields
Writes structured metafields: vat_number, company_name, is_valid, validated_at, reverse_charge, reverse_charge_note
Add Order Tags
Tags order with vat-validated and reverse-charge when applicable
Idempotency Check
Uses webhook_logs unique index on order_id + topic to prevent duplicate processing
Admin API Retry
Retry wrapper handles 429 (rate limit) and 5xx (server error) responses with exponential backoff
Error Handling
Failed webhook processing is logged and can be retried. Non-critical failures do not block order completion

Cross-Surface Auto-Populate Flow

When a customer saves their VAT number in the Account page, it automatically pre-fills across all other surfaces.
1

Account Save

Customer saves VAT in Account page. Customer metafield is written.

2

Cart Pre-fill

Cart page calls GET /api/proxy/customer-vat and auto-fills input.

3

Checkout Pre-fill

Checkout extension reads customer metafield via useAppMetafields.

4

Thank You Info

Order already has VAT from cart/checkout. Info banner shown instead of input.

Surface Comparison

Side-by-side comparison of capabilities across all validation surfaces

Feature Cart Drawer Checkout Thank You Account
Plans All All Plus Only All All
Authentication App Proxy App Proxy Session Token Session Token Session Token
Technology Vanilla JS Vanilla JS Preact Preact Preact
Auto-populate Yes Yes Yes N/A Source
Tax Exemption Logged-in Logged-in All (guest too) Future orders On save
Reverse Charge Via webhook Via webhook Direct Direct N/A
VIES Validation Real-time Real-time Real-time Real-time Real-time
Checkout Blocking JS-side JS-side Native N/A N/A
Company Info Yes Yes Yes Yes Yes
Guest Support No No Yes Yes No (login req)