Discounts are configured using Litium backoffice. See more about discounts in the Litium user guide. They are represented as order rows with the type Discount. See more about order rows under Field framework. Discounts are automatically calculated in Litium API. The calculation can not be overridden.
Discount types and priority
Discount codes
Threshold
Historical best price
Litium supports all discount types below. When more than one discount is active and valid for the cart, the following order of priority is used:
1. External discount (applied as Discounted product price discount type).
2. Product discounts:
- Discounted product price
- Product amount discount
- Product percentage discount
- Product group discounts, like Mix and match or Buy X and get a discount on the cheapest.
3. Order level amount discount
4. Order level percentage discount
5. Shipment fee discount and payment fee discount
6. Free gifts
When multiple discounts with the same priority exist, the system will choose the one with the highest value. Only one product discount can be applied per product instance (A product with quantity 4 is calculated as 4 product instances). All product discounts are evaluated together. Discounted product price, product amount and product percentage discount are evaluated together with Buy X discounts. A discount can also be set as not allowed to combine with other discounts or only allowed to combine with one or more specific discounts.
External discounts
The system supports receiving discounted product prices from external systems and applying them to products during a buyer's session. These discounts are automatically active across all channels and remain hidden from the backoffice interface.
When an external discount is applied to a product instance, it takes precedence and prevents any additional discounts from being applied to that same instance.
To implement external discounts, Litium provides the AddExternalDiscountAsync action through the cart context. You simply need to provide the required information for Litium to create an external discount.
Consider the example below for applying an external discount - this can be added to any MVC Accelerator controller:
[HttpPut]
[ValidateAntiForgeryToken]
[Route("setExternalDiscount")]
public async Task<IActionResult> SetExternalDiscount(string variantId)
{
if (!string.IsNullOrEmpty(variantId))
{
var cartContext = await HttpContext.GetCartContextAsync();
await cartContext.AddExternalDiscountAsync(new AddExternalDiscountArgs()
{
CurrencyCode = "SEK",
NotAfter = DateTimeOffset.UtcNow.AddDays(1),
NotBefore = DateTimeOffset.UtcNow.AddDays(-1),
PriceIncludingVat = 100,
Source = "Source",
VariantId = variantId,
AdditionalInfo = new Dictionary<string, object> { { "key", "value" } }
});
return Ok(variantId);
}
return BadRequest();
}
Litium allows one product discount per product
Only one discount can be applied per product instance. In an order with quantity 4 of a product A is calculated as 4 product instances.
For example, the order has 4 product A, unit price = 100 SEK and we have these discounts:
- Discount 1: Buy 3 product A get discount - 50 SEK on one unit.
- Discount 2: 10% discount for each Product A.
The order will look like this :
- Product A * 4 = 400 SEK
- Discount 1: - 50 SEK (This applied for 3 instance of A)
- Discount 2: - 10 SEK (This applied for the 4th instance of A)
For each instance of product, we can not have more than one discount applied.
All discount types include support for activation by a discount code, and this is configured in the backoffice when creating a discount.
Single discount codes that are named by the merchant manually can be used for the activation of discounts, one-to-many times. They are case-insensitive.
Generated discount codes that are selected when creating a discount, including the number of codes that should be generated by the system. They will be generated when saving the discount. Codes are generated using random upper case characters and numbers from a list. A generated code will be nine characters long. Generated codes are valid to use once for customers.
The buyer enters the discount code in checkout, and the system validates if the entered discount code corresponds to products in the cart and if an existing discount code has been used fewer times than the defined limit for that code.
The system will check if the code is connected to an active discount. If it is, it will be stored in the cart. Before calculating the order total in the checkout, the system will check if the discount code is applicable to the items in the customer's cart. If the discount code is unknown, invalid, or has already been used the allowed number of times, the customer will receive the message "Discount code invalid". If the discount code is valid, the customer will see the name of the discount, and the received amount off on the checkout page.
Usage is followed and displayed on the discount page. In this view, it is also possible to download an Excel sheet with a list of generated codes. The list also contains information about how many of the generated codes are left to use.
In certain campaign scenarios, it may not be feasible to generate discount codes at the time the discount is created.
To accommodate this, you can add or delete discount codes for an existing discount later using the API.
public class DiscountCodeHandler
{
private readonly Guid _discountCodeSystemId = new Guid("<existing-discount-systemid>");
private readonly DiscountService _discountService;
public AddDiscountCodeHandler(DiscountService discountService)
{
_discountService = discountService;
}
// Add provided codes
public void AddCodes(IEnumerable<string> codes)
{
var normalized = codes
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.ToHashSet();
var discount = _discountService.Get(_discountCodeSystemId);
var writable = discount.MakeWritableClone();
foreach (var code in normalized)
{
writable.Codes.Add(new DiscountCode { Code = code, MaxUsageCount = 1 });
}
_discountService.Update(writable);
}
public void DeleteCodes(IEnumerable<string> codes)
{
var normalized = codes
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var discount = _discountService.Get(_discountCodeSystemId);
var writable = discount.MakeWritableClone();
var codesToRemoveSet = new HashSet<string>(normalized, StringComparer.OrdinalIgnoreCase);
var discountCodesToRemove = writable.Codes
.Where(dc => codesToRemoveSet.Contains(dc.Code))
.ToList();
foreach (var discountCode in discountCodesToRemove)
{
writable.Codes.Remove(discountCode);
}
_discountService.Update(writable);
}
}
# Add codes
curl -X POST https://yourlitiumdomain/Litium/api/admin/sales/discounts/{systemId}/codes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '["CODE1", "CODE2"]'
# Generate codes
curl -X POST https://yourlitiumdomain/Litium/api/admin/sales/discounts/{systemId}/codes/action/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '10'
# Delete codes
curl -X DELETE https://yourlitiumdomain/Litium/api/admin/sales/discounts/{systemId}/codes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '["CODE1", "CODE2"]'
All discount types include support for activation by cart threshold, and this is configured in the backoffice when creating a discount. The threshold can be defines as a minimum amount for the products in the cart. You can also limit the cart threshold amount calculation to only consider products from a specific product list as well as a mimimum quantity of products.
The system supports discount combinations when calculating the cart threshold. Threshold amount is evaluated on the total cart amount excluding other discounts that applied on the same cart.
The threshold value for a discount can be set per currency in the backoffice. The threshold value is including VAT.
The historical best price is the lowest price (including or excluding VAT) for a product variant, channel, country, and currency, within the last 30 days prior to the evaluation date
This calculation includes multiple discount windows, which represent the time periods during when a discount is active for a product (from when the discount starts until it ends).
- A discount window starts when a discount becomes active and ends when it is no longer active. The system records both kinds of events so we can know when discounts started and when they ended.
- While a discount window is open, the evaluation date is the discount start. This ensures the 30-day comparison is anchored to the discount period.
- When a discount ends, the window is closed and its prices are considered as past data in subsequent calculations.
The lowest price is accessible via Storefront API
query {
productSearch(query: { content: { value: "*dress*"}})
{
nodes {
articleNumber
price {
discountPriceExcludingVat
discountPriceIncludingVat
unitPriceExcludingVat
unitPriceIncludingVat
vatRate
history {
lowestPriceExcludingVat
lowestPriceIncludingVat
}
}
}
}
}