Pricing and Discounts

Learn new ways to revolutionize Promotions and Pricing Logic.

Ask about this Page
Copy for LLM
View as Markdown

After completing this page, you should be able to:

  • Describe the purpose of the discountTypeCombination field and how the Best Deal mode modifies the traditional stacking behavior of Product Discounts and Cart Discounts.
  • Explain the functionality and benefits of Discount Groups for managing exclusive cart promotions and mass-disabling campaign discounts.
  • Differentiate between the ProportionateDistribution, EvenDistribution, and IndividualApplication modes for applying relative Cart Discounts.

Best deal discounts

If you’ve been working with commercetools for a while, you know that Product Discounts and Cart Discounts have historically played a very specific game: they always stacked. If a product was 50% off, and the customer added a "10% off" coupon, the coupon applied to the already reduced price.

While great for customers, this often hurt margins. Merchants wanted the ability to say, "If the item is already on sale, the coupon shouldn't apply to it unless it's a better deal."

Previously, preventing a Cart Discount from applying to an already discounted product required complex workarounds (like custom predicates checking for price.discounted). These solutions were brittle and hard to maintain. By default, the stacking behavior meant customers often got a sale price plus a Cart Discount.
The new discountsConfiguration in the Project settings allows you to change this default behavior. You can now choose between:
  • Stacking: the classic behavior. Product Discounts and Cart Discounts both apply.
  • BestDeal: the new capability. The engine compares the Product Discount against the Cart Discount and applies only the one that results in the lowest price for the customer. No more double dipping.
Example:
Let's say you want to protect your margins and switch your project from the default (Stacking) to the new Best Deal mode.

The first thing to do would be to create a Product Discount and a Cart Discount that could potentially overlap, for example:

  • Product Discount where a customer saves 30% on items from the "Tables" category. Create a Product Discount in the Merchant Center for a 30% discount on items from the Tables category
  • Cart Discount (created in the Merchant Center) where customers would get 10% off all tables if the Cart total is at least 100 EUR: Create a Cart Discount in the Merchant Center for a 10% discount off all tables if the Cart total is at least 100 EUR

Let’s see what happens by default when we create a new Cart and add an item from the "Tables" category to it:

Requestbash
curl -X POST "https://api.{region}.commercetools.com/{projectKey}/carts" \
  -H "Authorization: Bearer {bearerToken}" \
  -H "Content-Type: application/json" \
  --data '{
    "currency": "EUR",
    "country": "DE",
    "lineItems": [
      {
        "sku": "GMCT-01"
      }
    ]
  }' | jq
Resultjson
{
  "type": "Cart",
  "id": "8974e6bb-ed84-4749-87f1-41ee5b4cbc66",
  "version": 3,
...
      "price": {
        "id": "472112d6-623c-42e9-870b-b3c4baf83c3d",
        "value": {
          "type": "centPrecision",
          "currencyCode": "EUR",
          "centAmount": 25999,
          "fractionDigits": 2
        },
        "key": "25999EUR",
        "country": "DE",
        "discounted": {
          "value": {
            "type": "centPrecision",
            "currencyCode": "EUR",
            "centAmount": 18199,
            "fractionDigits": 2
          },
          "discount": {
            "typeId": "product-discount",
            "id": "fdea6c60-190e-412c-bd02-1edd6f2211b6"
          }
        }
      },
      "quantity": 1,
      "discountedPrice": {
        "value": {
          "type": "centPrecision",
          "currencyCode": "EUR",
          "centAmount": 16379,
          "fractionDigits": 2
        },
        "includedDiscounts": [
          {
            "discount": {
              "typeId": "cart-discount",
              "id": "e3e9aea0-4b77-4e4c-978b-ba7645c772a1"
            },
            "discountedAmount": {
              "type": "centPrecision",
              "currencyCode": "EUR",
              "centAmount": 1820,
              "fractionDigits": 2
            }
          }
        ]
      },
      "discountedPricePerQuantity": [
        {
          "quantity": 1,
          "discountedPrice": {
            "value": {
              "type": "centPrecision",
              "currencyCode": "EUR",
              "centAmount": 16379,
              "fractionDigits": 2
            },
            "includedDiscounts": [
              {
                "discount": {
                  "typeId": "cart-discount",
                  "id": "e3e9aea0-4b77-4e4c-978b-ba7645c772a1"
                },
                "discountedAmount": {
                  "type": "centPrecision",
                  "currencyCode": "EUR",
                  "centAmount": 1820,
                  "fractionDigits": 2
                }
              }
            ]
          }
        }
      ],
      "perMethodTaxRate": [],
      "addedAt": "2025-11-19T11:24:23.875Z",
      "lastModifiedAt": "2025-11-19T11:24:23.875Z",
      "state": [
        {
          "quantity": 1,
          "state": {
            "typeId": "state",
            "id": "f0527c44-0718-4a8c-bdfe-c94d581bf76e"
          }
        }
      ],
      "priceMode": "Platform",
      "lineItemMode": "Standard",
      "priceRoundingMode": "HalfEven",
      "totalPrice": {
        "type": "centPrecision",
        "currencyCode": "EUR",
        "centAmount": 16379,
        "fractionDigits": 2
      },
      "taxedPricePortions": []
    }
  ],
  "cartState": "Active",
  "totalPrice": {
    "type": "centPrecision",
    "currencyCode": "EUR",
    "centAmount": 16379,
    "fractionDigits": 2
  },
...
  "discountTypeCombination": {
    "type": "Stacking"
  },
  "totalLineItemQuantity": 1
}
From the result, you can see that first the Product discount was applied lowering the original product price from 259.99 EUR to 181.99 EUR to the product price and the Cart discount followed by lowering the Cart total from 181.99 EUR to 163.79 EUR. The discountTypeCombination field confirms that stacking was used.
Now, lets go ahead and switch the discount configuration in the Merchant Center by navigating to Settings âžś Project Settings âžś Miscellaneous, and changing the Promotion Prioritization by setting it to Best Deal: Set the Promotion Prioritization option to Best Deal in the Project Settings section of the Merchant Center.

Now let’s create the same Cart again and look at the result:

Resultjson
{
  "type": "Cart",
  "id": "6bb3f8d4-69a4-4642-9223-807f54feab8a",
  "version": 2,
...
      "price": {
        "id": "472112d6-623c-42e9-870b-b3c4baf83c3d",
        "value": {
          "type": "centPrecision",
          "currencyCode": "EUR",
          "centAmount": 25999,
          "fractionDigits": 2
        },
        "key": "25999EUR",
        "country": "DE",
        "discounted": {
          "value": {
            "type": "centPrecision",
            "currencyCode": "EUR",
            "centAmount": 18199,
            "fractionDigits": 2
          },
          "discount": {
            "typeId": "product-discount",
            "id": "fdea6c60-190e-412c-bd02-1edd6f2211b6"
          }
        }
      },
      "quantity": 1,
      "discountedPricePerQuantity": [],
      "perMethodTaxRate": [],
      "addedAt": "2025-11-19T11:27:57.052Z",
      "lastModifiedAt": "2025-11-19T11:27:57.052Z",
      "state": [
        {
          "quantity": 1,
          "state": {
            "typeId": "state",
            "id": "f0527c44-0718-4a8c-bdfe-c94d581bf76e"
          }
        }
      ],
      "priceMode": "Platform",
      "lineItemMode": "Standard",
      "priceRoundingMode": "HalfEven",
      "totalPrice": {
        "type": "centPrecision",
        "currencyCode": "EUR",
        "centAmount": 18199,
        "fractionDigits": 2
      },
      "taxedPricePortions": []
    }
  ],
  "cartState": "Active",
  "totalPrice": {
    "type": "centPrecision",
    "currencyCode": "EUR",
    "centAmount": 18199,
    "fractionDigits": 2
  },
...
  "discountTypeCombination": {
    "type": "BestDeal",
    "chosenDiscountType": "ProductDiscount"
  },
  "totalLineItemQuantity": 1
}

In this scenario, only one discount—either a Product Discount or a Cart Discount—could be applied. The Product Discount is chosen as the superior option because it provides the customer with a greater overall saving (30% off tables versus just 10%).

Discount Groups

In one of the previous chapters, we looked at how Product and Cart discounts fight for supremacy. Now, we’re going to zoom in on Cart Discounts specifically and how you manage them at scale.

If you run high-volume campaigns (like Black Friday or Cyber Week), you know the struggle:

  • The math problem: if a user has five different coupons, how do you ensure they only get the single best one without writing complex sortOrder logic?
  • The panic button problem: when a campaign ends, how do you turn off 50 different discount rules instantly without clicking 50 times?

Discount Groups introduced in 2025 can solve both of those problems.

Historically, if you wanted to offer "Exclusive" discounts (where a customer can pick only one coupon), you had to rely on StackingMode: StopAfterThisDiscount and rigorous management of sortOrder.

It was brittle. If the "10% Off" Cart Discount had a higher sort order than the "$5 Off" one, the 10% off would always "win" and stop the chain—even if the $5 off was actually a better deal for the customer!

Thanks to Discount Groups, you can now group multiple Cart Discounts under a single logical entity and you can deactivate the entire group in one request when discounts belonging to the group are no longer needed.
Example:
Creating a Discount Group is actually really simple. It can be done on the Merchant Center by going to Discounts âžś Cart discount list and using the Add discount group button in the upper right corner.

Let’s use it to create an example group with the following information:

  • Group name: Candle Bar Promo Week
  • Group key: candle-bar-promo
  • Rank: 0.6
  • Discount prioritization mode: Best Deal

The only remaining step is to incorporate the desired Cart Discounts into the newly established group.

You can create the following Cart Discounts using the Merchant Center UI (as shown in previous steps), or alternatively via the API if you prefer automation. For this example, we'll use the Merchant Center:

Let’s create two simple Buy and Get Cart Discounts (no discount codes needed) and add them to the group:
  • Save 10% on all Bar Accessories when you buy a Vanilla Candle (sortOrder: 0.7)
  • Save 20% on all Bar Accessories when you buy an Evergreen Candle (sortOrder: 0.4)
The trigger and target patterns should look something like this (for the 20% discount): Trigger and target configuration for a 20 percent Buy and Get Cart Discount on Bar Accessories when buying an Evergreen Candle.
Once we have our two discounts created (and activated) and added to the group, we can go ahead and create a new Cart with a Vanilla Candle, Evergreen Candle and a Bar Accessory: Discount Group Candle Bar Promo Week with both Buy and Get Cart Discounts and the group status set to active.
Requestbash
curl  -X 'POST' \
  'https://api.{region}.commercetools.com/{projectKey}/carts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {bearerToken}' \
  -d '{
    "currency": "EUR",
    "country": "DE",
    "lineItems": [
      {
        "sku": "VC-01"
      },
      {
        "sku": "EC-0993"
      },
      {
        "sku": "WOP-09"
      }
    ]
  }
' | jq
Resultjson
{
  "type": "Cart",
  "id": "b55dc4f2-6e3e-4619-b984-332be4e27268",
  "version": 3,
  "versionModifiedAt": "2025-12-08T13:18:02.399Z",
  "lastMessageSequenceNumber": 1,
  "createdAt": "2025-12-08T13:18:02.399Z",
  "lastModifiedAt": "2025-12-08T13:18:02.399Z",
  "lastModifiedBy": {
    "isPlatformClient": true,
    "user": {
      "typeId": "user",
      "id": "2a528594-4c01-48cb-9ef9-6cc3620978d1"
    }
  },
  "createdBy": {
    "isPlatformClient": true,
    "user": {
      "typeId": "user",
      "id": "2a528594-4c01-48cb-9ef9-6cc3620978d1"
    }
  },
  "lineItems": [
...
      "name": {
        "en-US": "Vanilla Candle",
        "en-GB": "Vanilla Candle",
        "de-DE": "Kerze mit Vanilleduft"
      },
...
      "name": {
        "en-US": "Evergreen Candle",
        "en-GB": "Evergreen Candle",
        "de-DE": "Kerze \"Evergreen\""
      },
...
      "name": {
        "en-US": "Wine Bottle Opener",
        "en-GB": "Wine Bottle Opener",
        "de-DE": "Korkenzieher"
      },
...
      "price": {
        "id": "060e78c1-c2ba-4424-ad18-a6e5a419ae85",
        "value": {
          "type": "centPrecision",
          "currencyCode": "EUR",
          "centAmount": 199,
          "fractionDigits": 2
        },
        "key": "199EUR",
        "country": "DE"
      },
      "quantity": 1,
      "discountedPricePerQuantity": [
        {
          "quantity": 1,
          "discountedPrice": {
            "value": {
              "type": "centPrecision",
              "currencyCode": "EUR",
              "centAmount": 183,
              "fractionDigits": 2
            },
            "includedDiscounts": [
              {
                "discount": {
                  "typeId": "cart-discount",
                  "id": "3aafd31c-53ff-42aa-858b-fbf8ed432944"
                },
                "discountedAmount": {
                  "type": "centPrecision",
                  "currencyCode": "EUR",
                  "centAmount": 16,
                  "fractionDigits": 2
                }
              }
            ]
          }
        }
      ],
...
  "discountTypeCombination": {
    "type": "Stacking"
  },
  "totalLineItemQuantity": 3
}

The system correctly applied the intended logic of our Discount Group. Although the Cart qualified for two discounts, and the 10% discount had a higher ranking, the 20% discount was the only one applied. This is because the 20% discount provided the customer with the better offer.

Relative Cart Discount distribution modes

We have continually enhanced the flexibility of how Cart Discounts are applied.

As you may remember, in 2024, we introduced the applicationMode field to CartDiscountValueAbsoluteDraft. This gave users greater control over how absolute discounts are distributed across specific line items.
Building on this, in 2025, we extended the same level of flexibility to relative discounts by adding the applicationMode field to CartDiscountValueRelativeDraft.

This setting can also be found when creating a new Cart Discount in the Merchant Center.

In the previous chapter, did you observe anything unusual about the discount application on the line items?

Although the Cart received the intended 20% discount on the bar accessory (wine bottle opener) price, a closer look reveals that the discount was applied to both Line Items, even though the intent was to discount only bar accessories. This behavior occurred because the discount's applicationMode was set to ProportionateDistribution by default: Cart Discount settings in the Merchant Center
We can examine the impact of the three available applicationMode settings on the 20% discount by creating a new Cart containing an Evergreen Candle and various bar accessories:
Requestbash
curl  -X 'POST' \
  'https://api.{region}.commercetools.com/{projectKey}/carts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {bearerToken}' \
  -d '{
    "currency": "EUR",
    "country": "DE",
    "lineItems": [
      {
        "sku": "EC-0993"
      },
      {
        "sku": "WOP-09"
      },
      {
        "sku": "WTP-09"
      },
      {
        "sku": "BUCK-023"
      }
    ]
  }
'  | jq
  • ProportionateDistribution (distributed proportionately across all involved items):
NameQtyUnit PriceDiscountFinal Price
Evergreen Candle1€ 2.99€ 0.50€ 2.49
Wine Bottle Opener1€ 1.99€ 0.34€ 1.65
Willow Teapot1€ 8.99€ 1.51€ 7.48
Ice Bucket1€ 4.99€ 0.84€ 4.15
Cart total:4€ 18.96€ 3.19€ 15.77

The total price of the bar accessories was €15.97. The discount is calculated as 20% of the bar accessories subtotal (€15.97), resulting in €3.19. When this discount is proportionally distributed across all line items (including the Evergreen candle), it represents approximately 16.9% of the total Cart price of €18.96. This means the discount is based on the qualifying items' subtotal, not the entire Cart total.

  • EvenDistribution (distributed evenly across all involved items):
NameQtyUnit PriceDiscountFinal Price
Evergreen Candle1€ 2.99€ 0.80€ 2.19
Wine Bottle Opener1€ 1.99€ 0.80€ 1.19
Willow Teapot1€ 8.99€ 0.80€ 8.19
Ice Bucket1€ 4.99€ 0.80€ 4.19
Cart total:4€ 18.96€ 3.20€ 15.76
The calculation shows that 20% of the bar accessories' total price (€15.97) is €3.20. This €3.20 discount was then applied evenly to all line items, resulting in an €0.80 price reduction for each item, including the Evergreen candle.
  • IndividualApplication (only applied proportionately to the discounted items):
NameQtyUnit PriceDiscountFinal Price
Evergreen Candle1€ 2.99€ 0.00€ 2.99
Wine Bottle Opener1€ 1.99€ 0.40€ 1.59
Willow Teapot1€ 8.99€ 1.80€ 7.19
Ice Bucket1€ 4.99€ 1.00€ 3.99
Cart total:4€ 18.96€ 3.20€ 15.76

This final option demonstrates that the 20% discount was exclusively applied to the intended line items (bar accessories). The price of the trigger Line Item (Evergreen candle) remains unchanged.

Keep in mind that while all three options yield a similar overall price reduction, the discount distribution across individual line items varies significantly. This is a crucial consideration when setting up Cart Discounts.

You may have noticed a slight difference in the total discount value between the Cart using ProportionateDistribution and the other two. This is an excellent observation and is not an error; the reason for this will be clarified in the following section.

Test your knowledge