Adjust your Tax Calculation to Use Billing Address in Optimizely Commerce

VP, Enterprise Platforms
Valtech

July 09, 2018

Even thought there are great tax calculation providers with add-ons for Optimizely Commerce (like Vertex), which makes tax compliance in e-Commerce a breeze, it happens to be required for developers to leverage the built-in taxation system in Optimizely Commerce.

Even thought there are great tax calculation providers with add-ons for Optimizely Commerce (like Vertex), which makes tax compliance in e-Commerce a breeze, it happens to be required for developers to leverage the built-in taxation system in Optimizely Commerce.

Configuration of tax jurisdictions, through various geographic descriptors, taxation types and corresponding fee’s are always required. Once in a while, we are on top of that in the need of making further changes than that.

Abstraction of Optimizely’s Tax System

Optimizely’s calculation workflows relies on an interface named ITaxCalculator, located in Optimizely.Commerce.Order, which acts as the abstraction of the entire tax calculation system. It can, like most other public Optimizely APIs, be easily interchanged with your own custom implementation – e.g. if you are in the need of adjusting the behavior beyond the configurable jurisdictions.

 

All you need to do is to implement the required operations as per your business requirements, and then register it via Dependency Injection.

 

Sample Customization of the Optimizely Tax System

Optimizely’s built-in tax calculations are of course tailored to meet common compliance requirements, but we are sometimes faced by the need of customizing it. Let me provide an example.

Out-of-the-box, Optimizely uses the shipping address of each shipment as the parameter for the tax calculation. That is not always sufficient to achieve tax compliance, and we have a number of times overridden the out-of-the-box behavior to accommodate the requirements for business-to-business transactions in parts of the world – that is relying on the billing address as the parameter for deciding the tax values.

class TaxCalculator: DefaultTaxCalculator, ITaxCalculator

{

    public TaxCalculator(IContentRepository contentRepository, ReferenceConverter referenceConverter, IShippingCalculator shippingCalculator

        , ILineItemCalculator lineItemCalculator, IReturnLineItemCalculator returnLineItemCalculator)

        : base(contentRepository, referenceConverter, shippingCalculator, lineItemCalculator, returnLineItemCalculator)

    {

    }




    /// <summary>

    /// Get Shipping tax total

    /// </summary>

    /// <param name="shipment"></param>

    /// <param name="market"></param>

    /// <param name="currency"></param>

    /// <returns>total shipping tax</returns>

    public new virtual Money GetShippingTaxTotal(IShipment shipment, IMarket market, Currency currency)

    {

        //Ceate inMemory shipment to avoid updating customer cart

        InMemoryShipment inMemoryShipment = this.CreateMemoryShipmentAndApplyAddress(shipment);




        return base.GetShippingTaxTotal(inMemoryShipment, market, currency);

    }




    /// <summary>

    /// Get tax total (sum of Sales and Shipping tax total) from order group

    /// </summary>

    /// <param name="orderGroup"></param>

    /// <param name="market"></param>

    /// <param name="currency"></param>

    /// <returns>Total tax amount</returns>

    public new virtual Money GetTaxTotal(IOrderGroup orderGroup, IMarket market, Currency currency)

    {

        //Ceate inMemory order group to avoid updating customer cart

        InMemoryOrderGroup inMemoryOrderGroup = this.CreateMemoryOrderGroupAndApplyShippingAddress(orderGroup);




        return base.GetTaxTotal(inMemoryOrderGroup, market, currency);

    }




    /// <summary>

    /// Get tax total (sum of Sales and Shipping tax total) from order form

    /// </summary>

    /// <param name="orderForm"></param>

    /// <param name="market"></param>

    /// <param name="currency"></param>

    /// <returns>Total tax amount</returns>

    public new virtual Money GetTaxTotal(IOrderForm orderForm, IMarket market, Currency currency)

    {

        //Ceate inMemory order form to avoid updating customer cart

        InMemoryOrderForm inMemoryOrderForm = CreateMemoryOrderFormAndApplyShippingAddress(orderForm);




        return base.GetTaxTotal(inMemoryOrderForm, market, currency);

    }




    private InMemoryOrderGroup CreateMemoryOrderGroupAndApplyShippingAddress(IOrderGroup orderGroup)

    {

        if (orderGroup is SerializableCart)

            orderGroup = ((SerializableCart)orderGroup).DeepClone() as IOrderGroup;




        InMemoryOrderGroup inMemoryOrderGroup = new InMemoryOrderGroup(orderGroup);




        //Sample - retrieve the billing address according to your business requirements.

        IOrderAddress billingAddress = ;




        //Tax is to be calculated from customer billing address instead of shipping address

        if (billingAddress != null)

            inMemoryOrderGroup.GetFirstShipment().ShippingAddress = billingAddress;




        return inMemoryOrderGroup;

    }




    private InMemoryOrderForm CreateMemoryOrderFormAndApplyShippingAddress(IOrderForm orderForm)

    {

        InMemoryOrderForm inMemoryOrderForm = new InMemoryOrderForm(orderForm);




        IShipment shipment = inMemoryOrderForm.Shipments.FirstOrDefault();




        if (shipment == null)

            return inMemoryOrderForm;




        //Sample - retrieve the billing address according to your business requirements.

        IOrderAddress billingAddress = ;




        //Tax is to be calculated from customer billing address instead of shipping address

        if (billingAddress != null)

            shipment.ShippingAddress = billingAddress;




        return inMemoryOrderForm;

    }




    private InMemoryShipment CreateMemoryShipmentAndApplyAddress(IShipment shipment)

    {

        InMemoryShipment inMemoryShipment = new InMemoryShipment(shipment);




        //Sample - retrieve the billing address according to your business requirements.

        IOrderAddress billingAddress = ;




        //Tax is to be calculated from customer billing address instead of shipping address

        if (billingAddress != null)

            inMemoryShipment.ShippingAddress = billingAddress;




        return inMemoryShipment;

    }

}

 

Please note that the retrieval of billing address has been removed from the sample, since that is required to be implemented according to your business logic – especially when we’re calculating taxes for the shipping fee. We’re relying on the concept of MemoryOrders in Optimizely Commerce to avoid duplication of the entire tax calculation logic shipped with the Optimizely platform. It saves a lot of headaches down the road, since we directly will benefit from any changes made by Optimizely during upgrades!

 

Original article was posted on Optimizely Fellow: http://fellow.aagaardrasmussen.dk/2018/07/09/adjust-your-tax-calculation-to-use-billing-address-in-episerver-commerce/

Contact us

We would love to hear from you! Please fill out the form and the nearest person from office will contact you.

Let's reinvent the future