Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Zac 239 posts 541 karma points
    Jun 24, 2016 @ 16:38
    Zac
    1

    Apply discounts before billing address?

    Hi,

    We were curious if it would be possible to apply a discount before setting the billing address.
    After reviewing the code, it appears that discounts can only be applied to an invoice and an invoice can only be prepared after a billing address is set. Is this correct? Are there any ways around this?

    Sidenote: By the way, the offers functionality is awesome. We have some pretty complex volume automatic discounts that we figured would be custom code, but your coupon constraints let us set this up without an issue.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jun 27, 2016 @ 21:46
    Rusty Swayne
    1

    Thanks Zac,

    I've done it before, but you need to know what you're doing with the constraints. Before you are able to generate an invoice, some of the constraints are invalid (taxes and sometimes shipping). You might also need to pay attention to whether or not you are dealing with an anonymous customer.

    The validation of the offer is actually the most complicated part as there could be more than one coupon (if allowed). This works first in first out, meaning the first coupon that validates sticks which may cause subsequent ones to invalidate due to constraints.

    The offer code is really the only thing that is stored once the offer is validated. Then, when the customer tries to validate the next one, the first is added (which will be successful) and then the next is "attempted" to be added - which may fail.

    So if you know that your coupons will not have constraints that require an invoice being created, you can actually store the list of offer codes and try to validate the coupon against the basket or the CheckoutManager.Context.ItemCache (copy of the basket used to generate the invoice). I'd use the ItemCache if it's an option for you since it will automatically invalidate if any basket operations are performed. This is important, since quantities and the actual product items can really affect the reward and also constraints.

    I'd be happy to give you more pointers if you let me know how you need it to work ...

  • Zac 239 posts 541 karma points
    Jun 27, 2016 @ 23:52
    Zac
    0

    Thanks Rusty.

    I'm going to circle back with the client now that I have more info. We can probably convince them to leave the discounts as an option that can only be set after the billing address is set.

    All of our customers are anonymous in this use-case and we don't have any constraints that require an invoice to be created.

    I'll let you know if we have more questions.

    Thanks

  • Chris 47 posts 127 karma points
    Sep 16, 2016 @ 17:16
    Chris
    0

    Hi Rusty,

    This is also a requirement for our project. We need to apply Marketing rules automatically at the Basket screen as quantities are changed (similar to TeaCommerce's implementation).

    Your original comment is helpful but we're wrestling with a bunch of different entities and trying to determine which ones are needed to complete this workflow (LineItem visitors, ExtendedDataCollections, etc...). It looks like StoreBasketController.cs is where the AJAX calls are going when the user edits the quantity textboxes so that's where we're digging at the moment.

    If you have any additional details on how to accomplish this I would really appreciate it!

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 17, 2016 @ 15:25
    Rusty Swayne
    0

    Hi Chris,

    Little embarrassing, but TBH - I'm not very familiar with TeaCommerce's implementation so you may need to describe how that works for me =)

    But I know it's possible to wrangle the functionality into the basket right now - and it gets easier if you can make the distinction between a discount that is added to the basket and a coupon added later. What I mean by this, is if by the time you get to the checkout, the coupon validation does not have to care about the discount that was added to the basket, the processes could work independently.

    All that has to happen to make this work is to actually ADD the discount line item to the basket rather than save the offer code and have it applied each time the invoice is prepared. Sort of confusing, I understand so I'll try to state it a different way:

    Coupon discounts are really never added to the item cache (the copy of the basket in the checkout manager). Instead, a list of the "approved" offer codes is stored in a simple list of strings in the checkout manager. Each time the PrepareInvoice method is called, an invoice builder is used to execute a chain of tasks (which allows people to modify the way an invoice is created). One of the tasks: https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Workflow/InvoiceCreation/CheckoutManager/AddCouponDiscountsToInvoiceTask.cs

    ... iterates through all of the offer codes and "applies" the coupon (which revalidates it) ulitimately getting a reward - which is a discount line item. The discount line item at that point is added to the invoice. It is important to note the invoice is never saved by the PrepareInvoice method so that the summary can be presented throughout the checkout process and we don't wind up with non sequential invoice numbering.

    If you notice, in the task, the validation ONLY considers offer codes in the list saved in the checkout manager and does not consider any of the existing line items that have already been copied to the invoice in the previous task:

    https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Core/Chains/InvoiceCreation/CheckoutManager/ConvertItemCacheItemsToInvoiceItemsTask.cs

    What this means if you put a discount line item into the basket (or checkout manager) item cache BEFORE the call to PrepareInvoice, it's never considered for the validation.

    I did a quick video to provide an example:
    https://drive.google.com/file/d/0B0o-8ZqA1sebN0VJU2hHZ3Y0SWs/view

    In this demo, I have two discounts.

    • The first is a "manual" discount for $2.00 and is added to the basket.
    • The second is a coupon discount for $1.00 added in the default way.

    The "manual" discount I added to the basket is just done if the view - no magic, just quick and dirty for the demo. This would be the one added in your basket controller. The demo is not doing any validation - so if you need to run it through the validation (e.g. like the coupons) you will have to do a bit more work.

    Unless you are creating your own constraints or rewards you probably won't have to worry so much about the LineItem visitors. However, if you need to do a check other line items in the basket to qualify your discount (or alter discounts already applied in the basket due to quantity changes) you might find them pretty nifty. https://en.wikipedia.org/wiki/Visitor_pattern

    Every line item collection in Merchello has an Accept method with takes an ILineItemVistor. In a nutshell, you create a class that implements ILineItemVistor which requires you to define a single method - Visit(ILineItem lineItem)

    You do whatever checks (or changes) to the line item passed to the Visit method. When you pass your class to the Accept method, the line item container will iterate through every one of it's line items and pass them one at a time to the visitors Visit method. In a nutshell, you don't have to think about all of the loops within loops within loops ... you can write code to deal with the line item and let the pattern take care of getting you there.

     public class MyQuantityGreaterThan2Visitor : ILineItemVisitor
     {
            private readonly List<ILineItem> _matches = new List<ILineItem>();
    
            public IEnumerable<ILineItem> Matches 
            {
                   get 
                   {
                          return _matches;
                   }
            }
    
           public void Visit(ILineItem lineItem) 
           {
                  if (lineItem.Quantity > 2) _matches.Add(lineItem);
           }
     }
    
    // usage
    var basket = CurrentCustomer.Basket();
    
    var visitor = new  MyQuantityGreaterThan2Visitor();
    
    basket.Accept(visitor);
    
    // now the visitor has found all the line items with quantities great than 2
    foreach(var item in visitor.Matches)
    {
         // do something with the item
    
    }
    

    I think this need has popped up enough times that we should probably consider making it a feature request in the issue tracker. Maybe decorate the constraints with another attribute that describes if they are valid to be used with a basket. I'll have to think about that a bit. It won't be as straight forward as one might think due to the way the validation works at the moment.

  • Chris 47 posts 127 karma points
    Sep 17, 2016 @ 18:16
    Chris
    0

    Thanks for the fantastic reply, Rusty. I need permission to access the video when you have a minute.

    We did create a test Visitor to modify the line item but it didn't get called when the user adjusted the quantity in the textbox. Is that by design or did we implement it wrong?

    We are essentially trying to create quantity price breaks for each product so we need to recheck Coupon rules (or our own custom "discount" script) each time the quantity is changed. Does this mean we also have to hook into the AJAX call responsible for updating the price on the front-end (looks like it might be StoreBasketController.cs)?

    TeaCommerce has the same Marketing section concept where you can create offers with constraints such as quantity, product type, etc... The difference is that the discounts are applied automatically throughout the website whenever the cart is modified.

    So, for example, if there is a price-break when buying 10 or more blue hats and the user adds that many to their cart, the price will automatically be adjusted. If they remove one hat from the cart down to 9 the price is recalculated and will return to retail cost because the quantity rule isn't met.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 18, 2016 @ 15:51
    Rusty Swayne
    0

    Hey Chris,

    Yeah - sorry about the access issue. I was going too quickly and used auto-fill for the form fields =) Shoe string budget mean I only have access to free screen casting software so I can't edit or blur.

    The visitor won't get called automatically. You would have to put that into you controller function or some service the controller uses when it handles the quantity update. Honestly nothing magical.

    Merchello is actually not too far off from being able to apply the marketing rules to the basket - it is setup that each of the offer providers resolves so building another provider to run those rules through (even the same ones the coupon currently uses) is just a matter of getting the time - or some documentation so people can start experimenting and giving us PRs.

    One of the use cases was a "flash sale" where someone could tweet everything off 20% for the next 30 minutes and it would automatically adjust all of the prices for matching products all over the store front end. All of the nuts and bolts are there to do this - but it was never implemented due to other priorities.

  • Chris 47 posts 127 karma points
    Sep 18, 2016 @ 20:14
    Chris
    0

    Hi Rusty,

    Yes, it would be great to be able to adjust line item prices according to specific business conditions, but while taking advantage of the rules and constraints work you've already put into the Marketing section.

    Maybe there is a way to separate pricing rules and logic from the methods developers can use to APPLY those rules (ie. automatic quantity discount, coupon, flash sale, etc...).

    We're still very new to Merchello so determining all the dependencies and steps required to overhaul the checkout process and apply a discount from the Marketing section at the Basket phase is a bit daunting. If you have any step-by-step suggestions for how we might be able to accomplish that with the existing 2.2.1 build I'd love to hear it.

    Thanks again.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 19, 2016 @ 15:31
    Rusty Swayne
    0

    Hi Chris,

    I think my first step to write up some technical documentation about the Offer Providers and the data modifier "filters" in Merchello.

    Offer Providers are basically classes that compose a the constraints and rewards into groups. Once composed the provider can be used to perform actions (generally validate and create a reward) for whatever has been configured. A coupon offer is just a single implementation of on Offer provider.

    The Data Modifiers thus far are just used to change pricing based on taxation values - e.g. include VAT in pricing. However, the concept is to add additional tasks in that chain to run the items through an offer provider that is intended for that usage - like the flash sale.

    There will probably be some minor kinks to work out to apply the concept to the basket, but my guess is they would be pretty straight forward. Again, I think documentation is the first step and then writing some code that is just abstract enough to allow tweaks for implementation specifics.

  • Chris 47 posts 127 karma points
    Sep 19, 2016 @ 16:52
    Chris
    0

    Hi Rusty,

    Definitely makes sense, but since we're working on a client project we don't have much time to wait for documentation =) It sounds like there is already a good level of abstraction for the Offer process so hopefully this can be developed in the near future. I'm sure its a key feature that the community would love to have.

    So for the short-term if we need to implement our own business logic should we keep our code in the Controller that handles the basket page and then just ensure each line item price is adjusted to our needs? Its my understanding that the values will then become part of the ExtendedData collection and then carried throughout the rest of the Checkout process per Merchello's default workflow.

    Thanks again,

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 19, 2016 @ 17:23
    Rusty Swayne
    0

    Hey Chris,

    If you would, make a little helper or service (maybe a visitor =) ) to keep the code outside of your controller. That way, I can look at what you did and possible bring as similar of a process into the Core if it makes sense and hope it minimizes the updates directly in the controller required when we get to it.

    You will need to either disable or replace the price validation task in the merchello.config before you do this (or Merchello will keep reverting your changes to match the catalog price). This is in place for items saved in a basket or wishlist and purchased at a later date to make sure that pricing is correct.

    https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.FastTrack.Ui/App_Plugins/Merchello/config/merchello.config#L189

    To get the idea (NOT TESTED)

     public class BasketDiscountsVisitor : ILineItemVisitor
     {
             private Lazy<MerchelloHelper> _merchello = new Lazy<MerchelloHelper>(() => new MerchelloHelper());
    
             public void Visit(ILineItem lineItem) 
             {
                    // whatever rule(s) you need
                    // You may opt to use multiple visitors (one for each rule - but it becomes more complex if the rules start getting contingent on one another)  
                    if (lineItem.LineItemType == LineItemType.Product && lineItem.Quantity > 10)
                    {
                          var product = _merchello.Value.Query.Product.TypedProductContent(lineItem.ExtendedData.GetProductKey()));
                          var alteredPrice = product.OnSale ?
                                      product.SalePrice * .9 : product.Price * .9;
    
                          lineItem.Price = alteredPrice;
                          // not required but could be useful
                          lineItem.ExtendedData.SetValue("mydiscountApplied", ".9");
                    }
             }
     }
    
  • Chris 47 posts 127 karma points
    Sep 19, 2016 @ 19:03
    Chris
    0

    Thanks, Rusty. We'll take a look at creating a Visitor but remember that we need to adjust the price when the user changes the Quantity textbox as well. Our test Visitor only got called once as the page was rendering. How can we also hook it into the AJAX response without modifying the Merchello Core?

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 19, 2016 @ 19:34
    Rusty Swayne
    0

    You won't have to change anything in the Merchello Core.

    If you are using a straight FastTrack Implementation (which I think you were) you can create your own basket controller that overrides the UpdateBasket method only.

    (Assuming you don't need any more customization than the UpdateBasket and you like the other functionality)

     [PluginController["MyPlugin"]
     public class MyBasketController : StoreBasketController 
     {
    
        /// <summary>
        /// Responsible for updating the quantities of items in the basket
        /// </summary>
        /// <param name="model">The <see cref="IBasketModel{TBasketItemModel}"/></param>
        /// <returns>Redirects to the current Umbraco page (generally the basket page)</returns>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public override ActionResult UpdateBasket(TBasketModel model)
        {
            if (!this.ModelState.IsValid) return this.CurrentUmbracoPage();
    
            // The only thing that can be updated in this basket is the quantity
            // CHANGE THIS STUFF and use your VISTOR
            foreach (var item in model.Items.Where(item => this.Basket.Items.First(x => x.Key == item.Key).Quantity != item.Quantity))
            {
                this.Basket.UpdateQuantity(item.Key, item.Quantity);
            }
    
    
            this.Basket.Save();
    
            // You can also override this if you need to 
            // do whatever.
            return this.HandleUpdateBasketSuccess(model);
        }
    } 
    
  • Chris 47 posts 127 karma points
    Sep 23, 2016 @ 18:52
    Chris
    0

    Hi Rusty,

    We're having trouble getting our overrides to hook into the default workflow. We started with your MyBasketController source but the UpdateBasket override never gets called (it goes directly to the BasetControllerBase method). Is using the PluginController decoration REQUIRED? We're trying to avoid creating an App_Plugin directory and keep our source in the default Controllers directory, but I'm wondering if that is a problem since we're trying to override a class that works within the FastTrack area.

    Thoughts?

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 23, 2016 @ 21:30
    Rusty Swayne
    0

    I don't thing it would be a problem to not use a PluginController attribute - as long as your views can be found.

    Can you post the code in your

    using(Html.BeginUmbracoForm<...
    
  • Chris 47 posts 127 karma points
    Sep 24, 2016 @ 00:45
    Chris
    0

    We have tried all of the following in our custom BasketForm.cshtml. None of them hit our AJAX override.

    using (Html.BeginUmbracoForm<MyBasketController>("UpdateBasket"){
    
    using (Html.BeginUmbracoForm<MyBasketController>("UpdateBasket", new { area = "" }, new { data_muifrm = "basket" })){
    
    using (Html.BeginUmbracoForm<MyBasketController>("UpdateBasket", new { area = "FastTrack" }, new { data_muifrm = "basket" })){
    
    using (Html.BeginUmbracoForm<MyBasketController>("UpdateBasket", new { area = "Merchello" }, new { data_muifrm = "basket" })){
    

    Including the Area parameter causes the default Base AJAX methods to hook but not our UpdateBasket(StoreBasketModel) override using the class from your previous post.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 24, 2016 @ 01:28
    Rusty Swayne
    0

    what happens when you just post e.g. Not use the AJAX override. It may be you need to change URL in the $ajax call - give going direct a shot to rule out there being a probelem there.

    You can override the endpoint of the ajax call in the merchello.settings.js (something like that - not at a computer at the moment)

  • Chris 47 posts 127 karma points
    Sep 24, 2016 @ 01:34
    Chris
    0

    Clicking the Checkout button just proceeds to the Billing Address page. No particular methods from the BasketControllerBase seem to get hit, but that may be normal. Everything seems to run okay

    It seems like we can't override ANY of the virtual functions that specify a T parameter (ie. AddBasketItem) also goes directly to the controller base and doesn't ever hit our custom Controller but the BasketForm method gets hit from the Html.Action call just fine.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 24, 2016 @ 14:58
    Rusty Swayne
    0

    Is your code somewhere I can see it? DM me.

  • Chris 47 posts 127 karma points
    Sep 24, 2016 @ 15:19
    Chris
    0

    Let me see if I can get you access to the repo. The project is very vanilla Merchello and Umbraco 7.5.2. Here's the current workflow:

    fsBasket.cshtml (in project root Views)

    @Html.Action("BasketForm", "MyBasket")
    

    MyBasketController (in project root Controllers directory, omitted area)

    public class MyBasketController : StoreBasketController
        {
    
            // Runs correctly from fsBasket Html.Action so we know the file is found
            [ChildActionOnly]
            public override ActionResult BasketForm(string view = "")
            {
                return base.BasketForm(view);
            }
    
            // Never gets called when adding an item. AJAX responses go directly to StoreBasketController and BasketControllerBase
            [HttpPost]
            [ValidateAntiForgeryToken]
            public override ActionResult AddBasketItem(StoreAddItemModel model) {
                return base.AddBasketItem(model);
            }
    
            // Never gets called when updating quantity. AJAX responses go directly to StoreBasketController and BasketControllerBase    
            [HttpPost]
            [ValidateAntiForgeryToken]      
            public override ActionResult UpdateBasket(StoreBasketModel model)
            {
                //...
            }               
        }
    

    We also added our own BasketForm.cshtml according to the MyBasketController location above and only changed the using statement for the form. We tried this with and without an area parameter but see the same results where the AJAX seems to only ever return a StoreBasketController object, not our custom MyBasketController:

    using (Html.BeginUmbracoForm<MyBasketController>("UpdateBasket", new { data_muifrm = "basket" }))
        {
           // Default FastTrack source....    
        }
    
  • Chris 47 posts 127 karma points
    Sep 24, 2016 @ 17:14
    Chris
    0

    Rusty,

    I'll bet this is where things derail in the basket.js. Where is this MUI object created?

    var url = MUI.Settings.Endpoints.basketSurface + 'UpdateBasket';
    

    The route getting called is always:

    /umbraco/Merchello/StoreBasket/UpdateBasket

    but I'd imagine we need it to call our own Controller which would be at

    /umbraco/MyStoreBasket/UpdateBasket

    So we need to change the Endpoints basketsurface somewhere?

  • Chris 47 posts 127 karma points
    Sep 24, 2016 @ 18:00
    Chris
    0

    Yep, that was it. There are hardcoded values in merchello.ui.settings.js that define the route endpoints. We changed the one for UpdateBasket and now are hooking into our custom controller class as quantities are updated.

    Rusty, is this the correct process to implement the desired functionality? I want to be sure we aren't hacking our way to a solution. =)

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 24, 2016 @ 22:22
    Rusty Swayne
    0

    Yep - that file is intended to allow you to customize it. Just don't overwrite your changes when you update next :-)

  • Chris 47 posts 127 karma points
    Sep 25, 2016 @ 17:20
    Chris
    0

    Right. Thanks, Rusty.

    In some cases, based on product sku, we'll need to override the default (price * quantity) returned from LineItemBase when calculating TotalPrice.

    What class or location is best to implement this custom pricing/quantity logic? Should it simply go in the UpdateBasket method or is there a deeper model we should inherit, perhaps on the line-item level?

    What else would be helpful is trying to understand the difference between DataModifiers, Visitors and task chains and when is best to use each. The ItemCacheValidation and MerchelloHelperProductDataModifiers chains both sound like they could apply here.

    Thanks again.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 26, 2016 @ 15:32
    Rusty Swayne
    0

    I think the UpdateBasket controller should work fine but it really depends on your application (meaning if you need it to be more modular you might consider doing it in some other class/service and then calling out to that).

    DataModifiers have more to do with displaying product pricing. e.g. Each time a product is rendered, it is run through the chain of data modifiers.

    The validation is sort of the reverse of that. They are there to ensure the pricing and quantity in the basket are valid for checkout. If a customer puts an item into their wishlist (or saved basket) which was on sale and then comes back two weeks later and the item is no longer on sale ... this would prevent them from being offered the sale price two weeks later.

  • Chris 47 posts 127 karma points
    Sep 26, 2016 @ 16:02
    Chris
    0

    Thanks, Rusty. That makes sense.

    We can make changes now via the UpdateBasket method but we might be starting to fight against Merchello's default behavior. We need to create custom LineItems for Products that have more complex pricing than the default (price * quantity) but the problem is if we create an ItemCacheLineItem with LineItemType.Product and a proper SKU it doesn't seem to register as a Product (ie. isProduct is false). Is this where LineItemType.Custom comes into play?

    Cheers.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Sep 26, 2016 @ 18:50
    Rusty Swayne
    0

    I'm not quite following.

    Are you trying to create a second line item for the same product?

    There really is no magic going on. Product data should just be put into the extended data collection: Usually here: https://github.com/rustyswayne/Merchello/blob/merchello-dev/src/Merchello.Web/Models/ContentEditing/ExtendedDataExtensions.cs#L20

  • Chris 47 posts 127 karma points
    Sep 29, 2016 @ 21:59
    Chris
    0

    We just need to do some custom calculations as the basket quantity is updated, which we have accomplished with your help!

    That said, we're stuck now trying to override the HandleUpdateBasketSuccess method as you mentioned. The UpdateQuantityAsyncResponse and UpdateQuantityResponseItem are both declared as internal.

    How do you recommend we interface with those classes in order to include some additional properties in the 'resp' object before it is serialized for use in the Javascript? Our goal is to touch the core as little as possible.

    For example:

    // HandleUpdateBasketSuccess
                        resp.AddUpdatedItems(this.Basket.Items);
                        resp.FormattedTotal = this.Basket.TotalBasketPrice.AsFormattedCurrency();
                        resp.ItemCount = this.GetBasketItemCountForDisplay();
                        resp.CustomLineItemProperty = "Value"; // We would like to do this in our override, but can't due to the internal scope
                        return this.Json(resp);
    
  • Chris 47 posts 127 karma points
    Sep 30, 2016 @ 20:51
    Chris
    0

    For now we have created an override class that replaces the existing UpdateQuantityAsyncResponse and everything seems to function well.

Please Sign in or register to post replies

Write your reply to:

Draft