Skip to content

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:

  1. The component is executed with current order state
  2. Component calculates the discount effect
  3. 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:

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.hrule

How 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 orderTotalThreshold parameter (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 order symbol
  • ✅ 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