Don't miss out Virtual Happy Hour this Friday (April 26).

Try our conversational search powered by Generative AI!

Loading...

Recommended reading 

Calculating orders [BETA]

[New in 8.12.0]

Introduction

Five calculation services can be used to calculate order totals in different levels:

  • ILineItemCalculator: For calculating extended price
  • IShippingCalculator: For calculating shipment cost, and total amount of items in the shipment
  • ITaxCalculator: For calculating taxes
  • IOrderFormCalculator: For calculating totals on an order form
  • IOrderGroupCalculator: For calculating totals on an order

Shipment required for correct calculation

Order calculators only calculate line items that belong to a shipment. This is a changed behavior from the way it worked in the workflow activities. When an order (cart, purchase order, or payment plan) is created through the IOrderRepository, a shipment is created.

Calculate all

The method GetTotals on IOrderGroup returns an instance of the class OrderGroupTotals. This instance contains calculated values for the order group, the order group's order forms, the shipments on the order forms, and the line items on the shipments.

The method calls all of the calculators to calculate the values.

public void GetTotals(IOrderGroup orderGroup)
{
    var calculatedTotals = orderGroup.GetTotals();

    Debug.WriteLine("Handling total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, calculatedTotals.HandlingTotal);
    Debug.WriteLine("Shipping total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, calculatedTotals.ShippingTotal);
    Debug.WriteLine("Sub total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, calculatedTotals.SubTotal);
    Debug.WriteLine("Tax total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, calculatedTotals.TaxTotal);
    Debug.WriteLine("Total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, calculatedTotals.Total);

    foreach (var form in orderGroup.Forms)
    {
        var orderFormTotals = calculatedTotals[form];

        Debug.WriteLine("Handling total for order form '{0}': {1}", form.OrderFormId, orderFormTotals.HandlingTotal);
        Debug.WriteLine("Shipping total for order form '{0}': {1}", form.OrderFormId, orderFormTotals.ShippingTotal);
        Debug.WriteLine("Sub total for order form '{0}': {1}", form.OrderFormId, orderFormTotals.SubTotal);
        Debug.WriteLine("Tax total for order form '{0}': {1}", form.OrderFormId, orderFormTotals.TaxTotal);
        Debug.WriteLine("Total for order form '{0}': {1}", form.OrderFormId, orderFormTotals.Total);

        foreach (var shipment in form.Shipments)
        {
            var shipmentTotals = orderFormTotals[shipment];

            Debug.WriteLine("Shipping cost for shipment '{0}': {1}", shipment.ShipmentId, shipmentTotals.ShippingCost);
            Debug.WriteLine("Items total for shipment '{0}': {1}", shipment.ShipmentId, shipmentTotals.ItemsTotal);

            foreach (var item in shipment.LineItems)
            {
                Debug.WriteLine("Extended price for '{0}': {1}", item.Code, shipmentTotals[item]);
            }
        }
    }
}

Line item calculator

The ILineItemCalculator calculates the extended price of a line item. 

public void GetExtendedPrice(ILineItem lineItem, ILineItemCalculator lineItemCalculator)
{
    var extendedPrice = lineItemCalculator.GetExtendedPrice(lineItem, Currency.USD);
    Debug.WriteLine("Extended price for '{0}': {1}", lineItem.Code, extendedPrice);
}

Change the default calculation of extended price

By inheriting from the default implementation of the interface, DefaultLineItemCalculator, it's easy to override the calculation of extended price. Just override the method CalculateExtendedPrice.

public class LineItemCalculatorSample : DefaultLineItemCalculator
{
    protected override Money CalculateExtendedPrice(ILineItem lineItem, Currency currency)
    {
        var rawExtendedPrice = lineItem.PlacedPrice * lineItem.Quantity - lineItem.LineItemDiscountAmount - lineItem.OrderLevelDiscountAmount;
        var result = rawExtendedPrice < 0 ? 0 : rawExtendedPrice;

        return new Money(result, currency);
    }
}

Change the validation for extended price

The default implementation validates that the extended price isn't negative after the calculation. To change the behavior, override the method ValidateExtendedPrice.

public class LineItemCalculatorSample : DefaultLineItemCalculator
{
    protected override void ValidateExtendedPrice(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Extended price must be greater than 0");
        }
    }
}

Shipping calculator

The IShippingCalculator calculates the shipment cost for the order, order form, and shipment. It also calculates the total amount of the line items in the shipment.

public void GetShipmentCost(IOrderGroup orderGroup, IShippingCalculator shipmentCalculator, ICurrentMarket currentMarket)
{
    var shipmentCost = shipmentCalculator.GetShipmentCost(orderGroup, currentMarket.GetCurrentMarket(), Currency.USD);
    Debug.WriteLine("Shiping cost for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, shipmentCost);
}
public void GetShipmentCost(IOrderForm orderForm, IShippingCalculator shipmentCalculator, ICurrentMarket currentMarket)
{
    var shipmentCost = shipmentCalculator.GetShipmentCost(orderForm, currentMarket.GetCurrentMarket(), Currency.USD);
    Debug.WriteLine("Shiping cost for order form '{0}': {1}", orderForm.OrderFormId, shipmentCost);
}
public void GetShipmentCost(IShipment shipment, IShippingCalculator shipmentCalculator, ICurrentMarket currentMarket)
{
    var shipmentCost = shipmentCalculator.GetShipmentCost(shipment, currentMarket.GetCurrentMarket(), Currency.USD);
    Debug.WriteLine("Shiping cost for shipment '{0}': {1}", shipment.ShipmentId, shipmentCost);
}
public void GetShippingItemsTotal(IShipment shipment, IShippingCalculator shipmentCalculator)
{
    var shippingItemsTotal = shipmentCalculator.GetShippingItemsTotal(shipment, Currency.USD);
    Debug.WriteLine("Shiping items total for '{0}': {1}", shipment.ShipmentId, shippingItemsTotal);
}

Change the default calculation of shipment cost and shipping item total

By inheriting from the default implementation of the interface, DefaultShippingCalculator, it's easy to override the calculation of shipment cost. Just override one or several of the methods CalculateShipmentCost to change the calculation of shipment cost. To change the calculation of shipping items total, override the method CalculateShippingItemsTotal.

public class ShippingCalculatorSample : DefaultShippingCalculator
{
    private readonly ILineItemCalculator _lineItemCalculator;

    public ShippingCalculatorSample(ILineItemCalculator lineItemCalculator)
        : base(lineItemCalculator)
    {
        _lineItemCalculator = lineItemCalculator;
    }

    protected override Money CalculateShipmentCost(IOrderGroup orderGroup, IMarket market, Currency currency)
    {
        //iterate over all order forms in this order group and sum the results.
        var shippingTotal = orderGroup.Forms
            .Sum(form => GetShipmentCost(form, market, currency).Amount);

        return new Money(shippingTotal, currency);
    }

    protected override Money CalculateShipmentCost(IOrderForm orderForm, IMarket market, Currency currency)
    {
        //iterate over all shipments in this order form and sum the results.
        var result = orderForm.Shipments
            .Sum(shipment => GetShipmentCost(shipment, market, currency).Amount - shipment.ShippingDiscountAmount);

        return new Money(result, currency);
    }

    protected override Money CalculateShipmentCost(IShipment shipment, IMarket market, Currency currency)
    {
        //find the shipping method assigned to the shipment
        var shippingMethods = ShippingManager.GetShippingMethods(String.Empty);
        var row = shippingMethods.ShippingMethod.FindByShippingMethodId(shipment.ShippingMethodId);

        //get the instance of the shipping provider.
        var type = Type.GetType(row.ShippingOptionRow.ClassName);
        var provider = (IShippingGateway)Activator.CreateInstance(type, market);

        //get the rate for this shipment
        var message = String.Empty;
        var rate = provider.GetRate(row.ShippingMethodId, shipment as Shipment, ref message);

        //convert result to correct currency and return.
        return rate != null ?
            CurrencyFormatter.ConvertCurrency(rate.Money, currency) :
            new Money(0, currency);
    }

    protected override Money CalculateShippingItemsTotal(IShipment shipment, Currency currency)
    {
        //calculate the total price of all line items
        var result = 0m;
        foreach (var lineItem in shipment.LineItems.Where(x => x.Quantity > 0))
        {
            var extemdedPrice = _lineItemCalculator.GetExtendedPrice(lineItem, currency);
            result += extemdedPrice.Amount;
        }

        return new Money(result, currency);
    }
}

Change the validation

The default implementation validates that the price is not negative after the calculation. To change the behavior, override the method ValidateShipmentCostForOrderValidateShipmentCostForOrderFormValidateShipmentCostForShipment, or ValidateShippingItemTotal, pending on which validation that should be overridden.

public class ShippingCalculatorSample : DefaultShippingCalculator
{
    private readonly ILineItemCalculator _lineItemCalculator;

    public ShippingCalculatorSample(ILineItemCalculator lineItemCalculator)
        : base(lineItemCalculator)
    {
        _lineItemCalculator = lineItemCalculator;
    }

    protected override void ValidateShipmentCostForOrder(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipment cost must be greater than 0");
        }
    }

    protected override void ValidateShipmentCostForOrderForm(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipment cost must be greater than 0");
        }
    }

    protected override void ValidateShipmentCostForShipment(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipment cost must be greater than 0");
        }
    }

    protected override void ValidateShippingItemTotal(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipping item total must be greater than 0");
        }
    }
}

Tax calculator

The ITaxCalculator calculates the tax cost for the order, and order form.

public void GetTaxTotal(IOrderGroup orderGroup, ITaxCalculator taxCalculator, ICurrentMarket currentMarket)
{
    var taxTotal = taxCalculator.GetTaxTotal(orderGroup, currentMarket.GetCurrentMarket(), Currency.USD);
    Debug.WriteLine("Tax total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, taxTotal);
}
public void GetTaxTotal(IOrderForm orderForm, ITaxCalculator taxCalculator, ICurrentMarket currentMarket)
{
    var taxTotal = taxCalculator.GetTaxTotal(orderForm, currentMarket.GetCurrentMarket(), Currency.USD);
    Debug.WriteLine("Tax total for order form '{0}': {1}", orderForm.OrderFormId, taxTotal);
}

Change the default calculation of tax total

By inheriting from the default implementation of the interface, DefaultTaxCalculator, it is easy to override the calculation of tax total. Just override one or several of the methods CalculateTaxTotal.

public class TaxCalculatorOverridingDefault : DefaultTaxCalculator
{
    public TaxCalculatorOverridingDefault(IContentRepository contentRepository,
        ReferenceConverter referenceConverter,
        IShippingCalculator shippingCalculator)
        : base(contentRepository, referenceConverter, shippingCalculator)
    {
    }

    //override this to implement custom tax retrieval
    protected override IEnumerable GetTaxValues(string taxCategory, string languageCode, IOrderAddress orderAddress)
    {
        return new[] { new TaxValue() };
    }

    //override this to implement custom tax calculation for a shipment
    protected override decimal CalculateTax(IEnumerable taxes, IShipment shipment, IMarket market, Currency currency)
    {
        return 0;
    }
}

Change the validation

The default implementation validates that the price is not negative after the calculation. To change the behavior, override the method ValidateTaxTotalForOrder, or ValidateTaxTotalForOrderForm, pending on which validation that should be overridden.

public class TaxCalculatorOverridingDefault : DefaultTaxCalculator
{
    public TaxCalculatorOverridingDefault(IContentRepository contentRepository,
        ReferenceConverter referenceConverter,
        IShippingCalculator shippingCalculator)
        : base(contentRepository, referenceConverter, shippingCalculator)
    {
    }

    protected override void ValidateTaxTotalForOrder(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipping item total must be greater than 0");
        }
    }

    protected override void ValidateTaxTotalForOrderForm(Money money)
    {
        if (money.Amount <= 0)
        {
            throw new ValidationException("Shipping item total must be greater than 0");
        }
    }
}

Order form calculator

The IOrderFormCalculator calculates the handling total, sub total, and total for an order form.

public void GetOrderGroupTotals(IOrderForm orderForm, IOrderFormCalculator orderFormCalculator, ICurrentMarket currentMarket)
{
    var handlingTotal = orderFormCalculator.GetHandlingTotal(orderForm, Currency.USD);
    var subTotal = orderFormCalculator.GetSubTotal(orderForm, Currency.USD);
    var total = orderFormCalculator.GetTotal(orderForm, currentMarket.GetCurrentMarket(), Currency.USD);

    Debug.WriteLine("Handling total for order form '{0}': {1}", orderForm.OrderFormId, handlingTotal);
    Debug.WriteLine("Sub total for order form '{0}': {1}", orderForm.OrderFormId, subTotal);
    Debug.WriteLine("Total for order form '{0}': {1}", orderForm.OrderFormId, total);
}

Change the default calculations

By inheriting from the default implementation of the interface, DefaultOrderFormCalculator, it is easy to override the calculations. Just override one or several of the methods CalculateHandlingTotalCalculateSubtotal, or CalculateTotal.

Change the validation

The default implementation validates that the price is not negative after the calculation. To change the behavior, override the method ValidateHandlingTotalValidateSubtotal, or ValidateTotal, pending on which validation that should be overridden.

Order calculator

The IOrderGroupCalculator calculates the handling total, sub total, and total for an order.

public void GetOrderGroupTotals(IOrderGroup orderGroup, IOrderGroupCalculator orderGroupCalculator, ICurrentMarket currentMarket)
{
    var handlingTotal = orderGroupCalculator.GetHandlingTotal(orderGroup, Currency.USD);
    var subTotal = orderGroupCalculator.GetSubTotal(orderGroup, Currency.USD);
    var total = orderGroupCalculator.GetTotal(orderGroup, currentMarket.GetCurrentMarket(), Currency.USD);

    Debug.WriteLine("Handling total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, handlingTotal);
    Debug.WriteLine("Sub total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, subTotal);
    Debug.WriteLine("Total for order '{0}': {1}", orderGroup.OrderLink.OrderGroupId, total);
}

Change the default calculations

By inheriting from the default implementation of the interface, DefaultOrderGroupCalculator, it is easy to override the calculations. Just override one or several of the methods CalculateHandlingTotalCalculateSubtotal, or CalculateTotal.

Change the validation

The default implementation validates that the price isn't negative after the calculation. To change the behavior, override the method ValidateHandlingTotalValidateSubtotal, or ValidateTotal, pending on which validation that should be overridden.

Do you find this information helpful? Please log in to provide feedback.

Last updated: Apr 28, 2015

Recommended reading