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-widgetAuthentication
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
Cart Validation Flow
User types
VAT number
VAT number
Client-side
format check
format check
Debounce
500ms
500ms
POST /api/
proxy/validate
proxy/validate
VIES API
validation
validation
Show result
+ update cart
+ 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-uiTarget
purchase.checkout.contact.render-afterAuthentication
Session Token JWT
Capabilities
api_access network_access block_progressFeatures
- ✓ 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
Contact Information
⚖ VAT Validation
✔ Valid — ACME GmbH, Berlin
ⓘ Reverse Charge applied. VAT will be removed from your order.
Shipping Address
Blocking State Example:
4
Thank You Page
Post-checkout VAT collection with race condition handling
Extension
vat-thank-youTarget
purchase.thank-you.block.renderAuthentication
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.
5
Customer Account
VAT profile management with cross-surface auto-populate
Extension
vat-customer-accountTarget
customer-account.profile.block.renderAuthentication
Session Token JWT
Storage
Customer metafield +
customer_vat_profiles tableFeatures
- ✓ 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
VAT Information
✔ Verified
DE123456789
ACME GmbH — Berlin, Germany
Edit VAT Number
⌛ Validating with VIES...
6
Shopify Function (Cart Validation)
Server-side checkout blocking via purchase.validation.run
Extension
vat-cart-validationType
Shopify Function (
purchase.validation.run)Plans
All Plans
Config Source
Shop metafield
cart_blocking_configFeatures
- ✓ 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
}
"blockCheckout": false,
"required": false
}
Synced from admin settings via
syncSettingsToMetafield()
Validation Logic
- ✓ If
required=trueand no VAT number: block with error - ✓ If
blockCheckout=trueand status isinvalid: 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: [] }
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 applicableIdempotency Check
Uses
webhook_logs unique index on order_id + topic to prevent duplicate processingAdmin 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) |