Wednesday 7 August 2024

OrderCloud Promotions Gotchas

On the OrderCloud project that we're wrapping up we came across a couple of interesting little "features" of promotions which we hadn't anticipated up-front, so I thought I'd share our experience in the hopes that this helps someone else out there.

OrderCloud Promotions

OrderCloud's promotion system is pretty amazing. If you aren't familiar I would have a quick skim through:

  1. Rules-Based Promotion Expressions
  2. Introducing Line Item-Level Promotions
  3. Promotion Enhancements

(which are in the order they were released/published).

As you can see, there are multiple different ways to customise the Promotion's EligibleExpression and ValueExpression to suit your every need (at least, all the examples we required). This could include values such as:

  • Order properties, eg. order.Total
  • Line item properties, eg. item.ProductID
  • Visitor properties, eg. order.FromUser.xp.region

If you're not basing your promotions on the order, line items, or site visitor, I'd (and I'm sure the OC team would) be very interested to know what you're basing them on!

Parallel vs Serial

Call it what you will - but being tech-minded parallel/serial resonates best with me - when combining promotions (particularly percentage-based promos) it is important to consider whether you could like to combine them all before applying the discount, or apply them one at a time.  

It is also important to make use of the Priority property to control which comes first, particularly when combining percentage + fixed values. That is,  total - 10% - $5 is not going to be the same as total - $5 - 10%.  This can all lead to some complex logic and scenarios involving cart level, line item level, auto-applied and manually-applied promotions, so my advice would be:

  1. Keep it as simple as possible, especially for Day 1 / MVP
  2. Get the business to come up with some standards, for example
    • Only 1 cart level promo can be active and auto-applied on any given day
    • Auto-applied promotions are applied first (in Priority order) then manually applied promotions in the order they are applied
      (note: this is the default, and anything else will require removing and re-adding promos)

So back to the parallel vs serial promotion scenario.  Let's consider the following examples:

  • Combined
    Subtotal$100 
    Promotion 110% off
    Promotion 27% off
    Calculation$100 - ($100*0.17)
    Total$83
  • One after the other:
    Subtotal$100 
    Promotion 110% off$100 - $10
    Promotion 27% off$90 - $6.30
    Total$83.70 

As you can see, this can lead to very different results.

By default, if you just do 2 promos with ValueExpression = order.Total * 0.1 and ValueExpression = order.Total * 0.07 for example (for 10% off and 7% off) OrderCloud will combine the promotion percentages and will apply 17% off.  Thanks to the OrderCloud team's assistance (on Sitecore Slack) and releasing a fix, we can now also do:
ValueExpression = (order.Subtotal - order.PromotionDiscount) * .10 for cart level promos or ValueExpression = (item.LineSubtotal - item.PromotionDiscount) * .7 for line items
which will calculate each promotion one after the other (or "stacking" the promotions as they referred to it).

Negative Order Totals

Something which might not immediately be apparent (but will certainly come up in testing) is the fact that there is nothing stopping your promotions from making your order total end up as a negative value!

If you have an order total of $30, and apply a promotion with ValueExpression = 50 (for $50 off) your total will end up as -$20. Not an issue with percentages, but certainly an issue for fixed value promotions.

Unfortunately the OC team replied that this would not be fixed/changed, as there are clients who need this functionality (no doubt there's a lot of use cases which other clients are using that we aren't on this particular project!) however there is a fairly easy workaround: use the min() function supported in OrderCloud expressions. 

We've implemented something like the following:

  • EligibleExpression = order.Total > 0
  • ValueExpression = min(order.Total, 50)
    (for a promo value of $50)

again, just a reminder that min() is only required for fixed values, not for percentages (which, by their very nature, can't take total below $0).

Conclusion

Promotions are a complex beast, and it's highly recommended that you get your BAs (and tech team) across exactly how you want them to function before getting stuck into the implementation; however it's great to know that OrderCloud has a very powerful and easy to use promotion system to support your requirements. While the promotion system may not always function the exact way that you need by default, it provides the features and flexibility that you need to customise it to your project's requirements; and really, at the end of the day, the same goes for OrderCloud in general!

Monday 13 May 2024

OrderCloud Webhook model validation

We had an issue recently where we were seeing (during local testing using VS dev tunnels) that our webhook endpoint was being called but returning a 400 response, indicating that there was a model binding error (using OrderCalculatePayload).  Of course the response is being returned, but as it's being called by OrderCloud it's not actually visible to us :'(

Frustratingly, even bumping up the logging does not actually show you what the issue is, however there is a great bunch of answers in the ASP.NET Core docs issues page which does provide the necessary info. Just chuck this in your Program.cs and either set a breakpoint inside it or check your console logs.  You should be able to see exactly which property of your model (likely xp as it was in our case) is not populated as expected by the webhook call! 

Monday 8 April 2024

OrderCloud .NET SDK logging

Just thought I'd drop a handy little piece of code for when you're running / debugging the OrderCloud .NET SDK locally. If you ever need to see which HTTP calls are being made, and any info about the request URL, body, response, or more importantly errors, you can just add this little snippet to your Program.cs after var app = builder.Build();

Hope this helps someone! Enjoy :)