Overview of the concepts related to product pricing and discounts in Composable Commerce.
Pricing
For example, an SKU can cost €25 in Germany and €30 in Spain. Since the SKU has two Prices defined, the purchase Price for a Customer can depend on the country of purchase. For more information, see Price selection.
For example, you can create a base price for a new mobile phone for Customers in Germany who belong to your Platinum Customer Group. With Product Discounts, you can offer a discounted price for the same combination of dimensions (Germany and Platinum Customer Group). This provides flexibility in being able to utilize the base price together with the discounted price on your web interface, in addition to smooth reporting analytics.
Types of Prices
100
per Product Variant) or stored separately as standalone entities (up to 50 000
per Product Variant). Although a Product can have both types of Prices at the same time, we recommend having only one type of Price for a Product for better performance. The priceMode
of a Product determines the type of Price used for price Selection.Embedded Prices
prices
field. With Embedded Prices, you can store up to 100
Prices for each Product Variant. For more flexibility with pricing your products with higher limits, see Standalone Prices.Standalone Prices
sku
field. With Standalone Prices, you can store up to 50 000
Prices for each Product Variant.External Prices
priceMode
is ExternalPrice
, set the externalPrice
value. The totalPrice
of the LineItem is automatically calculated by multiplying the externalPrice
by the quantity.priceMode
is ExternalTotal
, set the externalTotalPrice
value (unit price multiplied by the quantity). The totalPrice
of the LineItem is set from the value defined in externalTotalPrice
.Your code should be able to find the correct prices to display on your storefront and set the Line Item price when it's added to a Cart.
Consider using external prices in the following cases:
- Pricing data exceeds the limit of
50 000
Prices for a Product Variant. - Pricing data is stored externally in a Product Information Management (PIM).
In such cases, to display the Price on your storefront for a Product Variant or for a Line Item in the Cart, you would need to make API calls to the external system. Alternatively, you can also import your prices into Composable Commerce as Standalone Prices or Embedded Prices. - Pricing data is dynamic and changes frequently or is calculated on the fly based on some external values.
If pricing fluctuates frequently or is calculated based on external factors (like currency conversion rates), you need to obtain the data through API calls to the external system before displaying the prices on your storefront. - Pricing data is stored in some datastore that is colocated with your application code.
In such cases, to display Prices on your storefront or for Line Items in the Cart, you must make database calls rather than external API calls. - Pricing data is stored as Custom Objects.
Although Custom Objects are stored within Composable Commerce and you can access the data through commercetools' API calls, the Prices are still considered external as your code needs to access the data when you need to display or add pricing information into the Cart. Additionally, you cannot take advantage of all the price selection logic used by Embedded and Standalone Prices.
Tiered pricing
Tiered pricing can be useful in bulk-purchasing scenarios to apply a different pricing when a certain quantity of an item is added and ordered from a cart. For example, if a product's base price is €5, when 100 items are added and ordered from the cart, a tiered Price of €3 can be used for each item. Tiered pricing is an alternate way to discount prices without using Product Discounts.
minimumQuantity
is reached, the Price tier is applied for the entire quantity of a Product Variant added as Line Item to a Cart. If no Price tier is found for the Order quantity, the base Price is used.The Price tier is applied per Line Item of the Product Variant. For example, if the same Product Variant appears in the same Cart as several Line Items (which can be achieved by different values of a Custom Field on the Line Items), each Line Item must reach the minimum quantity to get the Price tier.
Both Embedded Prices and Standalone Prices provide support for tiered pricing.
Price selection
-
Product price selection: When displaying a price on a product detail page, the price is determined by the Product Projections API and the Product Projection Search API based on the following parameters:
-
priceCustomerGroup
: maps tocustomerGroup
of Price or StandalonePrice -
priceChannel
: maps tochannel
of Price or StandalonePrice -
priceCountry
: maps tocountry
of Price or StandalonePrice -
priceCustomerGroupAssignments
BETA: maps tocustomerGroup
of Price or StandalonePrice. For more information, see Fallback logic for multiple Customer Groups.Product Projection Search does not support multiple Customer Group prices.
The returned ProductVariantprice
field—added in API responses—contains the Price that matches most of the Price-selection parameters. -
Line Item price selection: When you create a Cart or add a LineItem to a Cart, the price is determined by the Carts API based on the following parameters:
- Cart
currency
: maps tovalue.currencyCode
of Price or StandalonePrice - Cart
customerGroup
: maps tocustomerGroup
of Price or StandalonePrice - LineItemDraft
distributionChannel
: maps tochannel
of Price or StandalonePrice - Cart
country
: maps tocountry
of Price or StandalonePrice - Customer
customerGroupAssignments
BETA: maps tocustomerGroup
of Price or StandalonePrice. For more information, see Fallback logic for multiple Customer Groups.
- Cart
Composable Commerce selects the price for Products and Line Items based on the following fallback logic.
Fallback logic
priceMode
value. You can set the priceMode
when creating a Product; for existing Products, use the Set PriceMode update action. If the priceMode
is not set, APIs use Embedded Prices as they consider the Product to have the Embedded
ProductPriceMode, as a fallback.A Price for a specific currency is selected in the below-mentioned order with Customer Group having the highest priority, followed by channel, and then country:
- Checks for a Price for the Customer Group, channel, and country
- Checks for a Price for the Customer Group and channel for all countries
- Checks for a Price for the Customer Group and country for all channels
- Checks for a Price for the Customer Group for all channels and countries
- Checks for a Price for the channel and country for all Customer Groups
- Checks for a Price for the channel for all Customer Groups and countries
- Checks for a Price for the country for all Customer Groups and channels
- Checks for a Price for any Customer Group, channel, and country
For all the Price-selection steps, Prices with a validity period are checked before Prices without a validity period. If a currently valid Price is found, it is used first.
validFrom
and validUntil
range of Embedded Price or StandalonePrice.Fallback logic for multiple Customer Groups BETA
customerGroup
or Customer customerGroupAssignment
field are considered for the logic. The price selection occurs only between eligible Prices matching a priority of the fallback logic order.discounted
or StandalonePrice discounted
field determines the cheapest price.customerGroupAssignment
array is used. If the price matched from the Customer customerGroupAssignment
and Cart customerGroup
fields are equal, the Price matching the Cart customerGroup
is used.Tiered prices, if any, are ignored when selecting the lowest price for the Product Variant.
The following example shows how a price is selected if a Customer is a member of multiple Customer Groups:
Customer Group | Country | Channel | Price |
---|---|---|---|
Alpha | USA | Zeta | $123 |
Beta | USA | Zeta | $100 |
Delta | * | Zeta | $99 |
* | * | * | $23 |
According to the fallback logic, Composable Commerce first checks for a price for the Customer Group, channel, and country—leaving us with $123 and $100. Composable Commerce selects $100 as it's the cheapest of the two prices. Since the first condition is met, the next checks in the price selection logic are ignored, although other cheaper prices exist.
Scoped Price search
External prices cannot be used in the Product Projection Search endpoint for filtering, faceting, or sorting.
Standalone
ProductPriceMode yields inconsistent results, as only Embedded Prices are taken into account.scopedPrice
field. In case of a partial match, this field is not present.validFrom
date, and search for the current Product Projection.- Embedded Price 1 with
country
:US
,value
: {centAmount
:1000
,currencyCode
:USD
} - Embedded Price 2 with
country
:US
,value
: {centAmount
:800
,currencyCode
:USD
},customerGroup
:B2B
priceCountry
: US
and priceCustomerGroup
: B2C
, are provided in the Product Projection Search request. Since these Price-selection parameters only match partially, the ProductVariant only contains the price
field with 'Embedded Price 1' defined for the Product Variant, and not the scopedPrice
field.Discounts
Discounts can assist in creating efficient pricing solutions for your business. For example, one best practice would be to take advantage of Product Discounts to display sale prices for your Products instead of using the available pricing solutions, which are better suited for storing base prices.
Typically, B2C business models have simple base pricing structures and rely on frequent promotional activities to drive sales. In contrast, B2B business models often have more complex pricing that involves high precision (sub-cent) prices and tiered pricing. Discounts play a less significant role in B2B as purchasing decisions are generally made by groups, reflecting a more structured decision-making process.
Composable Commerce offers the following types of Discounts:
Product Discounts
Product Discounts can be used for product or catalog discounts to display a discounted Price for your Products before they are added to a Cart, for example, on product detail pages (PDPs) or product listing pages (PLPs).
Only one Product Discount applies to any Price of a Product Variant at any given time. This means you can use different Product Discounts for different Product Variants within the same Product, and you can use multiple Product Discounts for the same Product Variant to offer discounts on different Prices. For example, one Product Discount with 10% off on the Product Variant Price in Euro, and another Product Discount with 20% off on the US Dollar Price of the same Product Variant.
sortOrder
determines which one applies. For published Products, Product Discounts apply based on the current ProductData. For unpublished Products, the staged ProductData is used to calculate the Discount.- For Embedded Prices, the system stores Product Discounts in the Price
discounted
field. - For Standalone Prices, the Product Discounts API uses the StandalonePrice
discounted
field in its current and staged representation.
value
to external
.Cart Discounts
Cart Discounts can be used to discount different elements of a Cart, some of which include:
- A discount on any item in a cart with CartDiscountLineItemsTarget or CartDiscountCustomLineItemsTarget
- A discount on the shipping cost of a cart with CartDiscountShippingCostTarget
- A discount on a cart's total with CartDiscountTotalPriceTarget
- A 'Buy X, get Y free' discount with CartDiscountValueGiftLineItemDraft
- A 'Buy X items, get Y of them at a discounted rate' discount with MultiBuyLineItemsTarget or MultiBuyCustomLineItemsTarget. Note that you can only apply a percentage-off discount on eligible items.
You can also use CartDiscountPatternTarget. It is a more powerful option and lets you apply an amount, a fixed price, or percentage discount to eligible items.
stackingMode
to StopAfterThisDiscount
on the CartDiscount that must be applied last.stores
array. If the array is empty or unspecified, the Discount applies (globally) to all Carts. To apply a Cart Discount to only a single Cart or Order, you can use Direct Discounts.Discount Codes
10
Discount Codes, and each Discount Code can include up to 10
Cart Discounts.Discount Codes are not ranked like Cart Discounts, but the order in which they apply on a Cart depends on the ranking of the Cart Discounts (the Discount Codes are associated to). For example, consider a scenario where the following discounts apply on a Cart:
- SUMMER SALE: a Cart Discount that discounts the Cart by €10 with a CartDiscount
sortOrder
of 0.05 - MYFIRSTPURCHASE: a Discount Code associated to a Cart Discount, NEW CUSTOMERS, that discounts the Cart by €5 with a CartDiscount
sortOrder
of 0.1
sortOrder
, Composable Commerce prioritizes NEW CUSTOMERS before SUMMER SALE, and first applies a discount of €5 followed by €10.Direct Discounts
Stacking
. Unlike Cart Discounts, they do not have a sorting order, and apply in the order listed in the directDiscounts
array of a Cart or Order.If a Direct Discount is applied to a Cart or Order, any matching Cart Discounts in the Project are ignored and do not apply. Cart Discounts using Discount Codes cannot be applied as well, as Direct Discounts and Discount Codes are mutually exclusive in a Cart or Order.
Discount interaction on Carts
discountTypeCombination
field in a Cart and Order indicates the discount type that applies during the best deal discount application.When evaluating the best deal, the Cart Discounts are evaluated and applied in the following order:
- Discounts on (Custom) Line Items (CartDiscountLineItemsTarget, CartDiscountCustomLineItemsTarget, MultiBuyLineItemsTarget, MultiBuyCustomLineItemsTarget, or CartDiscountPatternTarget)
- Gift Line Items
- Discounts on shipping cost (CartDiscountShippingCostTarget)
- Discounts on Cart total price (CartDiscountTotalPriceTarget)
The sort order and stacking mode are effective only among Cart Discounts with the same discount target.
List price | Price after product discount | Price after cart discount | |
---|---|---|---|
Shirt | US$100 | US$70 | US$90 |
Jean | US$120 | US$90 | US$60 |
---- | ---- | ---- | ---- |
Total price (inclusive of all taxes) | US$160 | US$150 |
With a lower price (US$150), the Cart Discount is chosen as the best deal.
If the combination of discounts offers a better price, the Line Items' prices might increase—for example, the shirt's price will be US$90 instead of the US$70.
List price | Price after product discount | Price after cart discount | |
---|---|---|---|
Shirt | US$100 | US$70 | US$60 |
Jean | US$120 | US$90 | - |
---- | ---- | ---- | ---- |
Total price (inclusive of all taxes) | US$160 | US$150 |
With a lower price (US$150), the Cart Discount is chosen as the best deal.
chosenDiscountType
will be cart-discount
, and the Product Discount is only applied for Line Items with no Cart Discounts.List price | Price after product discount | Price after cart discount | |
---|---|---|---|
Shirt | US$100 | US$70 | US90 |
Custom Line Item | US$50 | - | US$45 |
---- | ---- | ---- | ---- |
Total price (inclusive of all taxes) | US$120 | US$135 |
With a lower price (US$120), the Product Discount is chosen as the best deal.
Since no Product Discount applies for the Custom Line Item, the list price is considered—US$50.
List price | Price after product discount | Price after cart discount | |
---|---|---|---|
Shirt | US$100 | - | US90 |
---- | ---- | ---- | ---- |
Total price (inclusive of all taxes) | US$110 | US$80 |
With a lower price (US$80), the Cart Discount is chosen as the best deal.
The shipping cost and discount on the total cart price is applied irrespective of the stacking mode.