Sitecore Experience Commerce - Remove an Expired Promo from an Unpaid Order

Many online shops allow for the use of coupons and promotion codes. Some Sitecore Experience Commerce shops even allow the customer to use a promo code and [pay for the order at a later date.]( "") But what happens if the promotion is set to expire before the customer pays the balance on the order? In this case, it would be satisfying for the customer to be alerted that the promo will be expiring soon, and if payment isn’t received by the expiration date, the promo will be removed from the order. To do this, we will need to create a new minion.

This minion will review all unpaid orders with promotions and verify if the promotion code used when originally placing the order has expired, or if it is to expire soon. If expired, it will remove the promo from the order and send an email to inform the customer that the promotion has expired and present them with their new order balance to be paid. If it is set to expire within the ‘soon’ time-frame, based on a date threshold of your choosing, then it will send an email warning the customer that the promo code is to expire soon. Let’s get started.

First, we will need to create a minion. Once the minion class is created and configured to ‘wakeup’ at the desired time, we will need to create a pipeline for the minion to execute. Configure this pipeline to run a PipelineBlock to validate the order, and another to remove the coupon from the order.

configure =>

The validate block will accept a PipelineArgument that will hold the processing order id, review the order for expiration, and return the order if expired. The remove block will accept the order, remove the promo, and return the order to the minion.

Now that we have our minion and pipeline configured, we will need to get the list of completed order ids.

long completedListCount = await GetListCount("CompletedOrders");
var completedOrderIds = (await GetListIds("CompletedOrders", Convert.ToInt32(completedListCount))).IdList;

First, we get the completed list count by calling the inherited base method – GetListCount, passing the list name ‘CompletedOrders’.

We then pass the count as the ‘take’ parameter for the base GetListIds method, to ensure the minion grabs all the ids for orders that are in completed status. This method returns a ‘FindEntitiesInListArgument’, which contains a property that holds the ids as a list of strings.

Next, we’ll need to loop through all of the ids and create an argument based on each to pass to the minion pipeline. (Be sure to create a new CommerceContext and new CommercePipelineExecutionContextOptions for each iteration. This will prevent you from having concurrency issues within the loop.)

MinionArgument minionArgument = new MinionArgument();
minionArgument.OrderId = orderId;
Order updatedOrder = await ExpiredPromoMinionPipeline.Run(minionArgument, contextOptions);

Next, we’ll need to loop through all of the ids and create an argument based on each to pass to the minion pipeline. (Be sure to create a new CommerceContext and new CommercePipelineExecutionContextOptions for each iteration. This will prevent you from having concurrency issues within the loop.)

Validate Expired Promo Block

There are a few things we want to validate on the order, including:

  • Order object exists
  • Order is not currently being processed by another minion or task
  • Order is in completed status
  • Order has a balance

Once the order passes this validation, we need to verify that the order has a promo/coupon. If so, we’ll check if the processing date and time falls within our ‘soon’ timeframe or whether the date has passed or not. Let’s start by getting the CartCouponsComponent from the order, after we check if the order has the component.

var couponsComponent = order.GetComponent();

The CartCouponsComponent is a part of the ‘Coupons’ plugin, so we will need to add a reference the Sitecore.Commerce.Plugin.Coupons library. This component is where all promos associated with the order will live. If there are multiple promos applied to the order, we will want to loop through the list associated with the component. For the sake of this post, we will assume an order can only have one promo applied, so we will only need to grab the FirstOrDefault.

var cartCoupon = couponsComponent.List.Count > 0 ? couponsComponent.List.FirstOrDefault() : null;

The CartCoupon is returned with an EntityReference property called ‘Promotion’. We will use this entity reference to grab the associated commerce entity from commerce engine to validate the promo.

Promotion promo = await _findEntityPipeline.Run(new FindEntityArgument(typeof(Promotion), $"{cartCoupon.Promotion.EntityTarget}", false), context) as Promotion;

The ‘Promotion’ class is a member of the ‘Promotions’ plugin, so be sure to add a using statement for Sitecore.Commerce.Plugin.Promotions. If the promo exists in the system, we will verify if the current date falls within our ‘soon’ time range or if the promo has expired. It’s a best practice to store the value for the amount of days for the threshold in a ‘Policy’, but for the sake of this post we’ll just subtract 7 days from the promo expiration date and verify if the current date falls within our range.

var dateToWarn = promo.ValidTo.AddDays(-7);
if (DateTime.Now < promo.ValidTo && DateTime.Now >= dateToWarn)

Note that we validate against the ‘ValidTo’ property of the promo. If the validation returns true, proceed with the necessary functionality to send an email to the customer. If the current date has passed the ‘ValidTo’ date, proceed with removing the promo by returning the order and continuing to the remove block in the pipeline.

Remove Expired Promo Block

To remove the promo, we will execute the RemoveCouponFromCartPipeline which is a member of the ‘Coupons’ plugin. This pipeline accepts a CouponArgument composed of a ‘cart’ and ‘couponCode’. We will need to get a temporary cart based on the order, but first we will need to place the order on hold. Once the order is in the onHold status, we will execute the GetOnHoldOrderCartCommand, passing the order with the new status.

var heldOrder = await _holdOrderPipeline.Run(orderId, context.ContextOptions);
var tempCart = await _getOnHoldOrderCartCommand.Process(context.CommerceContext, heldOrder);

Now that we have our cart, we are ready to run our pipeline, passing the tempCart and couponId.

var removeCouponResult = await _removeCouponFromCartPipeline.Run(new CouponArgument(tempCart, couponId), context);     

This will return a Cart with the coupon removed. We will want to set the tempCart to equal the result cart and persist it. We may need to recalculate any totals for external systems, but the new total amount for the order will be reflected in the tempCart.Totals.GrandTotal.Amount. If all is successful, you will release the hold on the order, return it, and continue to the next order in the pipeline.

tempCart = removeCouponResult;
var newTempCartGrandTotalAmount = tempCart.Totals.GrandTotal.Amount;
var cart = (await _persistEntityPipeline.Run(new PersistEntityArgument(tempCart), context)).Entity as Cart;

var resultOrder = await _releaseOnHoldOrderPipeline.Run($"{orderId}", pipelineContextOptions);
return resultOrder;

I hope this Sitecore Commerce tutorial has been helpful. For more tutorials and thought leadership on Sitecore Commerce from our XCentium team, [please visit this link]( Experience Commerce 9 "More about Sitecore Experience Commerce 9").