Technical Documentation

Taxly: VAT Switcher App

Frontend Integration Guide for Developers

Back to Home

What is This App?

The Tax Switcher app allows Shopify store owners to display prices with or without VAT (Value Added Tax) based on customer type. The app automatically updates all prices across the storefront (product pages, collections, cart, checkout) when customers select their type.

Key Features

  • Customer Type Selection: Customers can choose between "Individual" (VAT included) or "Business" (VAT excluded) pricing
  • Country-Based VAT Rates: Store owners define VAT rates for specific countries (e.g., 20% for UK, 19% for Germany). The app automatically detects the customer's country and applies the appropriate rate
  • Flexible UI Options: Store owners can configure the app to use:
    • Customer Type Popup: A modal that appears when customers first visit
    • VAT Switcher: A floating button that allows toggling VAT on/off anytime
    • Both: Use both components together for maximum flexibility
  • Automatic Price Updates: All prices are automatically recalculated and updated when customer type changes

How VAT Rates Work

Store owners configure VAT rates through the app's admin interface by selecting a country and assigning a VAT rate percentage. When a customer visits the store, the app:

  1. Detects the customer's country using Shopify's localization system
  2. Finds the matching VAT rate for that country
  3. Applies the rate to calculate prices (for Individual customers, prices are increased by the VAT rate; for Business customers, prices remain unchanged)

Overview

This document provides technical documentation for external developers working with the Tax Switcher app's frontend components. It focuses on the publicly available interfaces, events, and how the app modifies price fields on the storefront.

Frontend Scripts

Main Script: customer-type-popup.js

The main frontend script (extensions/customer-type-popup/assets/customer-type-popup.js) is loaded on every page if one or both of the app blocks (Customer Type Popup or VAT Switcher) are enabled in the theme. The script handles both components depending on which blocks are enabled. It provides:

  1. Customer Type Popup: Displays a modal popup for customers to choose between "Individual" and "Business" customer types (if enabled in theme)
  2. VAT Switcher: Provides a floating button to toggle VAT inclusion/exclusion at any time (if enabled in theme)
  3. Price Field Updates: Automatically updates all price elements on the page based on customer type selection

Script Loading

The script is loaded via Liquid blocks in the theme:

<script src="{{ 'customer-type-popup.js' | asset_url }}" defer></script>

The script uses an IIFE (Immediately Invoked Function Expression) pattern and initializes automatically when the DOM is ready.

How Fields Are Edited

Price Element Detection

The script automatically detects price elements using a comprehensive list of CSS selectors. The complete list includes:

General Price Selectors

Cart Item Price Selectors

These selectors cover the most common Shopify theme patterns and ensure compatibility across different theme structures.

Price Update Process

1. Initial Processing

When a price element is first encountered:

  1. The script stores the original price text in a data-ctp-original attribute
  2. Marks the element as processed with data-ctp-processed="1"
  3. Marks the element as session-processed with data-ctp-session-processed="1"
// Example of how elements are marked
el.setAttribute('data-ctp-processed', '1');
el.setAttribute('data-ctp-original', el.textContent || '');
el.setAttribute('data-ctp-session-processed', '1');

2. Price Calculation

The script calculates new prices based on:

For Individual Customers:

For Business Customers:

3. Price Element Modification

The adjustPriceElement() function modifies price elements:

// Simplified example of price adjustment
function adjustPriceElement(el, mode, rate, cfg) {
  const originalText = el.getAttribute('data-ctp-original') || '';
  const parsed = parseFirstNumber(originalText);
  
  if (mode === 'Individual') {
    const increased = parsed.value * (1 + (rate / 100));
    const formattedNum = formatNumberLike(parsed.raw, increased, ...);
    el.textContent = originalText.replace(parsed.raw, formattedNum);
    ensureSuffix(el, individualSuffix, cfg);
  } else {
    // Business: keep original price, add suffix
    el.textContent = originalText;
    ensureSuffix(el, businessSuffix, cfg);
  }
}

4. Suffix Addition

The script adds VAT information suffixes to prices:

Suffixes are added as <span> elements with data-ctp-suffix="1" attribute:

<!-- Example result -->
<span class="price">
  €120.00
  <span data-ctp-suffix="1">VAT incl.</span>
</span>

Dynamic Updates

The script uses MutationObserver to watch for new price elements added to the DOM:

const mo = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    m.addedNodes.forEach(n => {
      if (n instanceof HTMLElement) {
        getPriceNodes(n).forEach(el => 
          updatePriceWithAnimation(el, mode, rate, cfg)
        );
      }
    });
  });
});
mo.observe(document.documentElement, {childList: true, subtree: true});

Cart-Specific Handling

Cart pages receive special treatment:

  1. Cart Item Mapping: The script maps cart variant IDs to product IDs for accurate VAT calculation
  2. Cart Total Calculation: Cart totals are recalculated by summing individual item totals
  3. Cart Drawer Support: Optimized updates for cart drawer/modal implementations

Public Events

customerTypeChanged Event

The script dispatches a custom event when the customer type selection changes:

window.dispatchEvent(new CustomEvent('customerTypeChanged', {
  detail: {
    selection: 'Individual' | 'Business'
  }
}));

Listening to the Event:

window.addEventListener('customerTypeChanged', function(event) {
  const selection = event.detail.selection; // 'Individual' or 'Business'
  // Your custom logic here
});

Use Cases:

Data Attributes

Element Markers

The script uses several data attributes to track and identify elements:

Attribute Purpose Example
data-ctp-processed Marks price elements that have been processed data-ctp-processed="1"
data-ctp-original Stores the original price text before modification data-ctp-original="€100.00"
data-ctp-session-processed Prevents duplicate processing in the same session data-ctp-session-processed="1"
data-ctp-suffix Identifies VAT suffix elements data-ctp-suffix="1"
data-ctp-product-identifier Stores product ID for cart items data-ctp-product-identifier="123456"

Anchor Element Data Attributes

The script reads configuration from anchor elements in the DOM:

Customer Type Popup Anchor:

<div id="customer-type-popup-anchor"
  data-preview-mode="false"
  data-show-once-per-session="false"
  data-translations='[...]'
  data-vat-rates='[...]'
  data-price-display-translations='[...]'
  data-price-display-design='{...}'
  data-locale="en"
  data-country-code="US"
  data-country-name="United States"
  data-popup-background-color="#ffffff"
  <!-- ... more theme customization attributes ... -->
></div>

VAT Switcher Block Anchor:

<div id="vat-switcher-block-anchor"
  data-preview-mode="false"
  data-vat-rates='[...]'
  data-price-display-translations='[...]'
  data-vat-switcher-translations='[...]'
  data-price-display-design='{...}'
  data-locale="en"
  data-country-code="US"
  data-button-size="medium"
  data-container-background="#ffffff"
  <!-- ... more styling attributes ... -->
></div>

Configuration Options

Theme Editor Settings

Both blocks expose settings through the Shopify theme editor:

Customer Type Popup Settings

VAT Switcher Block Settings

Metafield Configuration

The app uses Shopify metafields for configuration:

Integration Points

Listening for Price Updates

If you need to detect when prices are updated, you can:

1. Watch for customerTypeChanged events:

window.addEventListener('customerTypeChanged', function(event) {
  // Prices will be updated after this event
  setTimeout(() => {
    const prices = document.querySelectorAll('[data-ctp-processed]');
    // Process updated prices
  }, 100);
});

2. Monitor processed elements:

const observer = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      if (node.nodeType === 1 && node.hasAttribute('data-ctp-processed')) {
        // A price element was just processed
      }
    });
  });
});
observer.observe(document.body, { childList: true, subtree: true });

Custom Price Selectors

If your theme uses custom price selectors not covered by the default list, you can:

  1. Add data attributes to your price elements:
<span class="custom-price" data-ctp-processed="1" data-ctp-original="€100.00">
  €100.00
</span>
  1. Use standard Shopify price classes: The script recognizes many common patterns, but using standard Shopify classes ensures compatibility.

Cart Integration

Cart Attribute

The app sets a cart attribute when customer type is selected:

// Cart attribute is set via:
await fetch('/cart/update.js', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  },
  body: JSON.stringify({
    attributes: {
      customer_type: 'Individual' | 'Business'
    }
  })
});

Accessing Cart Attribute:

fetch('/cart.js')
  .then(res => res.json())
  .then(cart => {
    const customerType = cart.attributes.customer_type;
    // 'Individual' or 'Business'
  });

Cart Events

The script listens to various cart events for real-time updates:

You can trigger these events to force price updates:

document.dispatchEvent(new CustomEvent('cart:updated'));

Storage

Local Storage / Session Storage

The app stores customer type selection:

Accessing Stored Selection:

const selection = localStorage.getItem('customer_type_selection') || 
                  sessionStorage.getItem('customer_type_selection');
// Returns: 'Individual', 'Business', or null

CSS Classes and Styling

Popup Classes

VAT Switcher Classes

Price Animation Classes

CSS Custom Properties

The script sets CSS custom properties for theming:

Performance Considerations

Caching

The script implements several caching mechanisms:

  1. Price Elements Cache: Caches DOM queries for 1 second
  2. VAT Rate Cache: Caches VAT rate lookups for 5 seconds
  3. Cart Mapping Cache: Caches variant-to-product mappings in sessionStorage

Optimization Strategies

  1. Batch DOM Updates: Multiple price updates are batched to reduce reflows
  2. RequestAnimationFrame: Price visibility updates use requestAnimationFrame for smooth transitions
  3. Debounced Observers: MutationObserver callbacks are optimized to prevent excessive processing

Browser Compatibility

The script is compatible with:

Troubleshooting

Prices Not Updating

  1. Check if elements are detected:
const prices = document.querySelectorAll('[data-ctp-processed]');
console.log('Processed prices:', prices.length);
  1. Verify customer type selection:
const selection = localStorage.getItem('customer_type_selection') || 
                  sessionStorage.getItem('customer_type_selection');
console.log('Customer type:', selection);
  1. Check for JavaScript errors: Open browser console and look for errors related to customer-type-popup.js

Prices Flickering

The script attempts to hide prices initially to prevent flicker. If flickering occurs:

  1. Ensure the script loads with defer attribute
  2. Check that CSS is loaded before the script
  3. Verify that price elements have proper data-ctp-processed attributes

Cart Totals Not Updating

Cart totals require special handling. The script:

  1. Identifies cart total elements using specific selectors
  2. Recalculates totals from individual item prices
  3. Restructures the DOM layout for proper display

If totals aren't updating:

  1. Check browser console for errors
  2. Verify cart structure matches expected selectors
  3. Ensure cart items have been processed first

API Reference Summary

Events

Event Description Detail
customerTypeChanged Fired when customer type selection changes { selection: 'Individual' | 'Business' }

Data Attributes

Attribute Type Description
data-ctp-processed string Marks processed price elements
data-ctp-original string Original price text
data-ctp-session-processed string Session processing marker
data-ctp-suffix string VAT suffix marker
data-ctp-product-identifier string Product identifier for cart items

Storage Keys

Key Storage Type Description
customer_type_selection localStorage/sessionStorage Current customer type selection
ctp_cart_v2p sessionStorage Cart variant-to-product mapping
ctp_cart_h2p sessionStorage Cart handle-to-product mapping

Best Practices

  1. Don't modify processed elements directly: Always check for data-ctp-processed before modifying price elements
  2. Listen to events: Use customerTypeChanged event for custom integrations
  3. Respect storage: Don't overwrite customer_type_selection without understanding the app's logic
  4. Test with different themes: Price selectors vary by theme, test thoroughly
  5. Monitor performance: Large catalogs may require optimization

Support

For issues or questions:

  1. Check browser console for JavaScript errors
  2. Verify theme compatibility
  3. Review this documentation for integration patterns
  4. Contact app support with specific error messages and steps to reproduce

Email: contact@zonvi.io