Early Bird and Limited Usage Coupons With Sitecore Experience Commerce


A limited usage coupon discount is an attractive way to increase traffic on your web shop. 

Let us see how we can support the admin users in creating an early bird coupon from Business Tools.

Before I start, here is the link to github for the blocks that I created. 

Here is the high level plan:

Step 1: Modify the IGetEntityViewPipeline to add the new fields to specify max usage of public coupon.

Step 2: Modify the IDoActionPipeline to save the selection as a policy on the coupon.

Step 3: Create new coupon using Business tools, the rest of the logic is already in place OOTB.

 

Step 1: Add a property to view to display coupon max limit.

For full cs file click here

public class GetPublicCouponsLimitedUsageViewBlock : PipelineBlock
    {
        private readonly IFindEntitiesInListPipeline _findEntitiesInListPipeline;
        public GetPublicCouponsLimitedUsageViewBlock(IFindEntitiesInListPipeline findEntitiesInListPipeline)
        : base(null)
        {
            _findEntitiesInListPipeline = findEntitiesInListPipeline;
        }
                
        public override async Task Run(EntityView entityView, CommercePipelineExecutionContext context)
        {
            Condition.Requires(entityView).IsNotNull(string.Format("{0}: The argument cannot be null", Name));
            EntityViewArgument entityViewArgument = context.CommerceContext.GetObject();

            if (string.IsNullOrEmpty(entityViewArgument != null ? entityViewArgument.ViewName : (string)null) ||
                !entityViewArgument.ViewName.Equals(context.GetPolicy().PublicCoupons,
                    StringComparison.OrdinalIgnoreCase) && !entityViewArgument.ViewName.Equals(
                    context.GetPolicy().Master, StringComparison.OrdinalIgnoreCase))
            {
                return entityView;
            }

            var forAction = entityViewArgument.ForAction;

            if (!string.IsNullOrEmpty(forAction) && forAction.Equals(context.GetPolicy().AddPublicCoupon,
                    StringComparison.OrdinalIgnoreCase)
                    && entityViewArgument.ViewName.Equals(context.GetPolicy().PublicCoupons, StringComparison.OrdinalIgnoreCase))
            {
                List properties = entityView.Properties;

                ViewProperty viewPropertyCode = new ViewProperty
                {
                    Name = "Code",
                    RawValue = string.Empty,
                    IsReadOnly = false
                };

                properties.Add(viewPropertyCode);
               

                ViewProperty viewPropertyUsageCount = new ViewProperty
                {
                    Name = "Coupon Usage Limit",
                    RawValue = string.Empty,
                    IsReadOnly = false,
                    IsRequired = false,
                    UiType = "Dropdown"
                };

                var limitUsageCountList = new List();
                limitUsageCountList.Add(new Selection() { Name = "-999", IsDefault = true, DisplayName = "No Limit (Default)" });
                limitUsageCountList.Add(new Selection() { Name = "100", IsDefault = false, DisplayName = "Limit 100 Usages" });
                limitUsageCountList.Add(new Selection() { Name = "200", IsDefault = false, DisplayName = "Limit 200 Usages" });
                limitUsageCountList.Add(new Selection() { Name = "300", IsDefault = false, DisplayName = "Limit 300 Usages" });
                limitUsageCountList.Add(new Selection() { Name = "400", IsDefault = false, DisplayName = "Limit 400 Usages" });
                limitUsageCountList.Add(new Selection() { Name = "500", IsDefault = false, DisplayName = "Limit 500 Usages" });

                viewPropertyUsageCount.Policies = new List()
                {
                   new AvailableSelectionsPolicy()
                  {
                    List =  limitUsageCountList
                  }
                };

                properties.Add(viewPropertyUsageCount);
                entityView.UiHint = "Flat";                            
                return entityView;
            }

            Promotion entity = entityViewArgument.Entity as Promotion;

            if (entity != null)
            {
                EntityView publicCouponsView;

                if (entityViewArgument.ViewName.Equals(context.GetPolicy().Master, StringComparison.OrdinalIgnoreCase))
                {
                    EntityView entityView1 = new EntityView();
                    entityView1.EntityId = (entity != null ? entity.Id : (string)null) ?? string.Empty;
                    string publicCoupons = context.GetPolicy().PublicCoupons;
                    entityView1.Name = publicCoupons;
                    publicCouponsView = entityView1;
                    entityView.ChildViews.Add(publicCouponsView);
                }
                else
                {
                    publicCouponsView = entityView;
                }

                publicCouponsView.UiHint = "Table";
                FindEntitiesInListArgument entitiesInListArgument = await _findEntitiesInListPipeline.Run(new FindEntitiesInListArgument(typeof(Coupon), string.Format(context.GetPolicy().PublicCoupons, (object)entity.FriendlyId), 0, int.MaxValue), context);

                if (entitiesInListArgument != null)
                {
                    CommerceList list = entitiesInListArgument.List;

                    if (list != null)
                        list.Items.ForEach((c =>
                        {
                            Coupon coupon = c as Coupon;
                            if (coupon == null)
                                return;
                            EntityView entityView1 = new EntityView();
                            entityView1.EntityId = entityView.EntityId;
                            entityView1.ItemId = coupon.Id;
                            string couponDetails = context.GetPolicy().CouponDetails;
                            entityView1.Name = couponDetails;

                            EntityView entityViewCoupon = entityView1;
                            List properties1 = entityViewCoupon.Properties;
                            ViewProperty viewPropertyItemId = new ViewProperty();
                            viewPropertyItemId.Name = "ItemId";
                            viewPropertyItemId.RawValue = (coupon.Id ?? string.Empty);
                            viewPropertyItemId.IsReadOnly = true;
                            viewPropertyItemId.IsHidden = false;
                            properties1.Add(viewPropertyItemId);

                            List properties2 = entityViewCoupon.Properties;
                            ViewProperty viewPropertyCode = new ViewProperty();
                            viewPropertyCode.Name = "Code";
                            viewPropertyCode.RawValue = (coupon.Code ?? string.Empty);
                            viewPropertyCode.IsReadOnly = true;
                            properties2.Add(viewPropertyCode);


                            List propertiesUsage = entityViewCoupon.Properties;
                            ViewProperty propertiesUsageProperty = new ViewProperty();
                            propertiesUsageProperty.Name = "Coupon Usage Limit";
                            var limit = ((LimitUsagesPolicy)coupon.Policies.Where(x => x is LimitUsagesPolicy).FirstOrDefault()).LimitCount;
                            var stringLimit = limit.ToString();
                            propertiesUsageProperty.RawValue = stringLimit;
                            propertiesUsageProperty.IsReadOnly = true;
                            propertiesUsage.Add(propertiesUsageProperty);


                            publicCouponsView.ChildViews.Add(entityViewCoupon);

                        }));
                }
            }

            return entityView;
        }
    }

 

SIDE NOTE: See how dropdowns are added to the business tools. (AvailableSelectionPolicy and the ViewProperty type)

Step 2: Modify the IDoActionPipeline to save the selection as a policy on the coupon.

For full cs file click here

[PipelineDisplayName("Coupons.block.DoActionAddPublicCouponsLimitedUsageBlock")]
    public class DoActionAddPublicCouponsLimitedUsageBlock : PipelineBlock    
    {
        private readonly AddPublicCouponCommand _addPublicCouponCommand;
        private readonly IFindEntityPipeline _findEntityPipeline;
        private readonly IPersistEntityPipeline _persistEntityPipeline;

        public DoActionAddPublicCouponsLimitedUsageBlock(AddPublicCouponCommand addPublicCouponCommand, IFindEntityPipeline findEntityPipeline, IPersistEntityPipeline persistEntityPipeline)
          : base(null)
        {
            _addPublicCouponCommand = addPublicCouponCommand;
            _findEntityPipeline = findEntityPipeline;
            _persistEntityPipeline = persistEntityPipeline;
        }

        public override async Task Run(EntityView entityView, CommercePipelineExecutionContext context)
        {
            int limitValue = -999;
            string strCode = string.Empty;
            string regClassCode = string.Empty;
                        
            EntityView entityView1 = entityView;
            if (string.IsNullOrEmpty(entityView1 != null ? entityView1.Action : null) || !entityView.Action.Equals(context.GetPolicy().AddPublicCoupon, StringComparison.OrdinalIgnoreCase) || (!entityView.Name.Equals(context.GetPolicy().PublicCoupons, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(entityView.EntityId)))
                return entityView;

            Promotion promotion = context.CommerceContext.GetObject(p => p.Id.Equals(entityView.EntityId, StringComparison.OrdinalIgnoreCase));
            if (promotion == null)
                return entityView;

            ViewProperty code = entityView.Properties.FirstOrDefault(p => p.Name.Equals("Code", StringComparison.OrdinalIgnoreCase));

            if (string.IsNullOrEmpty(code != null ? code.Value : null))
            {
                strCode = code == null ? "Code" : code.DisplayName;
                string result = await context.CommerceContext.AddMessage(context.GetPolicy().ValidationError, "InvalidOrMissingPropertyValue", new object[1]
                {
                    strCode
                }, "Invalid or missing value for property 'Code'.");
                return entityView;
            }
                        
            ViewProperty limitViewProperty = entityView.Properties.FirstOrDefault(p => p.Name.Equals("Coupon Usage Limit", StringComparison.OrdinalIgnoreCase));

            if (string.IsNullOrEmpty(limitViewProperty != null ? limitViewProperty.Value : null))
            {
                string errorCode = limitViewProperty == null ? "Coupon Usage Limit" : limitViewProperty.DisplayName;
                string result = await context.CommerceContext.AddMessage(context.GetPolicy().ValidationError, "InvalidOrMissingPropertyValue", new object[1]
                {
                    errorCode
                }, "Invalid or missing value for property 'Code'.");
                return entityView;
            }

            if (limitViewProperty.Value != null)
            {
                bool isSuccess = int.TryParse(limitViewProperty.Value, out limitValue);
                if (!isSuccess)
                {
                    string errorCode = code == null ? "Coupon Usage Limit" : limitViewProperty.DisplayName;
                    string result = await context.CommerceContext.AddMessage(context.GetPolicy().ValidationError, "InvalidOrMissingPropertyValue", new object[1]
                    {
                    errorCode
                    }, "Invalid or missing value for property 'Coupon Usage Limit'.");
                    return entityView;
                }
            }

            CommerceContext commerceContext = context.CommerceContext;


            string couponCode = code != null ? code.Value : null;            

            Promotion promotion2 = await _addPublicCouponCommand.Process(commerceContext, promotion, couponCode);

            string couponId = $"{CommerceEntity.IdPrefix()}{couponCode}";

            var couponData = await _findEntityPipeline.Run(new FindEntityArgument(typeof(Coupon), couponId, false), context);

            if (couponData != null && couponData is Coupon)
            {
                var coupon = couponData as Coupon;
                if (coupon != null)
                {
                    coupon.Policies = new List()
                        {
                          new LimitUsagesPolicy()
                          {
                            LimitCount = limitValue
                          }
                        };                    

                    PersistEntityArgument persistEntityArgument = await _persistEntityPipeline.Run(new PersistEntityArgument((CommerceEntity)coupon), context);
                }
            }

            return entityView;

        }
    }

 

DON'T FORGET TO MAKE CHANGES IN CONFIGURESITECORE.CS !! It is available on github here

 

Step 3: Create new coupon using Business tools, the rest of the logic is already in place OOTB.

 

 

 

 

When the order is submitted using the public coupon, the coupon count is updated on the public coupon.

 

When the coupon is added to cart, there is a validation already to check the LimitUsagePolicy on the coupon and the current usage count of Coupon

 

You can also make the coupon restricted per user using the LimitUsersPolicy.

Feel free to ping me on Sitecore slack(kautilya.prasad) or twitter for any feedback.

 

View more blogs and tutorials about Sitecore Commerce

Learn about our Sitecore Commerce work.

 

SEARCH ARTICLES