The default VAT calculator shipped with Litium handles the normal B2C (business to consumer) scenario according to Swedish tax regulations. In the case of B2B (Business to Business) with sales outside Sweden to another EU country, the VAT should not be charged (zero VAT). This article explains how to change the VAT calculator to handle this scenario.
The first task is to identify whether the end customer qualifies for zero VAT. For this example, we will use the billing address in the order to determine this. If the billing address is from another EU country, the VAT should be zero, else it should be as default normal VAT.
Important: Swedish tax regulations specify additional declarations to be printed on invoices with more specific requirements regarding the customer, the customer's VAT registration, etc., which are not discussed here. Please read more on the Skatteverket site (in Swedish).
Determining whether VAT should be zero or not
According to the basic rule, if the billing address country is within EU (and outside Sweden) VAT should be zero (only for B2B transactions). We assume that the site is B2B, so we do not additionaly check whether the transaction is B2B or not.
This can be determined by a simple method:
/// <summary>
/// returns true if Billing address belongs to European Union except sweden.
/// </summary>
/// <param name="addressCarrier">The address carrier.</param>
/// <returns>
/// <c>true</c> if [is EU billing address] [the specified address carrier]; otherwise, <c>false</c>.
/// </returns>
public bool ZeroVAT(AddressCarrier addressCarrier)
{
if (addressCarrier == null)
return false;
var countryCode = addressCarrier.Country.ToUpper();
switch (countryCode)
{
case "BE": //Belgium
case "BG": //Bulgaria
case "CZ": //Czech Republic
case "DK": //Denmark
case "DE": //Germany
case "EE": //Estonia
case "IE": //Ireland
case "EL": //Greece
case "ES": //Spain
case "FR": //France
case "HR": //Croatia
case "IT": //Italy
case "CY": //Cyprus
case "LV": //Latvia
case "LT": //Lithuania
case "LU": //Luxembourg
case "HU": //Hungary
case "MT": //Malta
case "NL": //Netherlands
case "AT": //Austria
case "PL": //Poland
case "PT": //Portugal
case "RO": //Romania
case "SI": //Slovenia
case "SK": //Slovakia
case "FI": //Finland
//case "SE": //Sweden, sweden is removed since if from Sweden, VAT should be charged.
case "UK": //United Kingdom
return true; //zero VAT for all EU countries...
default:
return false;
}
}
Since this is determined when the billing address is set or changed, we need to make a note of whether to include VAT or not at that time. So we override the AddPaymentInfo method in the ICheckoutFlow concrete implementation, and mark out the order rows as "NoVAT".
The complete code for the checkout flow change is as follows:
using Litium.Common.InversionOfControl;
using Litium.Foundation.Modules.ECommerce.Carriers;
using Litium.Foundation.Modules.ECommerce.Plugins.CustomerInfo;
using Litium.Foundation.Modules.ECommerce.Plugins.Deliveries;
using Litium.Foundation.Modules.ECommerce.Plugins.Orders;
using Litium.Foundation.Modules.ECommerce.Plugins.Payments;
using Litium.Foundation.Security;
namespace Litium.Studio.AddOns.Samples.Checkout
{
/// <summary>
/// Override the default checkout flow plugin implementation.
/// </summary>
[Plugin("KCSamples")]
public class SampleCheckoutFlow : Litium.Foundation.Modules.ECommerce.Plugins.Checkout.CheckoutFlow
{
/// <summary>
/// String used to mark order rows.
/// </summary>
public const string NoVAT = "NoVAT";
/// <summary>
/// Initializes a new instance of the <see cref="CheckoutFlowPlugin" /> class.
/// </summary>
public SampleCheckoutFlow(IDeliveryFactory deliveryFactory, ICustomerInfoFactory customerInfoFactory, IPaymentInfoFactory paymentInfoFactory, IOrderRowFactory orderRowFactory, IOrderFactory orderFactory)
: base(deliveryFactory, customerInfoFactory, paymentInfoFactory, orderRowFactory, orderFactory)
{
}
public override PaymentInfoCarrier AddPaymentInfo(OrderCarrier orderCarrier, string paymentProviderName, string paymentMethod, AddressCarrier billingAddress, SecurityToken token)
{
var result = base.AddPaymentInfo(orderCarrier, paymentProviderName, paymentMethod, billingAddress, token);
//if the billing address is in another EU country, and
//if the site is a B2B site, mark the order rows as NoVAT.
if (result != null && ZeroVAT(result.BillingAddress))
{
orderCarrier.OrderRows.ForEach(x => x.Comments = NoVAT);
}
else
{
orderCarrier.OrderRows.ForEach(x => x.Comments = string.Empty);
}
return result;
}
/// <summary>
/// returns true if Billing address belongs to European Union except sweden.
/// </summary>
/// <param name="addressCarrier">The address carrier.</param>
/// <returns>
/// <c>true</c> if [is EU billing address] [the specified address carrier]; otherwise, <c>false</c>.
/// </returns>
public bool ZeroVAT(AddressCarrier addressCarrier)
{
if (addressCarrier == null)
return false;
var countryCode = addressCarrier.Country.ToUpper();
switch (countryCode)
{
case "BE": //Belgium
case "BG": //Bulgaria
case "CZ": //Czech Republic
case "DK": //Denmark
case "DE": //Germany
case "EE": //Estonia
case "IE": //Ireland
case "EL": //Greece
case "ES": //Spain
case "FR": //France
case "HR": //Croatia
case "IT": //Italy
case "CY": //Cyprus
case "LV": //Latvia
case "LT": //Lithuania
case "LU": //Luxembourg
case "HU": //Hungary
case "MT": //Malta
case "NL": //Netherlands
case "AT": //Austria
case "PL": //Poland
case "PT": //Portugal
case "RO": //Romania
case "SI": //Slovenia
case "SK": //Slovakia
case "FI": //Finland
//case "SE": //Sweden, sweden is removed since if from Sweden, VAT should be charged.
case "UK": //United Kingdom
return true; //zero VAT for all EU countries...
default:
return false;
}
}
}
Changing the VAT calculator
Since the order rows are marked in the Comments field, the VAT calculator can use the information to determine the exact VAT amount of each order row.
using Litium.Common.InversionOfControl;
using Litium.Foundation.Modules.ECommerce.Carriers;
using Litium.Foundation.Modules.ECommerce.Plugins.Vat;
using Litium.Studio.AddOns.Samples.Checkout;
namespace Litium.Studio.KC.Samples.VATCalculator
{
[Plugin("KCSamples")]
public class SampleVATCalculator:VatCalculator
{
public override void CalculateOrderRowVat(OrderRowCarrier orderRowCarrier)
{
//if the order rows are marked as NoVAT do not calculate VAT.
if (orderRowCarrier.Comments == SampleCheckoutFlow.NoVAT)
orderRowCarrier.TotalVATAmount = 0;
else
base.CalculateOrderRowVat(orderRowCarrier);
}
}
}
Note that the business logic of whether to have VAT or not is determined during the checkout flow, and that information is used in the VAT calculator. This helps to keep the VAT calculator fast because the pricing rules calculations should be as fast as possible. Also, when it comes to real implementation, the additional checks to determine whther an end customer should be charged VAT or not is not possible during the VAT calculation step.