Hey Brian!
You are correct in that it won't be fulfilled if the product that fulfills it is in your cart and you're running Evaluate on another product.
Basically what the Evaluate method does is take the variant being sent in, adding it to a in memory cart and then running the entire promotion engine on that cart. So that virtual cart won't have anything to do with your customer's cart.
You could, as a workaround, add it to the cart and then evaluate, but I wouldn't really recommend it. If your cart contains several different promotions, maybe a 20% on order or other 3 for 2 promotions, you can't reliably look at the yielding price after the promotion enginge runs on the virtual cart. The 20% promotion will for example spread out the price on the lineitems.
Not super helpful for now, but I'll give it a thought, it's a valid use case for sure!
When you call IPromotionEngine.Evaluate(contentLink...) it actually creates an in-memory cart with that item, and quantity = 1. It does not care about what in the actually or if a promotion requires 2 items to work (buy at least X ... ). It is used for getting promotions applied to individual item so you can display on the product listing page.
If you want to get promotions which can be applied with conditions, you can build a list of items to belong to their Target(s), and then check if the contentLink is in there. However as you might suspect it can be quite complicated.
Wouldn't a solution be to create the InMemoryOrderGroup yourself and do PromotionEngine.Run()?
I'm thinking:
1. Create an InMemoryOrderGroup
var inMemoryOrderGroup = new InMemoryOrderGroup(market, marketCurrency);
2. Take all the current items in your actual cart, copy their data to new InMemoryLineItem and add these to the InMemoryOrderGroup
foreach (var cartLineItem from currentLineItems) { var inMaremoryLineItem = new InMemoryLineItem(); inMemoryLineItem.Quantity = cartLineItem.Quantity; inMemoryLineItem.Code = cartLineItem.Code; inMemoryLineItem.PlacedPrice = cartLineItem.PlacedPrice; inMemoryOrderGroup.GetFirstShipment().LineItems.Add(inMemoryLineItem); }
3. Add the product from the detail page
var detailPageLineItem = new InMemoryLineItem { Quantity = 1, PlacedPrice = entryPrice, // From where you get the price on the detail page Code = code // From where you get the code on the detail page }; inMemoryOrderGroup.GetFirstShipment().LineItems.Add(detailPageLineItem);
4. Run the promotion engine and see what the resulting RewardDescriptions contain.
var rewardDescriptions = PromotionEngine.Run(inMemoryOrderGroup);
You'd still have the issues I described above with this approach, though. You can do this but "then what?" :D. If the feature is to show the price of the current variant exactly as if it would have been added to cart, then it might work. But there can be confusing cases and if the feature is to only show how much a certain promotion (the Buy Products for Discount from Other Selection) you don't know how much that promotion did on that exact lineitem and you'd be affected by all other promotions on the cart as well.
Also please note that running the promotion engine is by nature not something cheap to do performance wise. You have to content load all the promotions in the system (ought to be cached tho), apply logic to filter out those who arent active etc and then go through each and every promotion to see if it applies or not.
If you, which I assume, run promotions on the cart on page load, this would mean you run it twice. On a page which is highly critical to conversion. I would challenge this requirement with conversion rate argument! Or at least modify the requirement to make it async (i.e. loading the page with promotion banners etc and then after it's loaded spin off a new request to get the price if you've added it to the cart already).
Also, InMemoryLineItem is internal :( So you have the risk of your code breaking on a patch upgrade to Commerce.
Quan, don't you agree that the InMemory classes are valuable to use when doing these kinds of advanced promotion operations? Would you lobby to not make them internal? :)
You promised me you would give me a list of internal APIs you have to use, and why. I can't lobby anything until I see that :)
Haha I did and I started with it but stopped due to prioritations and me realizing it would take quite some time for me to analyze the need for it or if we used it incorrectly... :( But someday™ I hope to finish it!
It looks like that is what the OP wants to do in this case. That is, display the price on the details page "as if the user has already added it to their bag". Adding specifics on which promotion did what would require more work, yeah.
For saved amounts on entries the OP can retrieve the saved amount from the inmemorylineitem:
LineItem.TryGetDiscountValue(x => x.EntryAmount);
If you also want to display savings that do not affect an entry directly like order discounts/shipment discounts, maybe have seperate presentations for these on the detail page. For example a banner saying something like "Adding this item to the cart gives you 20% discount on the order!"
It is my understanding that Run() is what the Evaluate-method already does after it creates the InMemoryOrderGroup. But maybe it's because I'm looking at an older version at the moment.
But yeah, I can agree that it would be better to not have this feature at the moment. Especially if you have many promotions.
Yeah, that's not so good. Especially if the Evaluate()-method stops using InMemory objects itself for it's purposes.
For the OP: They're not internal in the sense that they can't be used, but they're internal in the sense that Epi does not support backwards compatability when you upgrade.
https://world.episerver.com/documentation/class-library/?documentId=commerce/10/3DEEBAB6
So that's something to consider if you go with that approach, you'll need to ensure that it still works after every upgrade.
Anyway, Quan. I'd like to concur with Joel here that it'd be nice if they would be made non-internal. :D
You can always request. We really like to minimize the API surfaces so we don't have the burden for backward compatibilities. So we need to understand which APIs you need to be public, and why.
Hello,
I am having a hard time getting the evaluate method of the promotion engine to work the way I think it does.
The issue I am having is I want to show the discounted price of a product on the detail page as if the user has already added it to their bag. The discount I am using is the built in Buy Products for Discount from Other Selection. The product that enables the discount is already in the bag and the one I am on the detail page for is apart of the other selection, yet no promo is being returned by evaluate. Once I add the item to the cart, the discount kicks in just fine. Below is the code I have now.
Promos always comes back empty.
I know though that evaluate works for other promotions, as we have a custom promotion that checks for if the logged in user is a part of a special group and evaluate returns the right discounted price which I am able to display on the product detail page.
Do I have to temporarily add the item to the cart and then evaluate?
Thanks for any help.
(We are on commerce 10)