Skip to main content

How pricing works

When a price-affecting part of the basket changes, UBM assembles the basket, enriches each line from product master data, sends it to a calculation engine, and stores the returned result alongside the basket. The response you get back is that stored result.

Items, coupons and loyalty reprice. Fulfillments, customer and tenders do not — a delivery address, a contact detail or a means of payment does not change what the goods cost. The full split is in Integrating with UBM.

your request product master data (PnP)
│ │
▼ ▼
┌──────────────────────────────────────────────────────┐
│ Unified Basket Manager │
│ 1. apply the change to the input basket │
│ 2. enrich lines: name, identifier, prices, UoM │
│ 3. price it with this business unit's engine │
└───────────────┬──────────────────────┬───────────────┘
│ │
CHECKOUT│ │PROMOTION
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ Checkout Engine │ │ Promotion Engine │
│ price AND tax │ │ price only — │
│ per tenant and │ │ NO tax │
│ country, stateful │ │ stateless │
└─────────┬──────────┘ └─────────┬──────────┘
└───────────┬────────────┘

calculated result, stored
and returned to you

Two engines — and only one of them returns tax

Which engine prices a basket is set per (tenant, business unit). The difference that decides whether an engine is usable is tax, not performance or feature depth:

ValueEngineReturnsCharacter
CHECKOUTCheckout Engine — the engine the point-of-sale itself usesPrice and taxStateful, because it was built for a lane: a transaction stays open while items are added one at a time, the way a cashier scans a sale. UBM holds a calculation session per basket and manages it for you. Deployed per tenant and per country
PROMOTIONPromotion Engine🛑 Price only. No tax. The calculated result comes back with an empty set of tax groupsStateless and multi-tenant. Each call carries the whole basket, so there is no session to hold open or fall out of step

🛑 PROMOTION returns no tax, and for most channels that is a blocker. It is a real, selectable configuration, and it will price a basket correctly including promotions — but every tax group on the calculated result will be empty. It is only viable where the consuming channel does not need tax from UBM, because it derives or applies tax itself. If your webshop expects UBM to return a tax-inclusive basket, PROMOTION will not serve it.

CHECKOUT is what gives the in-store parity guarantee. A basket priced by the Checkout Engine runs through the same rules as the same basket rung up at a till, tax included. On PROMOTION the promotions and prices are calculated but the tax half of that comparison is absent, so do not assume parity without checking which engine a business unit uses — see Configuration.

Because the setting is per business unit, one tenant can run both.

CHECKOUT is the default

A business unit with no engine configuration prices with the Checkout Engine. Nothing has to be provisioned for a new business unit to work, and the same holds if its configuration is later removed — it reverts to the in-store engine rather than stopping.

PROMOTION is therefore always a deliberate choice, made per business unit through customer-controlled configuration — see Configuration.

Resolving the right engine instance

This applies to the CHECKOUT engine only. It is deployed per tenant and per country, so UBM resolves the tenant's instance for the country the business unit sits in, with test tenants routed to their sandbox equivalent.

Two prerequisites follow, and both are silent until they fail:

  1. The business unit's country must be known to UBM. UBM keeps its own cache of business-unit country codes, fed from the business-unit event stream (see Data sources). A business unit whose country has never arrived produces a 404 Not Found naming the tenant and business unit.
  2. A Checkout Engine instance must be provisioned for that tenant and country. UBM does not create it, and an absent instance surfaces as a failed call rather than as a configuration error.

PROMOTION has neither prerequisite — it calls a single shared engine and does not need the country code.

How a line is enriched before pricing

Before a basket reaches either engine, UBM resolves each line against product master data. The rules are worth knowing, because they decide whether the values you send are used:

What you send is used. The catalogue fills in what you omit.

FieldResolution
Item identifierYours if you send one; otherwise the catalogue's. Required on the CHECKOUT path only — if neither exists there, the call fails with 400 naming the item
Regular sales unit priceYours if you send one; otherwise the catalogue's current active sales price specification
Purchase priceYours if you send one; otherwise the catalogue's current active purchase price specification
Item nameYours if you send one; otherwise the catalogue's
Sales unit of measurementYours if you send one; otherwise the catalogue's

Why the identifier matters on one path and not the other: the Checkout Engine identifies items the way a point of sale does, by the code that would be scanned — a GTIN or PLU. The Promotion Engine identifies them by item id. So a CHECKOUT business unit needs an identifier for every line, from you or from the catalogue, while a PROMOTION one does not.

🛑 This is why you should send the price the shopper was shown. If your storefront displayed 80 and the catalogue holds 90 — because the two are momentarily out of step — sending 80 is what keeps the basket agreeing with the page the shopper clicked on. Omit it and the basket prices at 90.

The price you send sets the regular price, not the final one

A line carries two prices in the calculated result, and conflating them is the usual source of "the price I sent is not the price I got back":

FieldMeaning
regularSalesUnitPricethe baseline — the undiscounted price the line starts from
actualSalesUnitPricewhat the line actually costs after the engine has applied promotions, coupons and loyalty

So a supplied price feeds the baseline, and a promotion can still take the line below it. Both of these are true at once: your price was used, and the line costs less than your price. The difference between the two, times quantity, is what the basket reports as its discount.

Discounts are not something you send. They come from promotions and coupons, which the engine applies.

A price specification counts only when it is the current, active specification of the right type. A price that is inactive, superseded, or of another type is ignored rather than treated as a fallback.

A stored price expires after two hours

The calculated result is stored with the timestamp of the operation that produced it, and checkout rejects a result older than two hours:

"Basket price is not calculated for more than 2 hours. Please recalculate the basket."

Call recalculate to refresh it. Recalculate also exists for the case where nothing about the basket changed but the catalogue or the promotions did — it forces a fresh calculation without altering the basket.

This matters for saved baskets. A basket a customer left overnight will price fine on read but fail at checkout until it is recalculated. Recalculating can legitimately change the total, so show the refreshed price rather than the one the customer last saw.

⚠ Omitting the price hands the decision to the catalogue

If you do not send a price, the catalogue's is used — and if your storefront and the catalogue have drifted, the basket will not match what the shopper saw. Nothing detects this: both systems are internally consistent, and UBM has no third opinion to compare against.

The practical rule: send the price you displayed. Falling back to the catalogue is right for items your channel does not price, and a silent mismatch for items it does.