Skip to main content

Pricing System

Unchained Engine uses a chain-of-responsibility pattern for pricing calculations. Multiple pricing adapters execute in sequence, each adding, modifying, or discounting prices.

Overview​

Prices are calculated at multiple levels:

DirectorPurpose
ProductPricingDirectorBase product price, taxes, product-level discounts
DeliveryPricingDirectorShipping and handling fees
PaymentPricingDirectorPayment processing fees
OrderPricingDirectorCombines all pricing, applies order-level discounts

Pricing Chain​

Adapters execute in order of their orderIndex (ascending). Lower numbers run first.

Each adapter:

  1. Receives the current calculation state
  2. Can add items to the calculation
  3. Passes control to the next adapter via super.calculate()

Order Index Guidelines​

RangePurposeExamples
0-9Base price calculationCatalog price, ERP integration
10-19DiscountsMember discounts, bulk pricing
20-29Tax calculationVAT, sales tax
30+Final adjustmentsRounding, currency conversion

Pricing Categories​

CategoryDescriptionTypical Use
BASEBase product/service priceInitial price calculation
DISCOUNTPrice reduction (negative amount)Coupons, promotions
TAXTax amountVAT, sales tax
DELIVERYShipping feesDelivery pricing
PAYMENTPayment processing feesCard fees, invoice fees

Price Item Properties​

When adding items to the calculation, each item has:

PropertyTypeDescription
amountnumberPrice in smallest currency unit (cents)
isTaxablebooleanShould tax be calculated on this amount?
isNetPricebooleanIs this a net price (excluding tax)?
categorystringPrice category (BASE, TAX, DISCOUNT, etc.)
metaobjectAdditional metadata

Pricing Sheet​

Access calculated prices via the pricing sheet:

const pricingSheet = await modules.orders.pricingSheet(order);

// Get totals
const total = pricingSheet.total(); // { amount, currency }
const gross = pricingSheet.gross(); // Before discounts
const net = pricingSheet.net(); // After discounts, before tax
const taxes = pricingSheet.taxes(); // Tax breakdown

// Get items by category
const discounts = pricingSheet.discounts();
const delivery = pricingSheet.delivery();
const payment = pricingSheet.payment();

// Sum specific items
const taxableAmount = pricingSheet.sum({ isTaxable: true });
const baseAmount = pricingSheet.sum({ category: 'BASE' });

GraphQL Price Fields​

Query product prices:

query ProductPrice($productId: ID!) {
product(productId: $productId) {
... on SimpleProduct {
simulatedPrice(currencyCode: "CHF", quantity: 1) {
amount
currencyCode
isTaxable
isNetPrice
}
}
}
}

Query cart pricing:

query CartPricing {
me {
cart {
total {
amount
currencyCode
}
items {
total {
amount
currencyCode
}
}
delivery {
fee {
amount
currencyCode
}
}
payment {
fee {
amount
currencyCode
}
}
discounts {
total {
amount
}
code
}
}
}
}

Best Practices​

1. Always Call super.calculate()​

async calculate() {
// Your logic here

// IMPORTANT: Continue the chain
return super.calculate();
}

Returning without calling super.calculate() stops the pricing chain.

2. Handle Currency Properly​

Always work in smallest currency units (cents) to avoid floating-point errors:

// Good
const amount = 1999; // $19.99 in cents

// Bad
const amount = 19.99; // Floating point issues

3. Include Metadata​

Add metadata for debugging and reporting:

this.result.addItem({
amount: 100,
category: 'TAX',
meta: {
adapter: this.constructor.key,
rate: 0.081,
},
});