Sitefinity provides option to create discounts and apply them based on coupon code, new customers or for specific roles and users. However, sometimes different discounts should be applied based on whether the user is a new customer or have applied several different discount coupons. The applicable discount and those that will be applied are calculated in the DiscountCalculator. We will customize it and override methods in order to modify the discount applied.
Custom Discount Calculator
We will inherit from the default one and override the GetApplicableCartDiscounts method. We modify the discounts collection based on our criteria.
protected
override
IList<CartDiscount> GetApplicableCartDiscounts(CartOrder cartOrder,
decimal
subTotal, User user, List<Role> userRoles)
{
var all =
base
.GetApplicableCartDiscounts(cartOrder, subTotal, user, userRoles);
var discounts = all;
if
(discounts !=
null
)
{
// Get Discount with specific code
var discount = discounts
.Where(d => d.DiscountType == DiscountType.Coupon && d.ParentDiscount.CouponCode.Contains(
"code"
))
.FirstOrDefault();
if
(discount !=
null
)
{
var identity = ClaimsManager.GetCurrentIdentity();
if
(identity !=
null
&& identity.UserId !=
null
&& identity.UserId != Guid.Empty)
{
User currentUser = UserManager.GetManager().GetUser(identity.UserId);
if
(
new
CustomerRetriever().IsNewCustomer(currentUser))
{
// Apply only the coupon code discount
//for new customers and remove other discounts
discounts.Clear();
discounts.Add(discount);
}
else
{
// Remove discount if not new customer
discounts.Remove(discount);
}
}
}
}
return
discounts;
}
Applying the Custom Discount Calculator
Unfortunately, the discount calculator is coupled to the EcommerceOrderCalculator and we will need to override the methods which use the default calculator and make them use our custom one:
public
class
EcommerceOrderCalculatorCustom : EcommerceOrderCalculator
{
protected
override
decimal
GetWithoutShippingTax(CartOrder cartOrder, User user, List<Role> userRoles,
bool
useExchangeRate)
{
decimal
taxOnTotalBasePrice = GetPreDiscountTax(cartOrder, useExchangeRate);
decimal
totalBasePrice = GetSubTotalWithoutTaxes(cartOrder, useExchangeRate);
decimal
totalBasePriceNoExchangeRate = GetTotalBasePriceNoExchangeRate(cartOrder);
decimal
discountTotal =
new
DiscountCalculatorCustom().CalculateAndApplyOrderDiscounts(cartOrder, totalBasePriceNoExchangeRate, user, userRoles, useExchangeRate);
decimal
withoutShippingTax = 0;
var taxDisplayMode = EcommerceSettings.Taxes.TaxDisplayMode;
if
(taxDisplayMode == EcommerceConstants.OrdersConstants.ExcludingTax)
{
withoutShippingTax = EcommerceOrderCalculatorCustom.TaxIncludingDiscount(taxOnTotalBasePrice, totalBasePrice, discountTotal);
}
else
{
withoutShippingTax = taxOnTotalBasePrice;
}
return
withoutShippingTax;
}
private
static
decimal
TaxIncludingDiscount(
decimal
taxOnTotalBasePrice,
decimal
totalBeforeDiscounts,
decimal
discountAmount)
{
return
totalBeforeDiscounts == 0m ? taxOnTotalBasePrice :
taxOnTotalBasePrice * (totalBeforeDiscounts - discountAmount) / totalBeforeDiscounts;
}
protected
override
decimal
GetDiscountTotal(CartOrder cartOrder, User user, List<Role> userRoles,
bool
useExchangeRate)
{
decimal
totalBasePriceNoExchangeRate = GetTotalBasePriceNoExchangeRate(cartOrder);
decimal
exchangeRateSubTotal = GetSubTotalTaxInclusive(cartOrder, useExchangeRate);
decimal
sumRoundedOrderDiscounts =
new
DiscountCalculatorCustom().SumRoundedOrderDiscounts(cartOrder, exchangeRateSubTotal, totalBasePriceNoExchangeRate, user, userRoles, useExchangeRate);
return
sumRoundedOrderDiscounts;
}
}
Register the calculator in the Global application file:
protected
void
Application_Start(
object
sender, EventArgs e)
{
Telerik.Sitefinity.Abstractions.Bootstrapper.Initialized +=
new
EventHandler<Telerik.Sitefinity.Data.ExecutedEventArgs>(Bootstrapper_Initialized);
}
protected
void
Bootstrapper_Initialized(
object
sender, Telerik.Sitefinity.Data.ExecutedEventArgs args)
{
if
(args.CommandName ==
"Bootstrapped"
)
{
ObjectFactory.Container.RegisterType<IOrderCalculator, EcommerceOrderCalculatorCustom>(
new
TransientLifetimeManager(),
new
InjectionConstructor());
}
}
Show message for removed discounts
In order to inform the customer for the removed discounts, we will use a custom Discounts List control.
public
class
MyDiscountList : DiscountList
{
protected
override
void
OnPreRender(EventArgs e)
{
var calc =
new
DiscountCalculatorCustom();
var ordersManager = OrdersManager.GetManager();
CartOrder shoppingCart = GetShoppingCartForUser(ordersManager);
var items =
new
List<CartDiscount>();
if
(shoppingCart.UserId !=
null
&& shoppingCart.UserId != Guid.Empty)
{
var user = UserManager.GetManager().GetUser((Guid)shoppingCart.UserId);
var roles = RoleManager.GetManager().GetRolesForUser(user.Id).ToList();
items = calc.GetNotApplicableCartDiscounts(shoppingCart, shoppingCart.SubTotalDisplay, user, roles).ToList();
}
else
{
items = calc.GetNotApplicableCartDiscounts(shoppingCart, shoppingCart.SubTotalDisplay,
null
,
null
).ToList();
}
if
(items !=
null
&& items.Count > 0)
{
var sb =
new
StringBuilder();
sb.Append(
"The following discounts are not applicable:"
);
foreach
(var item
in
items)
{
sb.Append(item.Title);
sb.Append(
", "
);
}
sb.Length -= 2;
this
.MessageLabel.Text = sb.ToString();
}
base
.OnPreRender(e);
}
}
The discount list is applied in the Shopping cart template:
<
asp:UpdatePanel
runat
=
"server"
>
<
ContentTemplate
>
<
div
class
=
"sfShoppingCartTotal"
>
<
table
class
=
"sfShoppingCartDiscountList"
>
<
tbody
>
<
tr
runat
=
"server"
ID
=
"beforeDiscountRow"
>
<
th
>
<
asp:Label
ID
=
"productTotalQuantityBeforeDiscountLabel"
runat
=
"server"
/>
<
asp:Label
runat
=
"server"
Text="<%$ Resources:OrdersResources, Subtotal %>" CssClass="sfTxtLbl"/>:
</
th
>
<
td
>
<
asp:Label
ID
=
"totalPrice"
runat
=
"server"
Text
=
""
CssClass
=
"sfTxtLbl"
/>
</
td
>
</
tr
>
<
cs:MyDiscountList
runat
=
"server"
ID
=
"discountRows"
/>
</
tbody
>
</
table
>
</
div
>
<
div
class
=
"sfTotalRowWrp"
>
<
asp:Label
ID
=
"productTotalQuantity"
runat
=
"server"
/>
<
asp:Label
ID
=
"subTotalLabel"
runat
=
"server"
Text='<%$Resources:OrdersResources, SubtotalWithDiscounts %>' CssClass="sfTxtLbl" />:
<
strong
class
=
"sfPriceTotal"
><
asp:Label
ID
=
"afterDiscountPrice"
runat
=
"server"
Text
=
""
CssClass
=
"sfTxtLbl"
/></
strong
>
</
div
>
</
ContentTemplate
>
</
asp:UpdatePanel
>
<
asp:LinkButton
id
=
"updateButton"
Text="<%$Resources:OrdersResources, Update %>" runat="server" CssClass="sfshoppingCartUpdateLnk" />
Here is a video of the discounts modification:
Full source can be found and forked on GitHub here.
Nikola Zagorchev
Nikola Zagorchev is a Tech Support Engineer at Telerik. He joined the Sitefinity Support team in March 2014.