Order Discounts
Discounts in Hantera are dynamic, component-based calculations that continuously re-evaluate as orders change. They enable flexible promotion logic that persists throughout an order’s complete lifecycle.
What are ComputedDiscounts?
A ComputedDiscount is an entity attached to an order that calculates discount amounts dynamically using a component. Unlike static discounts, ComputedDiscounts:
- Re-evaluate automatically whenever the order changes
- Persist indefinitely - continue working even after promotions end
- Use pure components - completely side-effect free runtime
- Apply flexibly - can discount entire orders, specific deliveries, or individual order lines
How They Work
When an order changes (items added, quantities updated, delivery methods changed, etc.), all ComputedDiscounts on that order are re-evaluated:
- The component is executed with current order state
- Component calculates the discount effect
- Discount is applied to the order total
This happens automatically - no manual triggering required.
Common Pattern: Rule-Based Promotions
The typical approach is to use a Rule to add ComputedDiscounts to orders, then let the discount component handle the calculation logic.
Example: Time-Limited Free Shipping
Here’s a complete example of a free shipping promotion for Black Friday:
1. Create a generic discount component:
param orderTotalThreshold: number
from order.total >= orderTotalThreshold match false |> nothing true |> percentage(target(e => e is Delivery), 100%)2. Create a rule to add the discount:
hook OnOrderCreated
let now = datetime.nowlet promoStart = datetime.parse('2025-11-29T00:00:00Z')let promoEnd = datetime.parse('2025-11-29T23:59:59Z')
let isPromoActive = now >= promoStart and now <= promoEnd
from isPromoActive match false |> [] true |> [{ effect = 'orderCommand' type = 'createComputedOrderDiscount' componentId = 'free-shipping' description = 'Black Friday Free Shipping' parameters = { orderTotalThreshold = '500' } }]3. Deploy via manifest:
# Install generic discount componenturi: /resources/components/free-shipping.hdiscountspec: codeFile: free-shipping.hdiscount
---# Install promotion-specific ruleuri: /resources/rules/black-friday-promotionspec: codeFile: black-friday-rule.hruleHow It Works
During Promotion (Nov 29, 2025):
- OnOrderCreated rule runs for each new order
- If date is within promotion period, adds ComputedDiscount to order
- Discount component receives
orderTotalThresholdparameter (500) - If order total ≥ 500, applies 100% discount to deliveries
- Customer sees free shipping applied
After Promotion Ends (Nov 30+):
- OnOrderCreated rule no longer adds discount to new orders
- Existing orders keep their ComputedDiscount
- If customer modifies order (adds items, changes address):
- Discount re-evaluates with new order state
- Still provides free shipping if total ≥ 500
- Removes discount if total drops below 500
This pattern ensures:
- Promotions apply correctly during active period
- Existing customers keep benefits after promotion ends
- Discount logic stays accurate even as orders change
- Component is reusable with different thresholds for future promotions
Pure Runtime Constraints
Discount components run in a pure runtime with significant restrictions:
What You CAN Do
- ✅ Access order data via the
ordersymbol - ✅ Access custom parameters passed to the component
- ✅ Perform calculations and logic
- ✅ Use pattern matching and filtering
- ✅ Return discount effects using helper methods (
percentage,absolute,target)
What You CANNOT Do
- ❌ Send messages to actors
- ❌ Send emails
- ❌ Query the graph
- ❌ Access registry
- ❌ Schedule jobs
- ❌ Modify external state
- ❌ Perform any side effects
The pure runtime ensures discounts evaluate quickly and safely, without unintended side effects.
Discount Helper Methods
Discount components use helper methods to construct discount effects:
target(filter)
Targets specific parts of the order for discounting:
// Target deliveries onlytarget(e => e is Delivery)
// Target specific order linestarget(e => e is OrderLine and e.productNumber = 'SKU123')percentage(target, rate)
Applies a percentage discount to the target:
// 10% off entire orderpercentage(order, 10%)
// 100% off shipping (free shipping)percentage(target(e => e is Delivery), 100%)
// 20% off specific productspercentage(target(e => e is OrderLine and e.category = 'electronics'), 20%)absolute(target, amount)
Applies a fixed amount discount to the target:
// $50 off entire orderabsolute(order, 50)
// $10 off each qualifying itemabsolute(target(e => e is OrderLine and e.quantity > 5), 10)order Symbol
The order symbol represents the entire order and can be used as a target:
// Discount entire order by 15%percentage(order, 15%)
// Fixed $100 off entire orderabsolute(order, 100)Note: order is a symbol, not a function - use it directly without parentheses.
Discount Component Signature
Discount components receive parameters and return a discount effect or nothing:
param someParameter: text
// Your calculation logic here
from condition match false |> nothing true |> percentage(order, 10%)Input: Custom parameters (configured when creating the discount via parameters field)
Output: Discount effect (using helper methods) or nothing if no discount applies
Advanced Example: Tiered Volume Discount
Here’s a more complex discount that applies different rates based on order value:
// Determine discount rate based on order subtotallet discountRate = order.subtotal match when order.subtotal >= 10000 |> 15% when order.subtotal >= 5000 |> 10% when order.subtotal >= 1000 |> 5% |> 0%
from discountRate > 0% match false |> nothing true |> percentage(order, discountRate)Add via rule:
hook OnOrderCreated
from [{ effect = 'orderCommand' type = 'createComputedOrderDiscount' componentId = 'volume-discount' description = 'Volume Discount'}]This discount:
- Applies 5% off orders over 1,000
- Applies 10% off orders over 5,000
- Applies 15% off orders over 10,000
- Re-calculates automatically as order value changes
Best Practices
Keep Components Pure
Never attempt side effects in discount components:
// ❌ BAD - Trying to send emailsendEmail { ... } // Will fail - not available in pure runtimefrom nothing
// ✅ GOOD - Pure calculation with discount effectfrom order.total > 100 match false |> nothing true |> percentage(order, 10%)Use Generic Components with Parameters
Create reusable discount components and configure them via parameters:
// ✅ GOOD - Generic "free-shipping" component// Threshold passed as parameter from ruleparam orderTotalThreshold: number
from order.total >= orderTotalThreshold match false |> nothing true |> percentage(target(e => e is Delivery), 100%)
// ❌ BAD - Promotion-specific component// Hardcoded values make it non-reusablefrom order.total >= 500 match false |> nothing true |> percentage(target(e => e is Delivery), 100%)Rules configure the parameters:
hook OnOrderCreatedfrom [{ effect = 'orderCommand' type = 'createComputedOrderDiscount' componentId = 'free-shipping' // Generic component description = 'Black Friday Free Shipping' parameters = { orderTotalThreshold = '500' // Configure threshold }}]Use Rules to Control When Discounts Apply
Let rules handle timing logic, keep discount components focused on calculation:
// Rule handles timing and configurationhook OnOrderCreatedlet isPromoActive = checkPromoPeriod()from isPromoActive match true |> [{ effect = 'orderCommand' type = 'createComputedOrderDiscount' componentId = 'my-discount' description = 'Promotional Discount' parameters = { rate = '10' } }] false |> []
// Discount component is pure calculationparam rate: numberfrom percentage(order, rate)Handle Edge Cases
Always validate input and handle edge cases:
// Handle empty orderslet hasItems = order.orderLines count > 0
from hasItems match false |> nothing true |> percentage(order, 10%)Use Clear Descriptions
The description field appears on invoices and customer communications:
- ✅
"Free Shipping on Orders Over $500","10% Volume Discount","Loyalty Member Benefit" - ❌
"Discount","Promo","Test"
Note: The discountId is auto-generated as a UUID and typically omitted from rules.
See Also
- Components - Learn about components and runtimes
- Rules - React to system events
- Order Actor - Order actor overview
- Order Commands - Commands for managing orders
- createComputedOrderDiscount Command - Command reference