JAVASCRIPTshopifyadvanced

Shopify Multi-Currency Converter

Add multi-currency support with automatic conversion and detection

#shopify#currency#multi-currency#conversion#internationalization
Share this snippet:

Code

javascript
1// Currency Converter Class
2class CurrencyConverter {
3 constructor(options = {}) {
4 this.shopCurrency = options.shopCurrency || 'USD';
5 this.currentCurrency = this.getCurrentCurrency();
6 this.rates = {};
7 this.selectors = {
8 money: '[data-currency-price]',
9 switcher: '[data-currency-switcher]',
10 };
11
12 this.init();
13 }
14
15 async init() {
16 // Load exchange rates
17 await this.loadExchangeRates();
18
19 // Create currency switcher
20 this.createCurrencySwitcher();
21
22 // Convert all prices on page
23 this.convertAllPrices();
24
25 // Watch for dynamically added prices
26 this.observePrices();
27 }
28
29 getCurrentCurrency() {
30 // Check localStorage
31 const saved = localStorage.getItem('selectedCurrency');
32 if (saved) return saved;
33
34 // Auto-detect from user location (requires API)
35 // For now, return shop currency
36 return this.shopCurrency;
37 }
38
39 async loadExchangeRates() {
40 try {
41 // Option 1: Use a free exchange rate API
42 const response = await fetch('https://api.exchangerate-api.com/v4/latest/' + this.shopCurrency);
43 const data = await response.json();
44 this.rates = data.rates;
45
46 // Option 2: Use Shopify's built-in currency files (if available)
47 // const response = await fetch('/services/javascripts/currencies');
48 // Parse and use Shopify's rates
49
50 } catch (error) {
51 console.error('Failed to load exchange rates:', error);
52 // Fallback rates
53 this.rates = {
54 'USD': 1.00,
55 'EUR': 0.85,
56 'GBP': 0.73,
57 'CAD': 1.25,
58 'AUD': 1.35,
59 };
60 }
61 }
62
63 createCurrencySwitcher() {
64 const switchers = document.querySelectorAll(this.selectors.switcher);
65
66 switchers.forEach(switcher => {
67 const currencies = Object.keys(this.rates);
68
69 const select = document.createElement('select');
70 select.className = 'currency-select';
71
72 currencies.forEach(code => {
73 const option = document.createElement('option');
74 option.value = code;
75 option.textContent = this.getCurrencySymbol(code) + ' ' + code;
76
77 if (code === this.currentCurrency) {
78 option.selected = true;
79 }
80
81 select.appendChild(option);
82 });
83
84 select.addEventListener('change', (e) => {
85 this.changeCurrency(e.target.value);
86 });
87
88 switcher.appendChild(select);
89 });
90 }
91
92 changeCurrency(newCurrency) {
93 this.currentCurrency = newCurrency;
94 localStorage.setItem('selectedCurrency', newCurrency);
95
96 this.convertAllPrices();
97
98 // Trigger custom event
99 document.dispatchEvent(new CustomEvent('currency:changed', {
100 detail: { currency: newCurrency }
101 }));
102 }
103
104 convertAllPrices() {
105 const priceElements = document.querySelectorAll(this.selectors.money);
106
107 priceElements.forEach(el => {
108 this.convertPrice(el);
109 });
110 }
111
112 convertPrice(element) {
113 const basePrice = parseFloat(element.dataset.currencyPrice);
114
115 if (isNaN(basePrice)) return;
116
117 const rate = this.rates[this.currentCurrency] || 1;
118 const convertedPrice = basePrice * rate;
119
120 element.textContent = this.formatMoney(convertedPrice, this.currentCurrency);
121 }
122
123 formatMoney(amount, currency) {
124 const symbol = this.getCurrencySymbol(currency);
125
126 return symbol + amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
127 }
128
129 getCurrencySymbol(currency) {
130 const symbols = {
131 'USD': '$',
132 'EUR': '€',
133 'GBP': '£',
134 'JPY': '¥',
135 'CAD': 'C$',
136 'AUD': 'A$',
137 'CHF': 'Fr',
138 'CNY': '¥',
139 'SEK': 'kr',
140 'NZD': 'NZ$',
141 };
142
143 return symbols[currency] || currency + ' ';
144 }
145
146 observePrices() {
147 // Watch for new price elements added to DOM
148 const observer = new MutationObserver((mutations) => {
149 mutations.forEach((mutation) => {
150 mutation.addedNodes.forEach((node) => {
151 if (node.nodeType === 1) {
152 const prices = node.querySelectorAll?.(this.selectors.money);
153 prices?.forEach(el => this.convertPrice(el));
154 }
155 });
156 });
157 });
158
159 observer.observe(document.body, {
160 childList: true,
161 subtree: true
162 });
163 }
164
165 // Get user's country for auto-detection
166 async detectUserCountry() {
167 try {
168 const response = await fetch('https://ipapi.co/json/');
169 const data = await response.json();
170 return data.country_code;
171 } catch (error) {
172 console.error('Failed to detect country:', error);
173 return null;
174 }
175 }
176
177 // Map country to currency
178 getCurrencyByCountry(countryCode) {
179 const mapping = {
180 'US': 'USD',
181 'GB': 'GBP',
182 'CA': 'CAD',
183 'AU': 'AUD',
184 'DE': 'EUR',
185 'FR': 'EUR',
186 'IT': 'EUR',
187 'ES': 'EUR',
188 'JP': 'JPY',
189 // Add more mappings
190 };
191
192 return mapping[countryCode] || this.shopCurrency;
193 }
194}
195
196// Initialize
197document.addEventListener('DOMContentLoaded', () => {
198 window.currencyConverter = new CurrencyConverter({
199 shopCurrency: 'USD'
200 });
201});

Shopify Multi-Currency Converter

Implement multi-currency support with automatic currency detection, conversion, and a currency switcher dropdown.

// Currency Converter Class
class CurrencyConverter {
    constructor(options = {}) {
        this.shopCurrency = options.shopCurrency || 'USD';
        this.currentCurrency = this.getCurrentCurrency();
        this.rates = {};
        this.selectors = {
            money: '[data-currency-price]',
            switcher: '[data-currency-switcher]',
        };

        this.init();
    }

    async init() {
        // Load exchange rates
        await this.loadExchangeRates();

        // Create currency switcher
        this.createCurrencySwitcher();

        // Convert all prices on page
        this.convertAllPrices();

        // Watch for dynamically added prices
        this.observePrices();
    }

    getCurrentCurrency() {
        // Check localStorage
        const saved = localStorage.getItem('selectedCurrency');
        if (saved) return saved;

        // Auto-detect from user location (requires API)
        // For now, return shop currency
        return this.shopCurrency;
    }

    async loadExchangeRates() {
        try {
            // Option 1: Use a free exchange rate API
            const response = await fetch('https://api.exchangerate-api.com/v4/latest/' + this.shopCurrency);
            const data = await response.json();
            this.rates = data.rates;

            // Option 2: Use Shopify's built-in currency files (if available)
            // const response = await fetch('/services/javascripts/currencies');
            // Parse and use Shopify's rates

        } catch (error) {
            console.error('Failed to load exchange rates:', error);
            // Fallback rates
            this.rates = {
                'USD': 1.00,
                'EUR': 0.85,
                'GBP': 0.73,
                'CAD': 1.25,
                'AUD': 1.35,
            };
        }
    }

    createCurrencySwitcher() {
        const switchers = document.querySelectorAll(this.selectors.switcher);

        switchers.forEach(switcher => {
            const currencies = Object.keys(this.rates);

            const select = document.createElement('select');
            select.className = 'currency-select';

            currencies.forEach(code => {
                const option = document.createElement('option');
                option.value = code;
                option.textContent = this.getCurrencySymbol(code) + ' ' + code;

                if (code === this.currentCurrency) {
                    option.selected = true;
                }

                select.appendChild(option);
            });

            select.addEventListener('change', (e) => {
                this.changeCurrency(e.target.value);
            });

            switcher.appendChild(select);
        });
    }

    changeCurrency(newCurrency) {
        this.currentCurrency = newCurrency;
        localStorage.setItem('selectedCurrency', newCurrency);

        this.convertAllPrices();

        // Trigger custom event
        document.dispatchEvent(new CustomEvent('currency:changed', {
            detail: { currency: newCurrency }
        }));
    }

    convertAllPrices() {
        const priceElements = document.querySelectorAll(this.selectors.money);

        priceElements.forEach(el => {
            this.convertPrice(el);
        });
    }

    convertPrice(element) {
        const basePrice = parseFloat(element.dataset.currencyPrice);

        if (isNaN(basePrice)) return;

        const rate = this.rates[this.currentCurrency] || 1;
        const convertedPrice = basePrice * rate;

        element.textContent = this.formatMoney(convertedPrice, this.currentCurrency);
    }

    formatMoney(amount, currency) {
        const symbol = this.getCurrencySymbol(currency);

        return symbol + amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    getCurrencySymbol(currency) {
        const symbols = {
            'USD': '$',
            'EUR': '€',
            'GBP': '£',
            'JPY': '¥',
            'CAD': 'C$',
            'AUD': 'A$',
            'CHF': 'Fr',
            'CNY': '¥',
            'SEK': 'kr',
            'NZD': 'NZ$',
        };

        return symbols[currency] || currency + ' ';
    }

    observePrices() {
        // Watch for new price elements added to DOM
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === 1) {
                        const prices = node.querySelectorAll?.(this.selectors.money);
                        prices?.forEach(el => this.convertPrice(el));
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // Get user's country for auto-detection
    async detectUserCountry() {
        try {
            const response = await fetch('https://ipapi.co/json/');
            const data = await response.json();
            return data.country_code;
        } catch (error) {
            console.error('Failed to detect country:', error);
            return null;
        }
    }

    // Map country to currency
    getCurrencyByCountry(countryCode) {
        const mapping = {
            'US': 'USD',
            'GB': 'GBP',
            'CA': 'CAD',
            'AU': 'AUD',
            'DE': 'EUR',
            'FR': 'EUR',
            'IT': 'EUR',
            'ES': 'EUR',
            'JP': 'JPY',
            // Add more mappings
        };

        return mapping[countryCode] || this.shopCurrency;
    }
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    window.currencyConverter = new CurrencyConverter({
        shopCurrency: 'USD'
    });
});

Liquid Template Integration

<!-- Product Price -->
<div class="product-price">
    <span data-currency-price="{{ product.price | divided_by: 100.0 }}">
        {{ product.price | money }}
    </span>
</div>

<!-- Compare at Price -->
{% if product.compare_at_price > product.price %}
    <span class="compare-price" data-currency-price="{{ product.compare_at_price | divided_by: 100.0 }}">
        {{ product.compare_at_price | money }}
    </span>
{% endif %}

<!-- Currency Switcher -->
<div class="currency-switcher">
    <label>Currency:</label>
    <div data-currency-switcher></div>
</div>

<!-- Cart Total -->
<div class="cart-total">
    <span>Total:</span>
    <span data-currency-price="{{ cart.total_price | divided_by: 100.0 }}">
        {{ cart.total_price | money }}
    </span>
</div>

CSS Styling

.currency-switcher {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
}

.currency-select {
    padding: 8px 30px 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
    font-size: 14px;
    appearance: none;
    background-image: url('data:image/svg+xml;utf8,<svg fill="%23333" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>');
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 16px;
}

.currency-select:hover {
    border-color: #999;
}

.currency-select:focus {
    outline: none;
    border-color: #0066cc;
}

[data-currency-price] {
    font-weight: bold;
}

Shopify Markets Integration

// For stores using Shopify Markets
class ShopifyMarketsConverter extends CurrencyConverter {
    async loadExchangeRates() {
        // Use Shopify's native multi-currency
        if (window.Shopify && window.Shopify.currency) {
            this.rates = window.Shopify.currency.rates || {};
        } else {
            await super.loadExchangeRates();
        }
    }

    changeCurrency(newCurrency) {
        // Update Shopify's currency
        if (window.Shopify && window.Shopify.currency) {
            window.Shopify.currency.active = newCurrency;
        }

        super.changeCurrency(newCurrency);

        // Reload cart with new currency
        this.updateCartCurrency(newCurrency);
    }

    async updateCartCurrency(currency) {
        try {
            await fetch('/cart/update.js', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    attributes: {
                        currency: currency
                    }
                })
            });
        } catch (error) {
            console.error('Failed to update cart currency:', error);
        }
    }
}

Auto-Detection on First Visit

// Auto-detect currency on first visit
document.addEventListener('DOMContentLoaded', async () => {
    const converter = new CurrencyConverter({
        shopCurrency: 'USD'
    });

    // If no saved currency, auto-detect
    if (!localStorage.getItem('selectedCurrency')) {
        const country = await converter.detectUserCountry();
        if (country) {
            const currency = converter.getCurrencyByCountry(country);
            converter.changeCurrency(currency);
        }
    }
});

Features

  • Real-Time Conversion: Live exchange rates from API
  • Auto-Detection: Detect user's location and currency
  • Persistent Selection: Saves currency choice in localStorage
  • Dynamic Updates: Converts newly loaded prices automatically
  • Shopify Markets: Compatible with Shopify's native solution
  • Custom Formatting: Proper currency symbols and decimals
  • Event System: Triggers events for integration
  • Fallback Rates: Works even if API fails

Related Snippets