Customizing tax classes

The Kentico E-commerce Solution enables you to modify tax classes. Thanks to this option, you can, for example:

  • Integrate a 3rd-party tax provider systems
  • Create tax application rules specifically according to your needs
  • Use your custom logic for applying taxes

To customize creating, retrieving and applying tax classes, implement a class that inherits from the TaxClassInfoProvider class and override its methods.

  1. Create a new class in your project in Visual Studio (for example, CustomTaxClassInfoProvider.cs) that inherits from the TaxClassInfoProvider class and registers the custom InfoProvider:

    
    
    
     [assembly: RegisterCustomProvider(typeof(CustomTaxClassInfoProvider))]
     public class CustomTaxClassInfoProvider : TaxClassInfoProvider
     {
     }
    
    
     

    Class location

    For learning purposes, the easiest way is to add the class to the App_Code folder (or Old_App_Code on web application installations).

    For production sites, we recommend creating a new assembly (Class library project) in your Kentico solution and including the provider class there. Then, add the appropriate references to both the assembly and the main Kentico web project. To learn more best practices, see Best practices for customization.

  2. Implement a method that overrides a default TaxClassInfoProvider method.

  3. Save your project and reload you Kentico website (and build your project if you use a web application installation).

If you open your website, Kentico uses the created CustomTaxClassInfoProvider class with the modified methods instead of the default implementation.

List of virtual methods in the TaxClassInfoProvider

GetTaxClassInfoInternal

Returns a tax class with a specified ID (int classId) or a tax class with a specified code name (string className) on a site with a specified name (string siteName).




protected virtual TaxClassInfo GetTaxClassInfoInternal(int classId) { }
protected virtual TaxClassInfo GetTaxClassInfoInternal(string className, string siteName) { }


SetTaxClassInfoInternal

Creates or updates a tax class according to a specified tax class object (TaxClassInfo classObj).




protected virtual void SetTaxClassInfoInternal(TaxClassInfo classObj) { }


DeleteTaxClassInfoInternal

Deletes a tax class according to a specified tax class object (TaxClassInfo classObj).




protected virtual void DeleteTaxClassInfoInternal(TaxClassInfo classObj) { }


GetTaxClassesInternal

Returns all tax classes on a site specified with its ID (int siteId).




protected virtual ObjectQuery<TaxClassInfo> GetTaxClassesInternal(int siteId) { }


GetSKUTaxClassesInternal

Returns all tax classes assigned to a product with a specified ID (int skuId).




protected virtual ObjectQuery<TaxClassInfo> GetSKUTaxClassesInternal(int skuId) { }


GetDepartmentTaxClassesInternal

Returns all tax classes assigned to a department with a specified ID (int departmentId).




protected virtual ObjectQuery<TaxClassInfo> GetDepartmentTaxClassesInternal(int departmentId) { }


GetTaxesInternal

Returns all taxes assigned to a product with a specified ID (int skuId) based on the specified country (int countryId), state (int stateId), and ZIP code (string zipCode). Or, returns all taxes that are applied to a specified shopping cart (ShoppingCartInfo cart) or a specified shopping cart item (ShoppingCartItemInfo item).

The default implementation does not use the ZIP code property. However, you can use it for custom scenarios if you override this method.




protected virtual DataSet GetTaxesInternal(int skuId, int countryId, int stateId, string zipCode) { }
protected virtual DataSet GetTaxesInternal(ShoppingCartInfo cart) { }
protected virtual List<IItemTax> GetTaxesInternal(ShoppingCartItemInfo item) { }


GetTaxValueInternal

Returns amount of tax money from a specified price (double price) and a percentage tax value from the given price (double taxValue). If the tax value is zero, returns zero.




protected virtual double GetTaxValueInternal(double price, double taxValue) { }


ApplyTaxInternal

Returns a price with taxes – adds an original price without taxes (double price) and a tax percentage from the given price (double taxValue).




protected virtual double ApplyTaxInternal(double price, double taxValue) { }


GetShippingTaxesInternal

Returns all taxes that are applied to a shipping option assigned to a specified shopping cart (ShoppingCartInfo cart).




protected virtual DataSet GetShippingTaxesInternal(ShoppingCartInfo cart) { }


GetSKUIDToGetTaxesInternal

Returns an ID of a product from which the taxes are used for the specified shopping cart item (ShoppingCartItemInfo item). (For product variants and products options of other than the Products type, the used taxes are from the parent product.)




protected virtual int GetSKUIDToGetTaxesInternal(ShoppingCartItemInfo item) { }


GetItemTaxClassInternal

Returns a tax class object used in a shopping cart from a specified data row values (DataRow dr). The data row must contain the following columns:

  • TaxClassID – string – the tax class ID
  • TaxClassDisplayName – string – a display name of the tax class
  • TaxClassZeroIfIDSupplied – boolean – if true, the tax class will not be applied for companies that have a tax registration ID specified
  • TaxValue – double – the percentage tax value
  • TaxIsGlobal – boolean – if true, the tax class is global



protected virtual IItemTax GetItemTaxClassInternal(DataRow dr) { }


Example – Calculating taxes from a different source

This example describes how to customize the tax class provider so that it integrates with a 3rd-party tax calculation service. The following customized provider selects the tax rate based on the department of the shopping cart item.

For purposes of this example, the taxes are created manually within the source code. You can replace these parts with a connector to your tax calculation service. You can also use any information stored in the shopping cart object to calculate taxes.




using System.Data;

using CMS;
using CMS.Ecommerce;

[assembly: RegisterCustomProvider(typeof(CustomTaxClassInfoProvider))]
public class CustomTaxClassInfoProvider : TaxClassInfoProvider
{
    /// <summary>
    /// Returns a set of taxes that are applied to the provided shopping cart items.
    /// Assigns taxes based on the items' departments.
    /// </summary>
    /// <param name="cart">Shopping cart with products to be taxed.</param>
    /// <returns>Shopping cart items' tax class dataset.</returns>
    protected override DataSet GetTaxesInternal(ShoppingCartInfo cart)
    {
        // Creates a new dataset for taxes
        DataSet ds = new DataSet();

        // Creates a new empty table for taxes
        DataTable table = GetNewTaxesTable();

        // Loops through all items in the shopping cart
        foreach (ShoppingCartItemInfo item in cart.CartItems)
        {
            // Note: You can replace the 'foreach' content with your custom logic of tax calculation.

            // Does not add any taxes to products without a department assigned
            if (item.SKU.SKUDepartmentID > 0)
            {
                // Gets the product's department to make a decision about the tax amount
                DepartmentInfo department = DepartmentInfoProvider.GetDepartmentInfo(item.SKU.SKUDepartmentID);

                // Gets the product's ID to know which product has which tax (for variants, gets the parent's ID)
                int skuId = item.SKU.IsProductVariant ? item.SKU.SKUParentSKUID : item.SKUID;

                // Assigns a tax based on the product's department
                switch (department.DepartmentName)
                {
                    // Adds a 20% tax to products from a department with a name "Coffees"
                    case "Coffees":
                        AddTaxRow(table, skuId, "Tax " + department.DepartmentDisplayName, 20);
                        break;

                    // Adds 11% and 10% taxes to products from a department with a name "Grinders"
                    case "Grinders":
                        AddTaxRow(table, skuId, "Tax " + department.DepartmentDisplayName + " 1", 11);
                        AddTaxRow(table, skuId, "Tax " + department.DepartmentDisplayName + " 2", 10);
                        break;

                    // Adds a 0% tax to products from a department with a name "Brewers" (i.e. does not add any taxes)
                    case "Brewers":
                        break;

                    // Adds a 5% tax to all other departments
                    default:
                        AddTaxRow(table, skuId, "Tax " + department.DepartmentDisplayName, 5);
                        break;
                }
            }
        }

        // Returns a built dataset with the taxes
        ds.Tables.Add(table);
        return ds;
    }

    /// <summary>
    /// Creates an empty tax table.
    /// </summary>
    /// <returns>Created empty table with predefined columns.</returns>
    private DataTable GetNewTaxesTable()
    {
        // Creates a new table
        DataTable table = new DataTable();

        // Adds the required columns for tax classes
        table.Columns.Add("SKUID", typeof(int));
        table.Columns.Add("TaxClassDisplayName", typeof(string));
        table.Columns.Add("TaxValue", typeof(double));

        // You can also add other (optional) columns:
        //table.Columns.Add("TaxIsGlobal", typeof(bool));
        //table.Columns.Add("TaxClassZeroIfIDSupplied", typeof(bool));

        // Returns the created empty table with predefined columns
        return table;
    }

    /// <summary>
    /// Creates a row in a tax table.
    /// </summary>
    /// <param name="taxTable">Tax table to which the row is added.</param>
    /// <param name="skuId">ID of the taxed product.</param>
    /// <param name="taxName">Display name of the added tax.</param>
    /// <param name="taxValue">Percentage value of the tax.</param>
    /// <param name="taxIsGlobal">True if the tax is global. False (default) if the tax is site-specific.</param>
    /// <param name="zeroTaxIfIDSupplied">True if the tax is 0% if the customer is a company
    /// with a tax registration ID. False (default) if the tax is for all customers.</param>
    private void AddTaxRow(DataTable taxTable, int skuId, string taxName, double taxValue, bool taxIsGlobal = false, bool zeroTaxIfIDSupplied = false)
    {
        // Creates a new row
        DataRow row = taxTable.NewRow();

        // Sets the required columns of a tax class
        row["SKUID"] = skuId;
        row["TaxClassDisplayName"] = taxName;
        row["TaxValue"] = taxValue;

        // You can also set other (optional) columns:
        //row["TaxIsGlobal"] = taxIsGlobal;
        //row["TaxClassZeroIfIDSupplied"] = zeroTaxIfIDSupplied;

        // Adds the row to the tax table
        taxTable.Rows.Add(row);
    }
}


Using both default and custom taxes

If you want to use custom taxes (for example, from a 3rd-party integration) along with your taxes entered in Kentico, you can achieve that with a little modification of the code above.

Instead of creating a new dataset, use the default dataset. At the end, do not add the table to the dataset as it is already there from the default implementation.




protected override DataSet GetTaxesInternal(ShoppingCartInfo cart)
{
    // Calculates the default taxes
    DataSet ds = base.GetTaxesInternal(cart);

    // Gets the table from the default tax implementation
    DataTable table = ds.Tables[0];

    // Loops through all items in the shopping cart
    foreach (ShoppingCartItemInfo item in cart.CartItems)
    {
        // Your custom tax calculation logic
    }

    // Returns a built dataset with the taxes
    return ds;
}