Skip to content

Instantly share code, notes, and snippets.

@jstemerdink
Created February 5, 2017 14:53
Show Gist options
  • Save jstemerdink/dcee170c14185b64dde09f43f43801ef to your computer and use it in GitHub Desktop.
Save jstemerdink/dcee170c14185b64dde09f43f43801ef to your computer and use it in GitHub Desktop.
A custom tax calculator for EPiServer Commerce

Custom tax calculator

When displaying the price of a lineitem including tax, you may see a difference if you add up the values of the lineitems and the total that is calculated by Commerce. This calculator fixes that difference.

The only difference with the original one is on line 102, I rounded the value before adding it to the total. The other methods needed to be copied as they are not proptected in the base.

Read my blog here

Powered by ReSharper image

namespace EPiServer.Reference.Commerce.Site.Features.Shared.Services
{
using System.Collections.Generic;
using System.Linq;
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Commerce.Order;
using EPiServer.Commerce.Order.Calculator;
using EPiServer.Commerce.Order.Internal;
using Mediachase.Commerce;
using Mediachase.Commerce.Catalog;
using Mediachase.Commerce.Orders;
/// <summary>
/// Class CustomTaxCalculator.
/// </summary>
/// <seealso cref="EPiServer.Commerce.Order.Calculator.DefaultTaxCalculator" />
public class CustomTaxCalculator : DefaultTaxCalculator
{
/// <summary>
/// The content repository
/// </summary>
private readonly IContentRepository contentRepository;
/// <summary>
/// The line item calculator
/// </summary>
private readonly ILineItemCalculator lineItemCalculator;
/// <summary>
/// The reference converter
/// </summary>
private readonly ReferenceConverter referenceConverter;
/// <summary>
/// The shipping calculator
/// </summary>
private readonly IShippingCalculator shippingCalculator;
/// <summary>
/// Initializes a new instance of the <see cref="CustomTaxCalculator"/> class.
/// </summary>
/// <param name="contentRepository">The content Repository.</param>
/// <param name="referenceConverter">The reference converter.</param>
/// <param name="shippingCalculator">The shipping calculator.</param>
/// <param name="lineItemCalculator">The line item calculator.</param>
public CustomTaxCalculator(
IContentRepository contentRepository,
ReferenceConverter referenceConverter,
IShippingCalculator shippingCalculator,
ILineItemCalculator lineItemCalculator)
: base(contentRepository, referenceConverter, shippingCalculator, lineItemCalculator)
{
this.contentRepository = contentRepository;
this.referenceConverter = referenceConverter;
this.shippingCalculator = shippingCalculator;
this.lineItemCalculator = lineItemCalculator;
}
/// <summary>
/// Gets the tax total for the order form.
/// </summary>
/// <param name="orderForm">The order form.</param>
/// <param name="market">The market to be used in the calculation.</param>
/// <param name="currency">The currency to be used in the calculation.</param>
/// <returns>The total tax amount for the order form.</returns>
/// <example>
/// <code source="../CodeSamples/EPiServer.Commerce/Orders/OrderCalculatorsSample.cs" region="GetTaxTotalForOrderForm" lang="cs" />
/// </example>
protected override Money CalculateTaxTotal(IOrderForm orderForm, IMarket market, Currency currency)
{
decimal amount1 = new decimal();
foreach (IShipment shipment in orderForm.Shipments.Where(x => x.ShippingAddress != null))
{
decimal amount2 = new decimal();
decimal amount3 = this.shippingCalculator.GetDiscountedShippingAmount(shipment, market, currency).Amount;
decimal amount4 = this.shippingCalculator.GetShippingItemsTotal(shipment, currency).Amount;
foreach (ILineItem lineItem in shipment.LineItems)
{
int taxCategoryId;
ITaxValue[] taxValues;
if (this.TryGetTaxCategoryId(lineItem, out taxCategoryId)
&& this.TryGetTaxValues(market, shipment, taxCategoryId, out taxValues))
{
decimal num2 = string.IsNullOrEmpty(orderForm.Name)
|| !orderForm.Name.Equals(OrderForm.ReturnName)
? lineItem.Quantity
: lineItem.ReturnQuantity;
decimal num3 = GetPriceExcludingTax(lineItem) * num2;
decimal totalQuantity = shipment.LineItems.Sum(l => l.Quantity);
amount2 += CalculateShippingTax(
taxValues,
amount4,
amount3,
num3,
lineItem.Quantity,
totalQuantity);
amount1 += currency.Round(GetTaxes(taxValues, TaxType.SalesTax, num3));
}
}
amount1 += currency.Round(amount2);
}
return new Money(currency.Round(amount1), currency);
}
/// <summary>
/// Calculates the shipping tax.
/// </summary>
/// <param name="taxes">The taxes.</param>
/// <param name="shippingTotal">The shipping total.</param>
/// <param name="shippingCost">The shipping cost.</param>
/// <param name="itemExtendedPrice">The item extended price.</param>
/// <param name="itemQuantity">The item quantity.</param>
/// <param name="totalQuantity">The total quantity.</param>
/// <returns>The shipping tax.</returns>
private static decimal CalculateShippingTax(
ITaxValue[] taxes,
decimal shippingTotal,
decimal shippingCost,
decimal itemExtendedPrice,
decimal itemQuantity,
decimal totalQuantity)
{
decimal basePrice = shippingTotal == decimal.Zero
? itemQuantity / totalQuantity * shippingCost
: itemExtendedPrice / shippingTotal * shippingCost;
return GetTaxes(taxes, TaxType.ShippingTax, basePrice);
}
/// <summary>
/// Gets the price excluding tax.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The prices excluding tax.</returns>
private static decimal GetPriceExcludingTax(ILineItem item)
{
return item.PlacedPrice - (item.TryGetDiscountValue(x => x.OrderAmount) + item.TryGetDiscountValue(x => x.EntryAmount)) / item.Quantity;
}
/// <summary>
/// Gets the taxes.
/// </summary>
/// <param name="taxes">The taxes.</param>
/// <param name="taxtype">The taxtype.</param>
/// <param name="basePrice">The base price.</param>
/// <returns>System.Decimal.</returns>
private static decimal GetTaxes(IEnumerable<ITaxValue> taxes, TaxType taxtype, decimal basePrice)
{
return
taxes.Where(x => x.TaxType == taxtype)
.Sum(x => basePrice * (decimal)x.Percentage * new decimal(1, 0, 0, false, 2));
}
/// <summary>
/// Try to get the tax category identifier.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="taxCategoryId">The tax category identifier.</param>
/// <returns><c>true</c> if the identifier was retrieved, <c>false</c> otherwise.</returns>
private bool TryGetTaxCategoryId(ILineItem item, out int taxCategoryId)
{
IPricing pricing =
this.contentRepository.Get<EntryContentBase>(this.referenceConverter.GetContentLink(item.Code)) as
IPricing;
if (pricing == null || !pricing.TaxCategoryId.HasValue)
{
taxCategoryId = 0;
return false;
}
taxCategoryId = pricing.TaxCategoryId.Value;
return true;
}
/// <summary>
/// Try to get the tax values.
/// </summary>
/// <param name="market">The market.</param>
/// <param name="shipment">The shipment.</param>
/// <param name="taxCategoryId">The tax category identifier.</param>
/// <param name="taxValues">The tax values.</param>
/// <returns><c>true</c> if tax values coould be retrieved, <c>false</c> otherwise.</returns>
private bool TryGetTaxValues(IMarket market, IShipment shipment, int taxCategoryId, out ITaxValue[] taxValues)
{
string categoryNameById = this.GetTaxCategoryNameById(taxCategoryId);
taxValues =
this.GetTaxValues(categoryNameById, market.DefaultLanguage.Name, shipment.ShippingAddress).ToArray();
return taxValues.Any();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment