Understanding the Promotion Evaluation Process
This guide explains how Deducto evaluates promotions when a cart is processed. Understanding the process is useful both for setting up promotions effectively and for troubleshooting when a promotion isn't being applied as expected.
When a cart is sent to Deducto for evaluation, the engine runs through a structured ordering and per-promotion check sequence to decide what applies, what doesn't, and why.
Promotion order
At the heart of this process is promotion orchestration – the careful balancing of multiple competing discounts and offers. Consider a customer buying three pairs of shoes who qualifies for both "20% off shoes" and "Buy 2 get 1 free". Which promotion should take precedence? Should they stack? Deducto's ordering rules resolve that deterministically before any promotion is applied.
The first step is collecting every enabled promotion that could potentially apply to the cart and sorting them. The order looks like this:
- Exclusive vs non-exclusive – promotions whose
After Processing ActionisEXCLUSIVEcome first, then everything else. Note:Whole Cart (Final)promotions always behave asCONTINUEregardless of how they're configured (see After Processing Action below), so they're never treated as exclusive for ordering. - Promotion type – within each of those groups,
Item Grouppromotions come first, thenWhole Cart, thenWhole Cart (Final). - Priority – within each type, higher priority numbers come first.
- Promotion ID – used as a tiebreaker when type and priority match.
This sorts every eligible promotion into one of five buckets, evaluated left-to-right:
flowchart LR
A1["Exclusive ·<br/>Item Group"] --> A2["Exclusive ·<br/>Whole Cart"]
A2 --> B1["Non-exclusive ·<br/>Item Group"] --> B2["Non-exclusive ·<br/>Whole Cart"]
B2 --> C1["Whole Cart (Final)"]
Within each bucket, promotions are sorted by priority (descending), then by promotion ID as the tiebreaker.
Promotion evaluation and application
Once promotions are sorted, the engine evaluates each one in turn. For each promotion the following checks run, in order. If any check fails the promotion is rejected and the engine moves to the next one.
When a promotion's rejection is surfaced, the reason appears in rejectedPromotions as a technical enum value (e.g. Stopped, PromotionUsageExceeded) – intended for debugging and for storefront logic that needs to react to specific cases, not for customer display. Not every rejection is surfaced: rejections from the early-stage checks below (currency, trigger conditions, coupon matching) are silently skipped to keep the response focused on actionable cases and to avoid leaking coupon-pool state.
- Currency check – the promotion must support the cart's currency. If not, the promotion is silently skipped.
- Trigger conditions – the cart-level conditions you configured must evaluate true. If they fail, the promotion is silently skipped.
- Coupon checks (only for promotions that require a coupon) – verify that an allowed coupon code was provided, the code exists in the relevant coupon pool, and the coupon hasn't exceeded its own usage limit. If the coupon checks fail, the promotion is silently skipped.
- Usage limits – overall (
Max Use Count Per Coupon) and per-customer (Max Use Per Email Address Count) usage limits are checked here. When a limit is hit, the response showsPromotionUsageExceededorPromotionPerCustomerUsageExceeded(each withusageCountLimit). - Exclusivity and stop checks – if an earlier promotion's
After Processing ActionwasEXCLUSIVEorSTOP, the current promotion is rejected (withExclusivityorStoppedrespectively). This check only applies toItem GroupandWhole Cartpromotions;Whole Cart (Final)is always evaluated. - Applied promotions limit – if the project's overall applied-promotions limit has been reached, the promotion is rejected with
AppliedPromotionsLimitReachedand the limit value is included in the response. - Promotion action – finally, the configured discount or effect runs against the cart. If the action can't take effect (for example, no matching items were found), the promotion is rejected with
NoApplicableCartItems.
If the action succeeds, the promotion is recorded as applied. The Lock Affected Items setting then decides whether the items that received this discount are still eligible for subsequent promotions: when enabled, those items are locked out; when disabled, they remain in play.
How item allocation works for Item Group promotions
Item Group promotions have a powerful rule system that lets you describe combinations of trigger and discount groups. When a cart could fill the groups in more than one way, Deducto's allocation algorithm tries the available possibilities and picks the assignment that produces the largest total discount (or, depending on your settings, the assignment that uses the cheapest items – see Item Preference Mode in Creating a Promotion).
After Processing Action
The After Processing Action set on a promotion governs what happens after it has been applied:
- CONTINUE – processing moves on to the next promotion in order, exactly as it would have without this setting.
- STOP – no further
Item GrouporWhole Cartpromotions are evaluated.Whole Cart (Final)promotions still run. - EXCLUSIVE – same downstream effect as
STOP: no furtherItem GrouporWhole Cartpromotions are evaluated.Whole Cart (Final)still runs. The difference is upstream: EXCLUSIVE promotions are also given priority in the evaluation order, so the engine evaluates them before any non-exclusive promotion.
Whole Cart (Final) promotions are exempt from STOP and EXCLUSIVE; they always run last, regardless of any earlier After Processing decision. That's what makes them the right tool for effects like a shipping discount or a gift-with-purchase that should sit on top of whatever else has applied.
Final results
Once every promotion has been evaluated, Deducto calculates the final totals and returns them to your storefront.
For each cart item, all discounts that applied to it are stacked into a discountedSubtotal. The response also lists the individual discountSteps so you can show or audit each contribution.
For each shipping method, only the best discount applies – multiple shipping promotions targeting the same method don't compound. The response exposes this as bestDiscount on the shipping method entry, with discountedPrice reflecting the result.
Alongside the cart totals, the response includes:
appliedPromotions– the promotions that successfully fired.rejectedPromotions– the considered-but-not-applied promotions whose rejection reasons are surfaced, each with arejectionReason(and any reason-specific extras likeusageCountLimitorappliedPromotionsLimit). Promotions skipped by the early-stage checks (currency, trigger conditions, coupon matching) are not included.cartItemPromotions– for each applied non-shipping promotion, the summed discount amount it contributed to cart items. Useful for showing per-promotion discount totals on the cart. Shipping promotions are excluded – their discount surfaces on the relevant shipping method instead.couponMatchResults– for each coupon code the customer entered: whether the code wasvalid, whether it wasapplied, aninvalidReason(if not valid), and thetriggeredPromotionsit matched. This is not a discount totals map.
For the full response shape, see the Verify endpoint API reference or the Custom Storefront Integration guide.
Summary
The evaluation process is deterministic: sort promotions, check each one against a known sequence of validations, apply or reject with a reason, then assemble the totals. A few things consistently catch people out when troubleshooting:
STOPandEXCLUSIVEonly halt furtherItem GroupandWhole Cartpromotions. They don't affectWhole Cart (Final).- Shipping discounts pick the best one per method rather than stacking – useful to remember when stacking promotions on shipping.
- Priority numbers only order promotions within the same type. A
Whole Cartpromotion with priority100still runs after everyItem Grouppromotion – even one with priority10– because type ordering wins out over priority. See Promotion order.
Once you have those facts in mind, the response payload tells you what applied, what was rejected, and the reason for most rejections. Early-stage rejections (currency mismatches, unmet trigger conditions, and coupon-matching failures) are silently skipped rather than surfaced – for those, check the promotion's currency support, trigger conditions, and coupon configuration directly.
Updated 14 days ago