Setting Up EU Taxes in Liferay: A Practical Guide

Configuring taxes for an online business can be a complex task, especially when operating in the European Union. With varying rules for domestic sales, cross-border transactions, and exports, businesses must navigate a web of regulations to ensure compliance. This post provides a practical guide to understanding and managing EU tax requirements in Liferay. We'll discuss different scenarios-selling within your country, across EU borders, and outside the EU-and highlight potential challenges, including Liferay's default limitations and how to address them.

EU taxes in Liferay - main photo

Introduction

Let’s start by outlining the topics we’ll cover in this blog. As the title and description suggest, we’re going to talk about taxes-specifically, taxes in Liferay. This article is divided into three parts:

  • First, we’ll explore the tax settings available in Liferay.
  • Next, we’ll cover the basics of EU taxation and identify the features missing in Liferay’s out-of-the-box tax system.
  • Finally, we’ll discuss potential solutions to the issues defined in the second part.

The first two sections should be easy to understand, even for those who are not Liferay experts, developers, or tax specialists. The third section, however, will go into some more technical solutions, which may be trickier to understand for readers without prior Liferay Commerce experience. That said, we’ll keep things as accessible as possible without going into excessive detail.

I hope you enjoy this article! And as always, if you need assistance with the topics covered here, don’t hesitate to reach out-our experts are happy to help.

Disclaimer

Before diving into the details, it’s important to note that while we are experts in Liferay, we are not tax consultants. Tax regulations are complex and vary depending on your business model, location, and customer country. Always consult a qualified legal or tax consultant to ensure your tax setup complies with local and international laws. This article is only meant to help you with starting your tax settings. Please keep in mind that the rules described here are valid at the time of writing but may have changed, which is another factor you need to consider.

For the purposes of this article, we assume the described business is located within the European Union. However, the tips shared here may also be helpful for businesses in other regions, even if the specific rules differ in your country outside the EU.

Taxes in Liferay Commerce

The tax settings in Liferay Commerce are quite straightforward:

  • Tax settings are tied to channel
  • By default there are three tax engines available: By Address, Remote, Fixed Tax Rate
  • You can define new tax engines if you wish

Tax calculations Tab in channel

The three tax engines serve different purposes:

  • Fixed Tax Rate: simply apply same tax rate for each customer. No matter which country the billing or shipping address is
  • By Address: possibility to define custom tax rate for each country, region, or even ZIP code.
  • Remote: tax rate is calculated remotely.

For "Fixed Tax Rate" and "By Address" tax engines you can also define different rates for different products. In EU this is common to have different rates for different product groups. For example food can have different tax rate than cars. This case is fully supported - you just have to define tax categories: Tax categories panel And then make sure their are assigned to products correctly: Tax category assignment to product

It is also possible to define tax category for shipping: Shipping tax category panel in channel

The settings are really straightforward and should not be an issue to configure.

EU Taxation basics

Let's first describe three cases you can encounter when dealing with taxes and what can be done "out of the box" and what requires extra handling:

  • Selling to same country
  • Selling to other country within EU
  • Selling to country outside EU

Please note we are using simplified rules which do not apply to all types of business. Going through all possible scenarios is out of the scope of this article.

However, the principles outlined here are broadly applicable and can be adapted to other types of businesses once you verify the correct rules for your specific situation with a tax advisor. You can also take a look at the European Commission's article "Where to Tax" for more information about taxes and finding out which can apply to your needs.

Selling Within the Same Country

This is the simplest case. When you sell products or services to customers in the same country as your business:

  • B2C (Business to Consumer): You apply the fixed VAT rate of your country, as defined by local tax laws.
  • B2B (Business to Business): You still apply the fixed VAT rate unless specific rules (like reverse charge) apply.

For example, if your business is based in Poland, and your customer is also in Poland, you'll charge the standard Polish VAT rate for a given product type (in most cases 23% with some lower percentage exceptions).

How to configure in Liferay:

  • Use Liferay’s built-in "By Address" tax engine. Set the desired tax rate for your country under "Commerce Settings" in the control panel. "By address" tax engine countries settings

You can also create different product types, each with its own tax percentage, such as 8% or 5%.

For simplicity, the rest of this article will use the "main" 23% VAT rate as an example. However, keep in mind that you can apply different tax rates for different countries and product types. This is not an issue and can be managed by adding the necessary "By Address" records as required.

Selling to Other EU Countries

When selling to other EU countries, the tax rules depend on whether the transaction is B2C or B2B.

B2B (Business to Business)

Let’s start with B2B, which is relatively straightforward:

  • VAT is generally not charged. Instead, the "reverse charge" mechanism applies, meaning the customer is responsible for accounting for VAT in their country.
  • To apply the "reverse charge" mechanism, you must validate the customer’s VAT number through the VIES system.

But how do you validate the VAT ID? This is the first challenge you might encounter, and we’ll discuss potential solutions shortly.

B2C (Business to Consumer)

  • If your total cross-border sales to consumers in other EU countries exceed the threshold (currently €10,000 per year for the entire EU), you must charge VAT based on the customer’s country. The legal and administrative aspects, such as tax registration, have been greatly simplified though by the introduction of the OSS (One-Stop Shop) scheme a few years ago.
  • If your sales are below the threshold, you can apply your local VAT rate without using the OSS scheme, unless you voluntarily opt into it for simplification.

Important Limitation in Liferay

As you can see, there can be two, or even three different tax rates when selling to another country.
Let’s take Poland as an example. When selling to:

  • A German company with a valid EU VAT ID: The reverse charge mechanism applies, which means 0% VAT from the order perspective.
  • A German consumer (not a business) with your sales below the threshold: You can apply your local Polish VAT rate of 23%.
  • A German consumer (not a business) with your sales above the threshold: You must apply the local German VAT rate of 19%.

Out of the box, Liferay does not allow setting different tax rates for the same country. For example:

  • If you are selling from Poland to Germany, you cannot configure Liferay to apply 0% VAT for B2B sales and simultaneously apply the German VAT rate for B2C sales.

Additionally, Liferay does not track whether your sales exceed the EU distance selling threshold. This data must be stored and managed separately.

We’ll explore potential solutions for both of these challenges soon.

Selling to Non-EU Countries

For sales to customers outside the EU:

  • B2C: VAT is typically not charged, as these are considered exports. However, local taxes or duties might apply in the customer’s country.
  • B2B: Similarly, VAT is not applied, but you need to ensure proper documentation for customs and tax authorities.

Required Documentation

To apply the 0% VAT rate, you often need proof of shipment, such as a bill of lading, shipping confirmation, or delivery receipt. Without this documentation, your local tax office may deny the 0% rate and require you to pay VAT.

How to configure in Liferay:

  • To apply 0% of VAT: simply do not configure country in "By Address" tax engine

Filling Liferay gaps in Tax calculations

Now let’s address the issues we identified earlier and explore how they can be resolved in Liferay. Before we dive in, a quick heads-up: this section will be more technical. While we won’t go as deep as providing code examples, it will include references that assume some familiarity with Liferay.

If you’re facing these tax-related challenges but can’t handle the implementation yourself, we’re here to help. With years of experience in Liferay Commerce, we understand the struggles. We’ve faced these challenges, learned through real-world implementations, and are now ready to deliver professional solutions tailored to your needs.

To sum up the key issues we need to solve:

  • Validate EU VAT IDs.
  • Apply different tax rates for B2B and B2C transactions within the same country.
  • (Optionally) Handle sales amount thresholds.

VAT ID Validation

VAT ID validation is essential for accurate tax calculations. Without proper validation, applying the correct tax rates, as described earlier, becomes impossible. Fortunately, the European Commission provides a SOAP API that allows VAT ID validation.

The exact implementation process depends on your customer creation flow. Here are some factors to consider:

  • Can B2B customers register on their own, or are they managed by your employees?
  • At what point is the VAT ID provided? During customer account creation, at checkout, or elsewhere? Typically, the VAT ID is stored at the account level, but custom workflows, such as a checkout step or modifications to existing processes, could be used.
  • What should happen if a VAT ID that was validated in the past is no longer valid?

Possible solutions

  • Create a custom boolean field at the Account Entry level, such as vatIdValid, and make it hidden.
  • Use a job scheduler to validate all accounts nightly. If a VAT ID for a given account is no longer valid, set vatIdValid to false.
  • Define actions to handle invalid VAT IDs based on business needs. For example notify the account manager via email or a system notification or block users from proceeding with checkout until the issue is resolved.

However, for businesses with a large number of accounts (thousands or more), checking each account daily may be resource-intensive, especially if many accounts are inactive or rarely used.

To reduce the workload, you could validate the VAT ID only when a user from the account logs in on a given day. This approach minimizes unnecessary validations for inactive accounts.

There are many ways to address this issue, and the best solution depends on your specific business needs. Whether it’s integrating validation into your customer creation workflow, automating notifications, or optimizing validation frequency, the key is to tailor the solution to your operations.

Different tax rates for B2B and B2C

One of the challenges in Liferay is how to apply different tax rates for B2B and B2C transactions within the same country in the EU. While it might be tempting to define separate channels for each case, this approach is often overkill. It also doesn’t fully address issues like B2C scenarios where the tax rate depends on exceeding the sales threshold.

A more flexible and efficient solution is to use a custom tax engine to handle the logic for you. There are two potential approaches:

  • Override "By Address" tax engine behavior and only change parts you need
  • Completely new tax engine (potentially using "By Address" parts internally)

Both are relatively straighforward. Overriding the "By Address" Tax Engine

  • Define a custom OSGi component.
  • In the component properties, set commerce.tax.engine.key=by-address and assign it a higher rank than the original implementation.
  • Implement the required methods in your custom class.
  • To reuse methods from the original "By Address" implementation, inject it into your class using the @Reference annotation.

Creating a New Tax Engine

  • Define a custom OSGi component with your own unique tax engine key.
  • Implement the required methods to suit your specific business logic.
  • Optionally, reuse parts of the "By Address" implementation for common functionality. You can access it using @Reference

Choosing the Right Approach

The choice between overriding and creating a new engine depends on your requirements:

  • Overriding is a good option if you only need minor adjustments while keeping most of the default behavior intact.
  • Creating a new engine offers more flexibility if your requirements differ significantly from the default implementation.

Handling Thresholds

This is only required if you want your portal to be ready for both: shops below sales thresholds and above. In case you always will be above threshold (or below) you could theoritically just "hard code" it in your tax engine. While hard coding is never the best solution it can work just fine if time is an issue.

To track whether your sales exceed the distance selling threshold, you need to store this information in Liferay. You can manage this in Instance Settings or Channel Settings, depending on your needs:

  1. Use Instance Settings:

    • If all channels share the same sales threshold, store the threshold data in the Instance settings.
    • This approach is suitable when all sales are consolidated under one legal entity.
  2. Use Channel Settings:

    • If each channel has its own threshold (e.g., for different suppliers or separate legal entities within your business), store the data in the Channel settings.
    • This approach allows greater flexibility and independence for managing sales thresholds per channel.

How to Store Threshold Data

There are two approaches to storing threshold data:

  1. Store Sales Amount:

    • Keep a running total of sales for the current year.
    • This provides precise data but requires all sales to be tracked through the portal. If there are sales outside Liferay, these must be included manually.
  2. True/False Threshold Flag:

    • Use a simple boolean field (e.g., "ThresholdExceeded") to indicate whether the threshold has been reached.
    • This is simpler and avoids the need for calculations but must be updated manually whenever threshold has been reached.
    • In many cases it is the best solution: many business do not qualify for simplified rules anyway

Steps to Implement:

  • Create the custom field in either Instance or Channel settings, depending on your requirements.
  • Update the field dynamically for portal-based sales or manually for external sales.
  • Develop logic in your custom tax engine to reference the stored threshold data and apply the appropriate VAT rate.

Storing a List of EU Countries

If you decide to calculate EU sales thresholds, it is important to know which countries are part of the EU and which are not.

By default, Liferay does not have this information. To ensure accurate calculations, you need to store this data somewhere. Options include (but are not limited to):

  • Adding a custom field at the country level to indicate EU membership.
  • Creating a system setting to include the A2 codes of EU countries. This is particularly helpful if you have multiple instances, as it avoids the need to configure each instance separately.
  • Including the data as a portal property, making it accessible across the portal.

Final Thoughts

While Liferay’s robust tools make it easier to configure tax rules, its default configuration is not sufficient for handling complex EU tax scenarios, especially for cross-border sales. Implementing a custom tax engine with proper threshold management-using either Instance or Channel settings-ensures compliance with VAT regulations and provides the flexibility needed for multi-channel operations.

At InnRay, we understand the challenges businesses face in setting up compliant and efficient tax configurations. Having already implemented similar solutions for various customers, we’re well-equipped to help you tailor Liferay to your specific needs. If you require support or guidance for your tax setup, feel free to reach out-we’re here to help.