When an order is placed in Sitecore commerce, the order goes to pending status. From pending status, an order can either be released or put on hold. OnHold is an interesting status in Sitecore Commerce. You can make changes to the order while it is on-hold.
If you come across a requirement of making changes to an order after it is placed, On-hold status is your best buddy. I had to implement, order updates. The updates were supposed to come from an external system. This is how I approached the whole solution.
- Pending order minion updates the order status to OnHold after the order goes to that external system.
- A new Ops API exposed to the external system(the api works just like any other api and expects an auth token).
- An order update pipeline which updates the order entity.
I will mainly share the details of that pipeline here but before I go to the details of that pipeline, I just want to highlight the main steps you need to do achieve this.
- GetOrderCommand: Gives you the order entity based on the order Id that you get as input.
- GetOnHoldOrderCartCommand: This command gives you a temporary cart to be able to update all the information. When an order is put on hold, SXC attaches a temporary cart with the order entity. This cart contains all the information for that order.
- Delete un-matching lines: If some lines were deleted from original order, we will delete them from the cart.
- Update matching lines: The lines we receive in the input model, would be updated in the cart.
- ReleaseOnHoldOrderCommand: This command converts the temporary cart to order and replaces the actual order.
- Release Order: An optional step, if we want to say, our order is now released.
- GetOnHoldOrderBlock: This block simply gets the order entity using GetOrderCommand and puts it in the args.
- GetTemporaryCartBlock: This block uses GetOnHoldOrderCartCommand to get the temporary cart from the order and puts the temporary cart in the args.
- DeleteMissingOrderLinesBlock: This block deletes the order lines which are not valid anymore.
public override async Task<UpdateOrderArgument> Run(UpdateOrderArgument arg, CommercePipelineExecutionContext context) { Condition.Requires(arg).IsNotNull($"{Name}: The UpdateOrderArgument cannot be null."); var updatedOrder = arg.RequestOrderModel; var originalOrder = arg.Order; context.Logger.LogInformation(EventIds.UpdateOrderLinesStarted, $"{Name}: Started deleting missing order lines for order: {originalOrder.Id}"); var deletedLines = GetDeletedLines(updatedOrder, originalOrder); foreach (var line in deletedLines) { arg.TemporaryOnHoldCart = await DeleteOrderLine(line, arg.TemporaryOnHoldCart, context); } context.Logger.LogInformation(EventIds.UpdateOrderLinesFinished, $"{Name}: Finished deleting order lines for order: {originalOrder.Id}. Total lines deleted: {deletedLines.Count}"); return arg; } private async Task<Cart> DeleteOrderLine(CartLineComponent cartLine, Cart cart, CommercePipelineExecutionContext context) { return await _commerceCommander.Command<RemoveCartLineCommand>().Process(context.CommerceContext, cart, cartLine); }
- UpdateOrderLinesBlock: This block updates the line quantity and other information.
public override async Task<UpdateOrderArgument> Run(UpdateOrderArgument arg, CommercePipelineExecutionContext context) { Condition.Requires(arg).IsNotNull($"{Name}: The UpdateOrderArgument cannot be null."); var updatedOrder = arg.RequestOrderModel; var originalOrder = arg.Order; var cart = arg.TemporaryOnHoldCart; context.Logger.LogInformation(EventIds.UpdateOrderLinesStarted, $"{Name}: Started updating order lines for order: {originalOrder.Id}"); foreach (var updatedOrderLine in updatedOrder.Line) { var orderLine = GetOrderLineByArticleId(originalOrder, updatedOrderLine.ArticleId, context); if (orderLine == null) { var newCartLine = CreateCartLine(originalOrder, updatedOrderLine); arg.TemporaryOnHoldCart = await AddOrderLine(newCartLine, cart, context); } else { arg.TemporaryOnHoldCart = await UpdateOrderLine(orderLine, updatedOrderLine, cart, context); } } context.Logger.LogInformation(EventIds.UpdateOrderLinesFinished, $"{Name}: Finished updating order lines for order: {originalOrder.Id}. Line updated: {updatedOrder.Line.Count}"); return arg; } protected async Task<Cart> UpdateOrderLine(CartLineComponent cartLine, LineItem line, Cart cart, CommercePipelineExecutionContext context) { cartLine.Quantity = line.Quantity; if (!string.IsNullOrEmpty(line.PromoCode)) { var hasAdjustment = cartLine?.Adjustments?.Any(x => string.Equals(x.Name, line.PromoCode, StringComparison.InvariantCultureIgnoreCase)) ?? false; if (!hasAdjustment) { cartLine.Adjustments.Add(new CartLineLevelAwardedAdjustment { DisplayName = line.PromoCode, Name = line.PromoCode, AdjustmentType = context.GetPolicy<KnownCartAdjustmentTypesPolicy>().Discount }); } } return await _commerceCommander.Command<UpdateCartLineCommand>().Process(context.CommerceContext, cart, cartLine); } protected async Task<Cart> AddOrderLine(CartLineComponent cartLine, Cart cart, CommercePipelineExecutionContext context) { return await _commerceCommander.Command<AddCartLineCommand>().Process(context.CommerceContext, cart, cartLine); }
- SaveOrderBlock: This block runs IPersistEntityPipeline to save the changes to the order entity.
- ReleaseOrderBlock: This block is responsible for converting the updated cart to the order and move it to Release status. It uses ReleaseOnHoldOrderCommand which is responsible for converting the temp cart to order and MovePendingOrderToReleasedCommand which then moves the order to Released.
Happy Sitecorians!