Shopify themes, liquid, logos, and UX
If your government requires you to show prices with taxes included, but you want your international customers to pay less than the VAT-included price, you will need to show two prices in your shop. One price will be with taxes included for your local customers, and one price will be without taxes for your international customers.
Tip: The solution presented here won't work in the following themes: Venture and Boundless.
Note: This solution will only work if your products have only one variant, and the price on the product page is not updated via JavaScript (usually found in a selectCallback function).
Shopify will automatically set your tax rates for you, but you might want to confirm that those rates are correct. To do so:
We're going to find the locations where the theme calls on product price. This will generally appear as:
{{ product.price | money }}
We will want to add in a variable here that will multiply the price by your tax rate:
{{ product.price | times:1.XX | money }}
Where XX = your tax rate. So if your tax rate is 10%, it should be times:1.1
. If it's 5%, it should be times:1.05
. In Canada, the federal tax rate is 5%, so we'll use times:1.05
.
This tutorial will use the Minimal theme as an example — every theme is built differently, so the areas where you have to modify your theme will differ if you're using another theme.
The Minimal theme changes this in the exact same spot.
product-grid-item.liquid
snippet to open it in the online code editor.{{ product.price | money }}
{{ product.price | times:1.05 | money }} Int price: {{ product.price | money }}So that it looks like this:
product.liquid
to open it in the online code editor.{{ product.price | money }}
{{ product.price | times:1.05 | money }}
<div class="purchase">
, add:<h3>Int price: {{ product.price | money }}</h3>before the closing
</div>
tag.Showing the international price for each line item in the table will most likely just make the page very cluttered and confusing. So we'll just add a note at the top of the page that lets international customers know that they will receive 5% off of the listed price. We will also add an international price at the end of the cart.
cart.liquid
to open it in the online code editor.<h3 style="text-align:center; color:red">All international orders receive 5% off!</h3>So that it appears like this:
We'll now change the line item prices in the table so that they are also marked up:
{{ item.line_price | money }}Changes to:
{{ item.line_price | times:1.05 | money }}
So that it appears as such:
{{ cart.total_price | money }}to
{{ cart.total_price | times:1.05 | money }} <br /> Int price: {{ cart.total_price | money }}
Your shop will now show both your prices including VAT as well as your international price without. Check out this test shop to see how it works.
TyW | Online Community Manager @ Shopify
- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit the Shopify Help Center or the Shopify Blog
Hi TyW, thanks for this information. On our store we want to display prices both with and without tax, but we have a few products where no tax is charged - these are in a collection and we use the Tax Override function. Is there a way to implement your solution but with a variable tax rate, rather than entering a fixed rate such as 1.2 as shown on your example? Thank you.
As per my point of view: the logic implemented by Shopify is not correct.
When prices are shown tax inclusive in shop pages, prices should show tax exclusive in checkout page if the country is tax exempt. otherwise the customer's country end up paying more.
Furthermore in checkout page taxes and tax rate should always show in any case.
That's how Woocommerce has implemented their logic.
tbo64 You are correct.
It should be the opposite. Not showing the excluding taxes price, and adding taxes at checkout.
Good evening Matthew, i was wondering if you manage to find out how to display prices with and without taxes, Thank you
Hi Marie
No, I never got it working in the end for it to show some items with tax and some without tax - all my products show the with tax price, even if they don't get charged tax once they get to the checkout.
Matt
Will this coding change the pricing in invoices i am sending out too?
My online store is synced with Vend and the prices it pulls in are tax inclusive. I think I need to use this code to show the without tax prices on the product/collection pages but ultimately to send correct VAT invoices to EU Business Customers
Hi TyW - great article, thanks, but do you know of a solution for this issue where products DO have multiple variants?
Hey there!
I am on that issue.
I have modified the code and it works on main product, but not with variants.
Have you found the way?
Kind regards
Are you able to only display the non-VAT price in the basket for international customers? So only add this code to the cart? I would prefer not to show the International price on the product page and collection listing.
i can code for multiple variants. ishopifyexpert@gmail.com 🙂
Thanks
did you manage to find a solution for variations ?
Hi TyW,
I've just installed a theme called Wokiee on my Shopify test site, I wonder if you can advise how I would make the changes on that. I had a look myself, but the lines must be some other place.
I was using a Magento1 site and thought I may be able to Shopify, because I cant afford Magento2. I have a B2B business.
Thank you.
Hi TyW,
How can I do this on the Venture theme?
Thanks in advance
I couldn't find product.price in Template/product.liquid.
I am using Fashe Theme.
Am I missing something? Below is the file product.liquid in the Template folder.
<!--Product Details Area Start-->
{% assign current_variant = product.selected_or_first_available_variant %}
{% assign enable_zoom = true %}
{% assign product_img_size = settings.img_size_product %}
{% include 'breadcrumb' %}
<meta itemprop="url" content="{{ shop.url }}{{ product.url }}">
<meta itemprop="image" content="{{ product.featured_image.src | img_url: 'grande' }}">
<!-- Product Detail -->
<div class="container bgwhite p-t-35 p-b-80">
<div class="flex-w flex-sb">
<div class="w-size13 p-t-30 respon5">
<div class="wrap-slick3 flex-sb flex-w">
<div class="wrap-slick3-dots"></div>
<div id="product-image-carousel" class="slick3">
{% for image in product.images %}
<div class="item-slick3" data-thumb="{{ image.src | img_url: '90x120' }}">
<div class="wrap-pic-w">
<img src="{{ image.src | img_url: product_img_size }}" alt="{{ image.alt | escape }}">
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="w-size14 p-t-30 respon5">
<h4 class="product-detail-name m-text16 p-b-13">
{{ product.title }}
</h4>
{% if product.selected_or_first_available_variant.price < product.selected_or_first_available_variant.compare_at_price %}
<span id="comparePrice" class="m-text17a p-r-10">
{{ product.selected_or_first_available_variant.compare_at_price | money }}
</span>
<span id="productPrice" class="m-text17b">
{{ product.selected_or_first_available_variant.price | money }}
</span>
{% else %}
<span id="productPrice" class="m-text17">
{{ product.selected_or_first_available_variant.price | money }}
</span>
{% endif %}
<p class="s-text8 p-t-10">
{{ product.content | split:"[video]" | first | strip_html | truncatewords:20 }}.
</p>
<!-- -->
<div class="p-t-33 p-b-60">
<form action="/cart/add" method="post" enctype="multipart/form-data" id="form_buy">
<div class="form-group p-b-10" {% if product.variants.size == 1 and product.variants.first.title contains 'Default' %}style="display:none"{% endif %}>
<select name="id" id="productSelect" class="form-control">
{% for variant in product.variants %}
{% if variant.available %}
<option {% if variant == product.selected_or_first_available_variant %} selected="selected" {% endif %} data-sku="{{ variant.sku }}" value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money_with_currency }}</option>
{% else %}
<option disabled="disabled">
{{ variant.title }} - {{ 'products.product.sold_out' | t }}
</option>
{% endif %}
{% endfor %}
</select>
{% if product.available and product.variants.size > 1 %}
{% for option in product.options %}
{% include 'swatch' with option %}
{% endfor %}
{% endif %}
</div>
<div class="form-group">
{% if settings.product_quantity_enable %}
<div class="flex-r-m flex-w p-t-12">
<div class="w-size160 flex-m flex-w">
{% if product.available %}
<div class="flex-w bo5 of-hidden m-r-22 m-t-10 m-b-10">
<button class="btn-num-product-down color1 flex-c-m size7 bg8 eff2">
<i class="fs-12 fa fa-minus" aria-hidden="true"></i>
</button>
<input class="size8 m-text18 t-center num-product" type="number" id="Quantity" name="num-product" value="1">
<button class="btn-num-product-up color1 flex-c-m size7 bg8 eff2">
<i class="fs-12 fa fa-plus" aria-hidden="true"></i>
</button>
</div>
{% endif %}
<div class="btn-addcart-product-detail size9 trans-0-4 m-t-10 m-b-10">
<!-- Button -->
<button type="submit" name="add" id="button-cart" {% unless current_variant.available %}disabled="disabled"{% endunless %} class="flex-c-m sizefull bg1 bo-rad-23 hov1 s-text1 trans-0-4" data-loading-text="{{ 'products.product.loading' | t }}">
{% unless current_variant.available %}
{{ 'products.product.sold_out' | t }}
{% else %}
{{ 'products.product.add_to_cart' | t }}
{% endunless %}
</button>
</div>
</div>
</div>
{% endif %}
{% if settings.product_quantity_message %}
<span id="variantQuantity" class="variant-quantity"></span>
{% endif %}
</div>
</form>
</div>
<div class="p-b-45">
{% if settings.product_vendor_enable %}
<span class="s-text8 m-r-35">{{ 'products.product.brand' | t }}: {{ product.vendor }}</span>
{% endif %}
<span class="s-text8">
{{ 'products.product.tags' | t }}:
{% for tag in product.tags %}
{% unless tag contains '_' %}
<a href="/collections/all/{{ tag }}">{{ tag }}</a>
{% unless forloop.last %}, {% endunless %}
{% endunless %}
{% endfor %}
</span>
</div>
<!-- -->
<div class="wrap-dropdown-content bo6 p-t-15 p-b-14 active-dropdown-content">
<h5 class="js-toggle-dropdown-content flex-sb-m cs-pointer m-text19 color0-hov trans-0-4">
{{ 'products.product.description' | t }}
<i class="down-mark fs-12 color1 fa fa-minus dis-none" aria-hidden="true"></i>
<i class="up-mark fs-12 color1 fa fa-plus" aria-hidden="true"></i>
</h5>
<div class="dropdown-content dis-none p-t-15 p-b-23">
<p class="s-text8">
{{ product.description | split:"[video]" | first }}
</p>
</div>
</div>
{% if settings.show_third_tab %}
<div class="wrap-dropdown-content bo7 p-t-15 p-b-14">
<h5 class="js-toggle-dropdown-content flex-sb-m cs-pointer m-text19 color0-hov trans-0-4">
{{ settings.third_tab_title }}
<i class="down-mark fs-12 color1 fa fa-minus dis-none" aria-hidden="true"></i>
<i class="up-mark fs-12 color1 fa fa-plus" aria-hidden="true"></i>
</h5>
<div class="dropdown-content dis-none p-t-15 p-b-23">
<p class="s-text8">
{{ settings.third_tab_text }}
</p>
</div>
</div>
{% endif %}
{% if settings.product_reviews_enable %}
<div class="wrap-dropdown-content bo7 p-t-15 p-b-14">
<h5 class="js-toggle-dropdown-content flex-sb-m cs-pointer m-text19 color0-hov trans-0-4">
{{ 'products.product.reviews' | t }}
<i class="down-mark fs-12 color1 fa fa-minus dis-none" aria-hidden="true"></i>
<i class="up-mark fs-12 color1 fa fa-plus" aria-hidden="true"></i>
</h5>
<div class="dropdown-content dis-none p-t-15 p-b-23">
<p class="s-text8">
{% include 'product-review' %}
</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% include 'related-products' %}
{{ 'option_selection.js' | shopify_asset_url | script_tag }}
<script>
// Pre-loading product images, to avoid a lag when a thumbnail is clicked, or
// when a variant is selected that has a variant image.
Shopify.Image.preload({{ product.images | json }}, '{{ product_img_size }}');
var selectCallback = function( variant, selector ) {
var $addToCart = $('#button-cart'),
$productPrice = $('#productPrice'),
$comparePrice = $('#comparePrice'),
$variantQuantity = $('#variantQuantity'),
$quantityElements = $('.quantity-selector, label + .js-qty'),
$addToCartText = $('#addToCartText'),
$featuredImage = $('#productPhotoImg');
if (variant) {
// Update variant image, if one is set
// Call timber.switchImage function in shop.js
if (variant.featured_image) {
$('#product-image-carousel').slick( 'slickGoTo', variant.featured_image.position - 1 );
}
// Select a valid variant if available
if (variant.available) {
// We have a valid product variant, so enable the submit button
$addToCart.removeClass('disabled').prop('disabled', false);
$addToCartText.html({{ 'products.product.add_to_cart' | t | json }});
$quantityElements.show();
// Show how many items are left, if below 10
{% if settings.product_quantity_message %}
if (variant.inventory_management) {
if (variant.inventory_quantity < 10 && variant.inventory_quantity > 0) {
$variantQuantity.html({{ 'products.product.only_left' | t: count: '1' | json }}.replace('1', variant.inventory_quantity)).addClass('is-visible');
} else if (variant.inventory_quantity <= 0 && variant.incoming) {
$variantQuantity.html({{ 'products.product.will_not_ship_until' | t: date: '[date]' | json }}.replace('[date]', variant.next_incoming_date)).addClass('is-visible');
} else {
$variantQuantity.removeClass('is-visible');
}
}
else {
$variantQuantity.removeClass('is-visible');
}
{% endif %}
} else {
// Variant is sold out, disable the submit button
$addToCart.addClass('disabled').prop('disabled', true);
$addToCartText.html({{ 'products.product.sold_out' | t | json }});
$variantQuantity.removeClass('is-visible');
if (variant.incoming) {
$variantQuantity.html({{ 'products.product.will_be_in_stock_after' | t: date: '[date]' | json }}.replace('[date]', variant.next_incoming_date)).addClass('is-visible');
}
else {
$variantQuantity.addClass('hide');
}
$quantityElements.hide();
}
// Regardless of stock, update the product price
Shopify.money_format = '{{shop.money_format }}';
//var customPrice = timber.formatMoney( Shopify.formatMoney(variant.price,Shopify.money_format) );
var a11yPrice = Shopify.formatMoney(variant.price, Shopify.money_format);
// var customPriceFormat = ' <span aria-hidden="true">' + customPrice + '</span>';
var customPriceFormat = ' <span class="visually-hidden">' + a11yPrice + '</span>';
{% if settings.product_show_compare_at_price %}
if (variant.compare_at_price > variant.price) {
//var comparePrice = timber.formatMoney(Shopify.formatMoney(variant.compare_at_price, Shopify.money_format));
var a11yComparePrice = Shopify.formatMoney(variant.compare_at_price, Shopify.money_format);
//customPriceFormat = ' <span aria-hidden="true">' + customPrice + '</span>';
//customPriceFormat += ' <span aria-hidden="true"><s>' + comparePrice + '</s></span>';
//customPriceFormat += ' <span class="visually-hidden old-price">' + a11yComparePrice + '</span>';
/*customPriceFormat += ' <span class="visually-hidden"><span class="visually-hidden">{{ "products.general.sale_price" | t }}</span> ' + a11yPrice + '</span>';*/
}
{% endif %}
$productPrice.html(customPriceFormat);
// Also update and show the product's compare price if necessary
if ( variant.compare_at_price > variant.price ) {
// var priceSaving = timber.formatSaleTag( Shopify.formatMoney(variant.compare_at_price - variant.price, '{{ shop.money_format }}') );
{% comment %}
priceSaving += ' (' + ( (variant.compare_at_price - variant.price)*100/(variant.compare_at_price) ).toFixed(0) + '%)';
{% endcomment %}
//$comparePrice.html({{ 'products.general.save_html' | t: saved_amount: '[$]' | json }}.replace('[$]', priceSaving)).show();
} else {
$comparePrice.hide();
}
{% if settings.show_multiple_currencies %}
var defaultCurrency = '{{ settings.default_currency | default: shop.currency }}';
var shopCurrency = '{{ shop.currency }}';
var cookieCurrency = Currency.cookie.read();
if (cookieCurrency == null) {
Currency.convertAll(shopCurrency, defaultCurrency);
} else {
Currency.convertAll(shopCurrency, cookieCurrency);
}
{% endif %}
} else {
// The variant doesn't exist, disable submit button.
// This may be an error or notice that a specific variant is not available.
$addToCart.addClass('disabled').prop('disabled', true);
$addToCartText.html({{ 'products.product.unavailable' | t | json }});
$variantQuantity.removeClass('is-visible');
$quantityElements.hide();
}
if (variant && variant.featured_image) {
var originalImage = $(".thumbnails img");
var newImage = variant.featured_image;
var element = originalImage[0];
Shopify.Image.switchImage(newImage, element, function (newImageSizedSrc, newImage, element) {
$(element).parents('a').attr('href', newImageSizedSrc);
$(element).attr('src', newImageSizedSrc);
$('.thumbnails .zoomWindowContainer div').css('background-image','url('+newImageSizedSrc+')');
});
}
// BEGIN SWATCHES
if (variant) {
var form = $('#' + selector.domIdPrefix).closest('form');
for (var i=0,length=variant.options.length; i<length; i++) {
var radioButton = form.find('.swatch[data-option-index="' + i + '"] :radio[value="' + variant.options[i] +'"]');
if (radioButton.length) {
radioButton.get(0).checked = true;
}
}
}
// END SWATCHES
};
jQuery(function($) {
var product = {{ product | json }};
{% if settings.product_quantity_message %}
{% for variant in product.variants %}
product.variants[{{ forloop.index0}}].incoming = {{ variant.incoming | default: false }};
product.variants[{{ forloop.index0}}].next_incoming_date = {{ variant.next_incoming_date | date: format: 'month_day_year' | json }};
{% endfor %}
{% endif %}
new Shopify.OptionSelectors('productSelect', {
product: product,
onVariantSelected: selectCallback,
enableHistoryState: true
});
// Add label if only one product option and it isn't 'Title'. Could be 'Size'.
{% if product.options.size == 1 and product.options.first != 'Title' %}
$('.selector-wrapper:eq(0)').prepend('<label>{{ product.options.first | escape }}</label>');
{% endif %}
/* Hide selectors if we only have 1 variant and its title contains 'Default'. */
{% if product.variants.size == 1 and product.variants.first.title contains 'Default' %}
$('.selector-wrapper').hide();
{% else %}
$('#variantBreak').removeClass('hr--clear');
{% endif %}
//$('.selector-wrapper').hide();
});
</script>
Hi,
Thanks for the guide, this worked fine.
A followup question to this would be how do we then modify the pricing on Campaign email templates for Marketing purposes?
Similar to the example, currently our shop configuration is to show prices excluding VAT. We then modified the templates to show price plus the VAT modification required, ie:
{{ product.price | times:1.XX | money }}
Now with our email campaigns, it shows the price without VAT, when you click through to the product it shows the price with VAT which is slightly confusing.
Email campaigns don't seem to be part of themes so how do i modify this?
Thanks,
Hello,
we have set up our store this way and are getting rounding errors like this:
11.76*1.19=13.9944
Customer would expect a total of 13.99*10=139.90 instead. How can we prevent this rounding problem?
Thanks!
Hello,
how do we prevent rounding issues setting things up like this?
13.99*10 should be 139.90 instead of 139.94
Super silly Shopify as one of the world leaders has not figured it out to show both prices.
Even what they describe here I can't get to work: https://help.shopify.com/en/manual/taxes/location#include-or-exclude-tax-based-on-your-customers-cou...
It is almost time to go back to Wordpress/Woocommerce. If you are a EU seller and do both B2C and B2B Shopify is a really complicated option.
That is ridicolous that including or excluding tax impacts margin! Any other system adds VAT to price. It makes no sense that you give away 20% margin - many cases you dont have such margin even - so you make loss?
Problem here is that Shopify seems to be a platform for small companies selling only B2C and in US
Lousy that such a workaround is needed for such a basic funciton. Especially for a 'world leading' and publicly traded company. Are you guys for real? How long has this thread been open? Get your dev team working!
Shopify sees the taxes management with a very US vision, not a European one.
The problem with he current system is that you will either make the EU customers confused or the customers outside EU.
1- You choose to show prices excluding taxes and charge the VAT at the checkout. It's extremely frustrating for EU customers who will think the EX-VAT price is the definitive one until they reach the check-out. It will generate an extreme frustration at this moment (imagine you've created an account and filled all fields etc... To discover that you have to pay 20% more or so).
2- You choose to show prices including taxes and this time, you will loose all non-EU customers who will think the definitive price is the Including one. It's a big source of abandoned baskets actually.
Yes, it's a very easy fix to implement, which is for now, apparently not planed. It does impact all merchants selling worldwide. In a very global market, it's a non sense, but hopefully they will fix this soon.
As far as I can tell the article author, TyW, has not responded to a single question. That is very unprofessional.
User | RANK |
---|---|
61 | |
53 | |
47 | |
39 | |
39 |
Photo by Marco Verch Sales channels on Shopify are various platforms where you can sell...
By Ollie May 25, 2023Summary of EventsBeginning in January of 2023, some merchants reported seeing a large amo...
By Trevor May 15, 2023With 2-Factor Authentication being required to use Shopify Payments, we’re here to help yo...
By Imogen Apr 26, 2023