Sitecore OrderCloud Documentation

docs

Portal login

Rules-Based Promotion Expressions

Published by Todd Menier on September 20, 2018

Last updated on June 26, 2024

Writing custom rule-base expressions for your order promotions may seem intimidating at first, but once you get the hang of it you'll realize there are virtually limitless possibilities which provide the flexibility to meet almost any business need. In fact, custom order promotions are a great example of how our core value Flexibility over Features informs everything we build (and don't build) into the platform. We've found that allowing developers to define their own custom if-then statements is incredibly robust and solves more problems than providing a limited set of non-customizable promotions out-of-the-box.

Promotions Overview

Every promotion requires two rule expressions, an EligibleExpression, which evaluates the current state of the order and returns true or false indicating whether the promotion can be applied to the order, and a ValueExpression, which evaluates the order and returns a monetary value, which is then subtracted from the order subtotal.

There are several properties that you can include in both expressions to build your custom promotion: order supports the same properties as the Order model returned from v1/orders API endpoints, including xp. items supports the following functions:

  • items.any()

    • true if any item on the order matches filter

  • items.all()

    • true if all items match filter

  • items.quantity()

    • returns sum of line item quantities matching your specified condition

  • items.count()

    • returns number of line items on the order matching your specified condition

  • items.total()

    • compare result to a dollar amount

  • product.incategory()

    • true if a product is directly assigned or the specified category

  • product.inparentcategory()

    • true if a product is assigned to the specified category or any of it's children, at any depth

A single property supports in() (allows rule engine to check if a property value exists in the comma delimited list.)

Array properties supports the following functions:

  • contains() (allows rule engine expressions to check if a value exists in an array. Both string and number arrays are supported.)

  • count() (returns number of array items matching your specified condition)

  • any() (true if any array item matches filter)

  • all() (true if all array items match filter)

Here are a few examples:

  • order.xp.myarray.contains('value2') (true if Order.xp.myarray contains the value "value2")

  • order.xp.myarray.count() = 3 (true if Order.xp.myarray has 3 items)

  • order.xp.myarray.any(item = 'four') (true if Order.xp.myarray has item = 'four')

  • order.xp.Tags.all(item = 'tag*') = true (true if Order.xp.Tags all items start with 'tag')

  • items.any(Product.xp.Tags.contains('value2') (true if a lineitem contains a product where Product.xp.Tags contains the value "value2")

  • item.Product.xp.NumberArray.contains(23) (true if a lineitem contains a product where Product.xp.NumberArray contains the value 23)

  • item.product.xp.myarray.any(item = 20) (true if a lineitem has a product where Product.xp.myarray has any item = 20)

  • item.product.xp.Tags.count(item = 'tag*') = 3 (true if a lineitem has a product where Product.xp.Tags has 3 items that start with 'tag')

The following operators are all supported in rule engine expressions:

  • Comparison: = , < , >, <=, >=

  • Logical: and, or and not

  • Mathematical:+, -,*, / and %

Additionally, you can wrap any of your expressions in these functions:

  • min(...)

    • returns the smaller of the two arguments

  • max(...)

    • returns the larger of the two arguments

  • ifs(...)

    • Returns a value based on evaluating a number of conditions, returning a default if none are true

    • The last parameter in the expression is used as the default

Limiting Promotion Eligibility

Can only be used on Promotions where LineItemLevel is true.

  • ItemLimitPerOrder

    • Positive integer that defines the maximum number of items a given promotion will be applied to

    • ValueExpression will be the discount applied to each eligible item

    • Cannot be used in conjunction with QuantityLimitPerOrder

  • QuantityLimitPerOrder

    • Positive integer that defines the maximum quantity of eligible line items

    • ValueExpression will be the discount applied to each eligible quantity

    • Cannot be used in conjunction with ItemLimitPerOrder

  • ItemSortBy

    • Comma delimited string of line item properties

    • Sort descending by using !

    • Supports sorting on xp

    • If no value is provided, items will be sorted by the default value which is DateAdded ascending

  • Sample use case: 30% off the least expensive 3 items if a user spends at least $50

1{
2 "Code": "30OFF",
3 "LineItemLevel": true,
4 "ItemLimitPerOrder": 3,
5 "ItemSortBy": "LineSubtotal",
6 "EligibleExpression": "order.Subtotal >= 50",
7 "ValueExpression": "item.LineSubtotal * .3"
8}

Examples

$10 off any order greater than $50:
EligibleExpression: "order.Subtotal > 50"
ValueExpression: "10"

Free Shipping when you spend $60:
EligibleExpression: "order.Subtotal >= 60"
ValueExpression: "order.ShippingCost"

BOGO (limited to 1 free item):
EligibleExpression: "items.quantity(ProductID = 'ABC') > 1"
ValueExpression: "items.total(ProductID = 'ABC') / items.quantity(ProductID = 'ABC')"

$5 off the order total when any line item has a given product ID:
EligibleExpression: "items.any(ProductID = '123')"
ValueExpression: "5"

5% off all line items with a product with ID in the given list:
EligibleExpression: "item.ProductID.in('ID1', 'ID2', 'ID3')"
ValueExpression: "item.LineSubtotal * .05"

15% off all line items with a product assigned to a given CategoryID:
EligibleExpression: "item.product.incategory('Bikes')"
ValueExpression: "item.LineSubtotal * .15"

10% off when all products are on sale with a maximum promotion discount of $20 (utilizing product xp):
EligibleExpression: "items.all(Product.xp.OnSale = true)"
ValueExpression: "min(order.Subtotal * .1, 20)"

30% off when you buy 10 or more products assigned to a given CategoryID:
EligibleExpression: "items.quantity(product.incategory('GuitarAccessories')) >= 10"
ValueExpression: "items.total(product.incategory('GuitarAccessories')) * .3"

20% off when you buy these 2 products together (no quantity limit):
EligibleExpression: "items.any(ProductID = 'ABC') and items.any(ProductID = 'XYZ')"
ValueExpression: "(items.total(ProductID = 'ABC') + items.total(ProductID = 'XYZ')) * .2"

10% off your entire order when you spend more than $200 in these categories:
EligibleExpression: "items.total(product.incategory('Kitchen')) + items.total(product.incategory('Bedding')) + items.total(product.incategory('Bathroom')) > 200"
ValueExpression: "order.Subtotal * .1"

$50 off line items from a given supplier when you spend more than $100 on that supplier's products:
EligibleExpression: "item.SupplierID = '123' and items.total(SupplierID = '123') >= 100"
ValueExpression: "50 / items.count(SupplierID = '123')"

25% off a user's first order (utilizing user xp):
EligibleExpression: "order.FromUser.xp.FirstOrder = true"
ValueExpression: "order.Subtotal * .25"

10% off orders for all registered users:

EligibleExpression: "not (order.FromUser.ID = 'myDefaultAnonUserID') and order.Subtotal > 0"
ValueExpression: "order.Subtotal * .10"

15% off your entire order when order xp property in the array (utilizing user xp):
EligibleExpression: "order.xp.foo.in('bar','brr','brb')"
ValueExpression: "order.Subtotal * .15"

BOGO (scales with quantity):
EligibleExpression: "items.quantity(ProductID = 'XYZ') > 1"
ValueExpression: "((items.quantity(ProductID='XYZ')/2) - (items.quantity(ProductID='XYZ') % 2 * .5)) * items.total (ProductID='XYZ') / items.quantity(ProductID='XYZ')"

10% off all line items where the Product xp contains a given value:
EligibleExpression: "item.product.xp.Tags.contains('XYZ')"
ValueExpression: "item.LineSubtotal * .10"

15% off all line items where the Product contains a given number of items in an array that start with a given expression:
EligibleExpression: "item.product.xp.Tags.count(item = 'tag') = 3"
ValueExpression: "item.LineSubtotal * .15"

5%, 10% or 15% off discount on products assigned to a given category based on spend threshold between $10-$50:
EligibleExpression: "items.total(product.incategory('A') >= 10 and item.product.incategory('A')"
ValueExpression: "ifs(items.total(product.incategory('A')) >= 50, item.LineSubtotal .15, items.total(product.incategory('A')) >= 30, item.LineSubtotal * .10, item.LineSubtotal * .05)"

Expressions using Order and Item History

$5 off if a user has placed more than four orders in the past year
EligibleExpression: orderhist.count('1Y') > 4
ValueExpression: 5

10% off if a user has ordered a given product 10 or more times in the past 6 months
EligibleExpression: itemhist.quantity('6M', 'Product.ID = {productID}') >= 10
ValueExpression: order.Subtotal * .10

1% of the total of all orders placed in the past month
EligibleExpression: orderhist.count('1M') >= 1
ValueExpression: orderhist.total('1M') * .01

That's a lot of flexibility. What else should I know?

Considerations

  • Both EligibleExpression and ValueExpression are limited to 400 characters each. Use them wisely. If you're bumping up against the character limit, there's a good chance you could optimize your promo expressions in some way.

  • Promotion eligibility is evaluated by the api at the time the promotion is applied, and then again on order submit.

Enhancements Roadmap

Other Resources


Still have questions?
Ask in our Community Channel

Content Powered By
Sitecore Logo

© Copyright 2024, Sitecore OrderCloud®. All rights reserved.

Contact Us
Privacy Policy
Sitecore