Working with Select Product promotions
The Select Product promotion effect lets customers choose one or more free or discounted items when their basket qualifies for a promotion. This guide explains how it works in the basket model and what your storefront needs to do to support it.
How it works
Unlike the Add Product effect — which automatically inserts items into the basket with no customer input — Select Product surfaces a list of eligible items that the customer actively chooses from.
When a promotion with a Select Product effect is applied to the basket, Norce populates an OptionalItems collection on each basket row that qualifies. Your storefront reads this collection and renders the choices beneath or alongside the qualifying row.
When the customer picks an item, you call InsertBasketItem with the full OptionalItems entry. Norce handles the association to the parent row.
Once added, the selected item is persisted to the database and gets its own basket item ID. It is visible in the basket as a child row clearly associated to its parent.
Rendering the selection UI
The entire selection UI should be driven by basket data. For each basket row:
- Check whether the row has an
OptionalItemscollection with entries - If it does, render the available choices — typically inline beneath the qualifying row
- When the customer selects an item, call Insert Basket Item with the OptionalItems entry as-is
- Norce handles the association to the parent row
Example Basket (with optional items)
{
"Id": 885,
"CustomerId": null,
"CompanyId": null,
"SalesContactId": null,
"StatusId": 3,
"CurrencyId": 2,
"CurrencyCode": "SEK",
"Comment": "A test basket with select product optional items, fields omitted for size",
...omitted...
"Items": [
{
"Id": 1793,
"LineNo": 1,
"ParentLineNo": null,
"PartNo": "100011",
"Quantity": 2,
"Comment": "This item has optional items under it",
...omitted...
"OptionalItems": [
{
"Id": 0,
"LineNo": 0,
"ParentLineNo": null,
"ProductId": 27,
"PartNo": "100015",
"PriceDisplay": 80.00,
"VatRate": 1.25,
"Quantity": 1,
"Comment": "optional basketitem 1 with promotional discount for display",
...omitted...
"PriceDisplayIncVat": 100.0000,
}
],
...omitted...
"AppliedPromotions": [
"Does not contain the select-product promotion"
],
"RequirementPromotionIdSeed": "59,1,2 - does contain the select product promotion along with others",
"PriceDisplayIncVat": 5037.49,
...omitted...
}
],
...omitted...
"AppliedPromotions": [
{
"Id": 59,
"Name": "Select product test for machines",
"ShortDescription": "Applied promotions on Basket level contains Select product promotion candidates, even if they are not used yet (appliedamount=0), its productfilter contains the effect products as well.",
...omitted...
"ProductFilters": [
{
...omitted...
"PartNo": "100015"
},
{
...omitted...
"PartNo": "100016"
}
],
...omitted...
"ExternalCode": "prm_select",
"AppliedAmount": 0.00
}
],
...omitted...
}
Example Basket (with added optional item)
{
"Id": 885,
"CustomerId": null,
"CompanyId": null,
"SalesContactId": null,
"StatusId": 3,
"CurrencyId": 2,
"CurrencyCode": "SEK",
"Comment": "A test basket with a select product effect applied, fields omitted for size",
...omitted...
"Items": [
{
"Id": 1793,
"LineNo": 1,
"PartNo": "100011",
"Quantity": 2,
"Comment": "This item has no longer an optional item, if its only one is already applied",
...omitted...
"OptionalItems": []
...omitted...
"AppliedPromotions": [
"Does not contain the select product promotion, since it will only change the amount for the new item below"
],
"RequirementPromotionIdSeed": "59,1,2 - does contain the select product promotion along with others",
"PriceDisplayIncVat": 5037.49,
...omitted...
},
{
"Id": 1794,
"LineNo": 2,
"ProductId": 27,
"PartNo": "100015",
"PriceDisplay": 80.00,
"VatRate": 1.25,
"Quantity": 1,
"Comment": "optional basketitem 1 with promotional discount for display",
...omitted...
"AppliedPromotions": [
{
"Id": 59,
"Name": "Select Product promotion, were the applied ammount is the discount to the price list price",
"DiscountCode": null,
"AppliedAmount": 19.00,
"AppliedAmountIncVat": 23.75
}
],
...omitted...
"PriceDisplayIncVat": 100.0000,
}
],
...omitted...
"AppliedPromotions": [
{
"Id": 59,
"Name": "Select product test for machines",
"ShortDescription": "Applied promotions on Basket level contains applied select product promotion with the total sum from all added optional items.",
...omitted...
"ProductFilters": [
{
...omitted...
"PartNo": "100015"
},
{
...omitted...
"PartNo": "100016"
}
],
...omitted...
"ExternalCode": "prm_select",
"AppliedAmount": 19.00
}
],
...omitted...
}
Limitation: promotions not tied to specific rows
OptionalItems are only populated when the promotion can be associated to a specific basket row — for example, "buy product X, get to choose from Y or Z."
If the promotion condition is not row-specific (for example, a basket total threshold like "spend over 500 kr"), Norce cannot attach OptionalItems to a particular row. In this case the OptionalItems collection will be empty, and you need to fall back to reading the eligible product list directly from the promotion object on the basket, then look up those products separately to render the selection.
Contrast with Add Product
| Select Product | Add Product | |
|---|---|---|
| Customer choice | Yes — customer picks from a list | No — items added automatically |
| Location in basket | Child row, associated to parent | Added to end of basket |
| Persisted to DB | Yes, once selected | No — in-memory only, no basket item ID |
| Tied to a specific row | Yes (when condition is row-based) | No |
Add Product items can be identified by the absence of an internal basket item ID and by their association to a promotion rather than a parent row.
Handling promotion expiry
A selected item is not automatically removed from the basket if the promotion stops applying (for example, if the qualifying product is removed or the promotion expires). The item remains in the basket, but the promotion-based price no longer applies — the price will change.
Your storefront should watch for this. The signal to look for: a child basket row with no active promotion effect, on a parent that is not a bundle or package. When this occurs, warn the customer before checkout that the conditions for the selected item have changed.