Skip to content
Logo Theodo

Learn how to implement VAT for your e-commerce website with Sylius in 6 minutes

Fabien Charlier8 min read

Learn how to implement VAT for your e-commerce website with Sylius in 6 minutes

If you are using Sylius for your e-commerce website, then you will inevitably come across the subject of Value Added Tax (VAT). As a software engineer at Theodo, I have spent countless hours customizing every one of its aspects, for it to behave in the exact way I wanted it to.

So you don’t need to! Here are the main steps you can follow to avoid wasting any time!

Table of contents:

Setting up your first VAT engine 🔧

Sylius VAT handling revolves around three distinct entities, allowing it to choose which rate it has to apply. One of these entities is linked to the article - the tax category, another is linked to the customer - the zone, and the last one connects the latter two - the tax rate.

A schema representing the different entities involved in Sylius VAT calculation

Tax Category 🏷

The tax category is just a category for your product, which defines which rates should be applied. It allows you to sell products which aren’t taxed for the same rate - for example, clothes and food. You’ll need to add all of your tax categories to your fixtures (the base data you want to have every time you restart your shop - see Sylius documentation):

    fixtures:
        tax_category:
            options:
                custom:
                    food:
                        name: 'Food'
                        code: 'food'
                    clothes:
                        name: 'Clothes'
                        code: 'clothes'

Zone 🌍

The zone is a Sylius concept which is way broader than the VAT: it is either a group of countries, a group of provinces, or a group of other zones. Here, what we will be looking at is the zone of the customer. A zone has an interesting characteristic, called scope, which defines what it can be used for: ‘shipping’, ‘tax’, or ‘all’. A zone with a shipping scope is only used to calculate shipping methods and fees, whereas a zone with tax scope is only used to calculate VAT. If the scope is all, the zone will be used for both. You’ll need to define the different zones that you need to your fixtures:

    fixtures:
        geographical:
            options:
                countries:
                    - 'FR'
                    - 'DE'
                    - 'US'
                    - 'GB'
                zones:
                    EUROPE:
                        name: 'Europe'
                        scope: 'tax'
                        countries:
                            - 'FR'
                            - 'DE'
                    AMERICA:
                        name: 'America'
                        scope: 'tax'
                        countries:
                            - 'US'

Tax Rate 💸

Finally, a tax rate is an entity that defines all the necessary information for Sylius to calculate the amount of taxes your customer should pay. This must at least contain:

The last field, isIncludedInPrice, might seem enigmatic at first glance, however it is quite simple. It defines how the VAT should be calculated. For example, for a T-shirt costing 12€, and a tax rate of 20%:

You’ll need to define a tax rate for every couple of zone and tax category:

    clothes-europe:
        code: 'clothes-europe'
        name: 'Tax rate for clothes in Europe'
        category: 'clothes'
        zone: 'EUROPE'
        amount: 0.2
        calculator: 'default'
        included_in_price: true
    clothes-america:
        code: 'clothes-america'
        name: 'Tax rate for clothes in America'
        category: 'clothes'
        zone: 'AMERICA'
        amount: 0.15
        calculator: 'default'
        included_in_price: true
    food-europe:
        code: 'food-europe'
        name: 'Tax rate for food in Europe'
        category: 'food'
        zone: 'EUROPE'
        amount: 0.2
        calculator: 'default'
        included_in_price: true

If you only need to setup a basic VAT engine, without any added complexity, then you’re good to go! Jump to your code editor and try it out! Sylius will handle all the calculations on its own, and add the correct adjustments (modifications of price) to your articles automatically.

How does Sylius VAT engine works? 🧠

However, if you need a custom behavior of your engine (for example: billing a commission and adding VAT to it), or if you want a deeper understanding of this system, I’ll dive further into the engine now.

The Order Processor ✨

The VAT engine is included in the OrderProcessor: a list of operations that are executed every time a cart is modified (for example, it is triggered by adding an item to cart, changing the shipping address, etc…). That means that the VAT will be erased and recalculated every time your customer modifies its cart. This includes recalculating the shipping fees, the VAT, ensuring that every item is sold at the correct price, etc…

When the OrderProcessor asks for a VAT calculation, it calls a service called OrderTaxesProcessor, whose only goal is to call some Applicators. Applicators are where the magic happens, and are responsible for finding the correct tax amount, and adding this amount to the order. Each object that can generate VAT has a dedicated Applicator: there is one for the OrderItem, one for the shipping fees, etc…

Note that, prior to asking for a VAT calculation, the OrderProcessor calls another service which removes the taxes from the order. That allows us to calculate the VAT again, from an empty and controlled state.

A schema of the different steps of the Order Processor used by Sylius

Schema of the different steps of the Order Processor from Sylius documentation

The Applicators 👨‍💻

An applicator will do the following steps:

The Calculators 🧮

The Sylius VAT calculators are some pieces of code that handle the calculation of the correct tax amounts. They are given a price and a tax rate, and they return the correct amount of vat. Sylius, by default, provides one that calculates the VAT the way you expect it (a basic division), but if you need to implement some custom logic here (for example, no VAT at all for product that cost less than 5€), you can do so easily.

The only things to do are:

    // NoTaxesIfPriceLowerThanFiveCalculator.php
    class NoTaxesIfPriceLowerThanFiveCalculator implements CalculatorInterface
    {
        public function calculate(float $base, TaxRateInterface $rate): float
        {
            if ($base <= 5) {
                return 0;
            }

            if ($rate->isIncludedInPrice()) {
                return round($base - $base / (1 + $rate->getAmount()));
            }

            return round($base * $rate->getAmount());
        }
    }
    // services.yaml
    App\Taxation\Calculator\NoTaxesIfPriceLowerThanFiveCalculator:
        tags:
            - { name: 'sylius.tax_calculator', calculator: 'no_taxes_if_price_lower_than_five' }

My tips and tricks 🥇

Now you know almost everything that you need to customize and code tax calculations with Sylius with ease. However, there is still some details and tricks that I discovered which can be useful to anyone working with this system.

Changing the default address 🏠

By default, to calculate the zone which corresponds to the current cart, Sylius uses the billing address. However, if you want it to use the shipping address instead, you don’t have to add any code or to overwrite anything. There is an optional parameter that you can add to your yaml configuration, and which does exactly that:

    sylius_core:
        shipping_address_based_taxation: true

Multiple zones for one country 🌐

If a country is attached to multiple zones having ‘tax’ as their scope, then issues may arise. Indeed, Sylius expects a country to have a single zone available for tax, and, if it finds more that one, it will randomly return one of them. This will leave you with unexpected behaviors, which can cause severe bugs. Instead, you should split your countries into multiple zones, until no country is present multiple times.

Example: I deliver food and clothes in FR, IE (Ireland) and UK. Food has a 10% rate in every country, but clothes are only taxed in FR with 15% rate. I would like to create a zone containing every country (EUROPE), and a zone containing only FR (FRANCE), so that I can create two tax rates:

However, this won’t work, as France is included in two different zones. Instead, I have to declare a zone containing IE and UK (GREAT-BRITAIN) and a zone containing FR (FRANCE), and then create 3 tax rates:

Conclusion: customize your website 🔥

You can now easily twist the Sylius VAT engine to your needs, and explore its features in detail. And the best part is that you can replace any part of this engine! Thanks to the Sylius architecture using numerous specialized services, you can easily override and replace any code that doesn’t exactly meet your needs, allowing you to create the e-commerce website that exactly fits your business.

Liked this article?