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.
INFO
ComputedDiscounts use a pure runtime - components cannot perform side effects like sending emails, creating actors, or modifying external state. They can only perform calculations based on the order data provided.
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:
//free-shipping.hdiscount
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:
//black-friday-rule.hrule
hook OnOrderCreated
let now = datetime.now
let 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 component
uri: /resources/components/free-shipping.hdiscount
spec:
codeFile: free-shipping.hdiscount
---
# Install promotion-specific rule
uri: /resources/rules/black-friday-promotion
spec:
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 only
target(e => e is Delivery)
// Target specific order lines
target(e => e is OrderLine and e.productNumber = 'SKU123')percentage(target, rate)
Applies a percentage discount to the target:
// 10% off entire order
percentage(order, 10%)
// 100% off shipping (free shipping)
percentage(target(e => e is Delivery), 100%)
// 20% off specific products
percentage(target(e => e is OrderLine and e.category = 'electronics'), 20%)absolute(target, amount)
Applies a fixed amount discount to the target:
// $50 off entire order
absolute(order, 50)
// $10 off each qualifying item
absolute(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 order
absolute(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:
//my-discount.hdiscount
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:
//volume-discount.hdiscount
// Determine discount rate based on order subtotal
let 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:
//volume-discount-rule.hrule
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 email
sendEmail { ... } // Will fail - not available in pure runtime
from nothing
// ✅ GOOD - Pure calculation with discount effect
from 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 rule
param 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-reusable
from order.total >= 500 match
false |> nothing
true |> percentage(target(e => e is Delivery), 100%)Rules configure the parameters:
hook OnOrderCreated
from [{
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 configuration
hook OnOrderCreated
let isPromoActive = checkPromoPeriod()
from isPromoActive match
true |> [{
effect = 'orderCommand'
type = 'createComputedOrderDiscount'
componentId = 'my-discount'
description = 'Promotional Discount'
parameters = {
rate = '10'
}
}]
false |> []
// Discount component is pure calculation
param rate: number
from percentage(order, rate)Handle Edge Cases
Always validate input and handle edge cases:
// Handle empty orders
let 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