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.

Every request that changes the price runs four steps inside the same call: Unified Basket Manager applies the change, fills in the item data from product data, prices the basket with the business unit's engine, and stores the calculated result, which is what the response returns.

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

The same basket priced by each engine comes to the same 280.00 SEK with the 2-for-160 sock promotion applied, but only the Checkout Engine returns tax: its tax groups show VAT 25 percent of 56.00, while the Promotion Engine's tax groups are empty.

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

Three prerequisites follow, and each is silent until it fails:

  1. The business unit must not be deleted or closed. A basket for one gets 404 Not Found naming the tenant and business unit.
  2. The business unit must have a country. UBM keeps its own copy of each business unit's country, kept up to date from the business-unit events (see Data sources), and asks Business Unit Management directly when its copy has none. A newly created business unit can therefore be priced straight away. A business unit with no country in Business Unit Management cannot be priced on CHECKOUT: the call returns 400 Bad Request naming the business unit.
  3. 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 none of these prerequisites. It calls a single shared engine and does not need the country code.

Before a CHECKOUT basket is priced, three checks run. A deleted or closed business unit gets 404 Not Found naming the tenant and business unit. Next the business unit needs a country, taken from UBM's own copy or else asked from Business Unit Management; without one, the call returns 400 Bad Request naming the business unit. Last, a Checkout Engine must exist for that tenant and country; without one, the call fails. Only then is the basket priced. PROMOTION needs none of this.

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.

FieldOn CHECKOUTOn PROMOTION
Item identifierYours if you send one; otherwise the catalogue's. Required: if neither exists, the call fails with 400 naming the itemYours if you send one; otherwise the catalogue's. Not required
Regular sales unit priceYours (unitPrice) is stored on the line but not used for pricing, because the Checkout Engine prices from its own catalogueYours if you send unitPrice; otherwise the catalogue's current sales price specification. It prices the line. If neither exists, the call fails with 400
Item nameYours if you send one; otherwise the name the Checkout Engine returnsYours if you send one; otherwise the catalogue's
Purchase priceNot filled inThe catalogue's current purchase price specification; not settable by the caller
Sales unit of measurementNot filled inThe catalogue's

On a PROMOTION business unit, what you send is used and product data fills in what you leave out. A mug line sent with only its item id and a unitPrice of 110.00 gets its name, its GTIN, its purchase price of 48.00 and its unit of measure from product data, and keeps your 110.00 instead of the catalogue's 120.00.

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.

🛑 On a PROMOTION business unit, this is why you should send the price the shopper was shown. If your storefront displayed 110.00 and the catalogue holds 120.00 — because the two are momentarily out of step — sending 110.00 as unitPrice is what keeps the basket agreeing with the page the shopper clicked on. Omit it and the basket prices at 120.00. On a CHECKOUT business unit the Checkout Engine prices from its own catalogue whatever you send.

On a PROMOTION business unit, when the storefront shows a mug at 110.00 but product data still holds 120.00, sending unitPrice 110.00 prices the basket at 110.00, matching the page. Sending no price prices it at 120.00, and nothing detects the mismatch.

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 the line's discount.

A line carries two prices. The socks start from a regularSalesUnitPrice of 90.00, the baseline from your price or the catalogue's; the 2-for-160 promotion makes the actualSalesUnitPrice 80.00, and the discount is (90.00 − 80.00) × 2 = 20.00, leaving a line total of 160.00.

For the line's total, read extendedAmount rather than multiplying actualSalesUnitPrice by the quantity. The unit price is rounded, so the product can be a cent out.

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 specification of the right type. A price that is 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.

A basket priced at 280.00 on Monday evening still reads fine on Tuesday morning, but checkout is refused because the price is more than two hours old. Recalculating prices it again at 300.00, because the sock promotion ended overnight, and checkout then succeeds.

⚠ Omitting the price hands the decision to the catalogue​

On a PROMOTION business unit, 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.