UPDATE: If you have > Commerce 10.1 there is a feature for this that solves the issue much more gracefully! I haven't evaluated it yet, but will do and update this blog post! http://world.episerver.com/documentation/developer-guides/commerce/marketing/exclude-products-from-promotions/
Note: this is a post regarding the new Promotion System, introduced in Commerce 9.24. I highly recommend using it, if you are not already.
Selling gift cards can be very good for business for many reasons (including the notorious “This gift card has expired” where the shop owner gets the income “for free”).
So can I sell gift cards with my Episerver Commerce site?
Absolutely! A lot of sites already do it. A common way of implementing it is creating a custom episerver content class that inherits VariationContent. I won’t go through an implementation on this in this blog post however, I want to focus on some things that are good to know when combining selling gift cards and promotions.
You might think “since it’s just a normal variant, it will work perfectly with promotions, just like my other variants”. And you are right! The promotion system works in a way that all items you add to the cart are affected, regardless of what created them. But when it comes to gift cards, you might not want every discount in your system to affect the price. Let’s take a common discount “20% off everything!”:
The Loophole
Initial setup
Have 5 gift cards in your cart worth $10 a piece ->
You now have 5 giftcards having spent $40 (due to the 20% discount)
The Loop
- Step 1: Buy 5 gift cards, pay $40 with 4 x $10 gift card
- Step 2: Receive the new gift cards and go back to step 1
Each iteration of the steps will grant you 1 free gift card worth $10, seeing as you only pay with the gift cards you purchased initially with the $40.
You can quickly realize this seems bad for business, especially if you have digital gift cards that are sent instantly via mail. This time tomorrow ebay will be filled with your newly created currency, which will quickly inflate seeing as you’re doing the “Print more bills in order to have more money!”-thing that historically doesn’t really work out… All from one person who likes loopholes and have the massive amount of initial investment of $40.
How can we prevent this?
As far as I’ve seen, Epi doesn’t have a built in way of excluding specific line items from the promotion evaluation (Would be great if such a feature was introduced!) so we’ll have to hack around it.
If you're running > Commerce 10.1. use http://world.episerver.com/documentation/developer-guides/commerce/marketing/exclude-products-from-promotions/.
So, normally you want to have somewhere in your cart loading/updating flow
((IPromotionEngine)_promotionEngine).Run((ICart) cart);
But we want to add some hacky logic to exclude gift cards, so instead of that line, create a method that
- Removes the gift card line items from the cart
- Run the promotions on the cart without gift card line items
- Catches the discounts applied and applies them to the existing cart
Like such:
public void RunPromotions(ICart cart) { // Presuming you have a meta field on line item that tells you // if the line item is a gift card or not if (cart.GetAllLineItems().All(li => !li.GetIsGiftCard())) { ((IPromotionEngine) _promotionEngine).Run(cart); return; } // We must exclude at least one giftcard. To do this, // perform all calculations on a clone of the cart and // copy the result to the original cart var clone = (ICart)((IDeepCloneable)cart).DeepClone(); // Note that this implementation is working around always only having // 1 OrderForm per Cart and 1 Shipment per Cart. // It can be wise use several Shipments, but we do that once // we create a PurchaseOrder of it var cloneForm = clone.Forms.First(); var cloneShipment = cloneForm.Shipments.First(); foreach (var li in cloneShipment.LineItems.Where(li => li.GetIsGiftCard()).ToList()) { cloneShipment.LineItems.Remove(li); } _promotionEngine.Run(clone); var cartForm = cart.Forms.First(); var cartShipment = cartForm.Shipments.First(); // Set the shipping discount from the clone var shipmentDiscount = cloneShipment.TryGetDiscountValue(x => x.ShipmentDiscount); cartShipment.TrySetDiscountValue(x => x.ShipmentDiscount, shipmentDiscount); // Set the entry discounts from the clone var cartLineItems = cartShipment.LineItems.ToDictionary(li => li.LineItemId); foreach (var cloneLineItem in cloneShipment.LineItems) { ILineItem cartLineItem; if (cartLineItems.TryGetValue(cloneLineItem.LineItemId, out cartLineItem)) { cartLineItem.TrySetDiscountValue( x => x.EntryAmount, cloneLineItem.GetEntryDiscount() ); cartLineItem.TrySetDiscountValue( x => x.OrderAmount, cloneLineItem.TryGetDiscountValue(x => x.OrderAmount) ); } else { // Rare case, but you can make it happen that you'll // get a gift from the clone, but not the ordinary cart cartShipment.LineItems.Add(cloneLineItem); } } foreach (var giftToRemove in cartShipment.LineItems.Where( li => li.IsGift && !cloneShipment.LineItems.Any(cl => cl.LineItemId == li.LineItemId)) .ToList()) { // Remove eventual gifts you got from the cart with the gift cards cartShipment.LineItems.Remove(giftToRemove); } // Remove promotions from the orderfrom and re add them from the clone form cartForm.Promotions.Clear(); foreach (var p in cloneForm.Promotions) cartForm.Promotions.Add(p); }
It is a very hacky way of doing it, but it works. Ebay will not be filled with your gift cards, the loopholey person that had $40 still has $40, the universe it at peace.
If you have any comments/BETTER SOLUTIONS/questions about this post, feel free to leave a comment below.