Pricing and discounts overview

Overview of the concepts related to product pricing and discounts in Composable Commerce.

Pricing

A Price represents the purchase value of a Product Variant, or SKU, in a specific currency. The uniqueness of a Price is determined by the price scope (currency, country, Customer Groups, or Channels) assigned to it, and only one Price can exist for a price scope.
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.

In addition to the above, based on your business case, you can use discounts to add flexibility to your product pricing—such as for temporary promotion campaigns. In Composable Commerce, Discounts reduce your effort in using and maintaining discounted and base prices as they can be deactivated when the promotion ends.
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.

To create an efficient Price model, follow our best practices.

Types of Prices

Composable Commerce offers two ways of storing your product prices. The prices can be embedded in the Product Variants (up to 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

Embedded Prices are stored within a ProductVariant, and are associated to it through its 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.

Embedded Prices are managed and queried using the Products API. You can add Prices when creating a Product, or manage Prices of an existing Product using the Add Price, Set Prices, Change Price, or Remove Price update actions.

Changes to Embedded Prices can be staged before being published to the current representation of a Product.

Standalone Prices

Standalone Prices are not embedded within a Product Variant, but are standalone resources that are associated to a ProductVariant through its sku field. With Standalone Prices, you can store up to 50 000 Prices for each Product Variant.

Standalone Prices are managed and queried using the Standalone Prices API, independent from your Products, thus contributing to better query performance.

Changes to Standalone Prices can be staged with StagedStandalonePrice before being published.

External Prices

External prices are stored and maintained in a system external to Composable Commerce, except when using Custom Objects for storing your pricing data. Since external prices are not part of the product catalog, they are not available on the Product Projection Search API as well as the Product Search API for filtering, faceting, or sorting products by price.

You can set an external price for the Line Item when creating or updating a Cart using the following actions: Add LineItem, Remove LineItem, Change LineItem Quantity, Set LineItem Price, or Set LineItem TotalPrice. If the Line Item quantity is 1, set the value for externalPrice. For a higher quantity, set the value (unit price value multiplied by the quantity) for 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.

When the PriceTier 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

Although you can specify product prices with different price scopes to serve all customers, you would want the most suitable price selected for your customer. The price displayed on a product detail page is determined using the Product price selection parameters, and the price calculated on a Cart is determined using the Line Item price selection parameters.

  • Product price selection: When displaying a price on a product detail page, the price is determined by the Product Projections API and Product Projection Search endpoint based on the following parameters:

    • priceCurrency: maps to value.currencyCode of Price or StandalonePrice
    • priceCustomerGroup: maps to customerGroup of Price or StandalonePrice
    • priceChannel: maps to channel of Price or StandalonePrice
    • priceCountry: maps to country of Price or StandalonePrice

    The returned ProductVariant price 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 to value.currencyCode of Price or StandalonePrice
    • Cart customerGroup: maps to customerGroup of Price or StandalonePrice
    • LineItemDraft distributionChannel: maps to channel of Price or StandalonePrice
    • Cart country: maps to country of Price or StandalonePrice

Composable Commerce selects the price for Products and Line Items based on the following fallback logic.

Fallback logic

To consider what type of Price must be used for a Product Variant, APIs check the Product 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:

  1. Checks for a Price for the Customer Group, channel, and country
  2. Checks for a Price for the Customer Group and channel for all countries
  3. Checks for a Price for the Customer Group and country for all channels
  4. Checks for a Price for the Customer Group for all channels and countries
  5. Checks for a Price for the channel and country for all Customer Groups
  6. Checks for a Price for the channel for all Customer Groups and countries
  7. Checks for a Price for the country for all Customer Groups and channels
  8. Checks for a Price for any Customer Group, channel, and country

If a Price is not found in one step, the next steps are applied until the Price is found. If no Price is found with an exact match, wildcards (all countries, channels, and CustomerGroups) are used.

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.

The Price is shown only when the timestamp of the request is within validFrom and validUntil range of Embedded Price or StandalonePrice.

If the Price has PriceTier, the Price tier valid for the Line Item quantity is selected. However, the tiered price will be ignored if the Price is already discounted (by a Product Discount). If no Price tier can be used, the base Price is used.

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 a Product at any given time. If more than one active Product Discount matches a Price, the ProductDiscount 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.

Product Discounts can apply to both Embedded Prices and Standalone Prices:

Product Discounts are managed and queried using the Product Discounts API. For cases where the base prices are stored externally or when Composable Commerce logic cannot support your discounting requirements, you can use external discounts. In these cases, the discounts are calculated by an external system and you need to set the discounted amounts in Composable Commerce. To use external discounts, set the ProductDiscountDraft value to external.

Cart Discounts

Cart Discounts can be used to discount different elements of a Cart, some of which include:

Cart Discounts are recalculated every time a Discount Code, LineItem, or CustomLineItem is added or removed from a Cart, or when an Order is created from a Cart.

Unlike Product Discounts, multiple Cart Discounts can apply on a Cart at any given time, but in a ranked order. This includes Discount Codes applied during checkout, which are associated with Cart Discounts. You can avoid applying further Cart Discounts to the Cart by setting the stackingMode to StopAfterThisDiscount on the CartDiscount that must be applied last.

Cart Discounts can be defined globally for a Project, or be specific to one or more Stores in a Project. In the latter case, the Discount is only applied to Carts that belong to one of the Stores referenced in the CartDiscount 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.

Cart Discounts are managed and queried using the Cart Discounts API.

Discount Codes

Discount Codes can be used to offer additional Cart Discounts to eligible Customers by use of unique codes during checkout. With Discount Codes, you can also control the number of times they apply on a Cart, and this limit can be configured to apply on a Customer's Cart or any Cart. A Cart can have up to 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

Thus, based on the sortOrder, Composable Commerce prioritizes NEW CUSTOMERS before SUMMER SALE, and first applies a discount of €5 followed by €10.

Discount Codes are managed and queried using the Discount Codes API.

Direct Discounts

Direct Discounts are used to represent Cart Discounts that can be applied on Quotes, and are associated only with a single Cart or Order. They are always active and valid, and have the default StackingMode, 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.

Customer-specific pricing and discounts

Composable Commerce also lets you define prices and discounts specific to Customers using Customer Groups or Business Units. For more information, see Customer-specific products and prices.

Additionally, as Buyers, Customers can also negotiate prices with a Seller using Quotes. For more information, see Quotes overview.