🎁 Perplexity PRO offert
Context
The 9.1.x branch of PrestaShop introduces a new Discount system, protected behind a feature flag. This structural change is directly reflected in classes/CartRule.php, which serves as the foundation for the new system while maintaining backward compatibility with the legacy cart rules system.
Sources: DevDocs 9.1.x, Official Blog, Core Monthly January 2026, GitHub
developbranch.
1. New imports and dependencies
9.0.x
The CartRuleCore class in 9.0.x uses classic PrestaShop imports without dependency on the new discount system.
9.1.x (develop)
New use statements appear at the top of the file:
use PrestaShop\PrestaShop\Adapter\ContainerFinder;
use PrestaShop\PrestaShop\Adapter\Discount\Application\DiscountApplicationService;
use PrestaShop\PrestaShop\Core\Domain\CartRule\CartRuleSettings;
use PrestaShop\PrestaShop\Core\Domain\Discount\DiscountSettings;
use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountType;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
Impact: The CartRule class now integrates with:
DiscountType: a Value Object that distinguishes application levels (ORDER_LEVEL,PRODUCT_LEVEL)DiscountApplicationService: the service that orchestrates discount application in the new systemFeatureFlagSettings/FeatureFlagStateCheckerInterface: to dynamically check if the new Discount system is enabled
2. Discount Feature Flag Integration
New method: isDiscountFeatureFlagEnabled()
A key addition in 9.1.x is the private method that checks the feature flag state:
private function isDiscountFeatureFlagEnabled(): bool
{
return $this->getFeatureFlagManager() !== null
&& $this->getFeatureFlagManager()->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT);
}
Role: This method acts as a switch throughout the file. When the flag is disabled (default), behavior remains identical to 9.0.x. When enabled, new calculation logic takes over.
New method: getFeatureFlagManager()
Protected method that instantiates the FeatureFlagStateCheckerInterface from the Symfony container:
protected function getFeatureFlagManager(): ?FeatureFlagStateCheckerInterface
3. New concept: getType() — Discount Typing
Added in 9.1.x
The getType() method is introduced to classify a cart rule according to the new discount model with 4 types:
| Type | Description |
|---|---|
Catalog (PRODUCT_LEVEL) |
Discount applied to a product or product segment |
Cart (ORDER_LEVEL) |
Discount on total cart amount (excluding shipping) |
| Free Shipping | Free shipping |
| Free Gift | Free product added to order |
This method returns a DiscountType Value Object based on the internal configuration of the cart rule (fields reduction_product, free_shipping, gift_product, etc.).
Developer impact: Modules that query the discount type can now use $cartRule->getType() instead of manually checking individual fields.
4. Changes in getContextualValue()
The getContextualValue() method — the core of discount calculation — contains the most significant changes.
4.1 Percentage reduction at order level (ORDER_LEVEL)
9.0.x: Percentage reduction is applied only to product total.
9.1.x: When the feature flag is enabled and type is ORDER_LEVEL:
if ($this->getType() === DiscountType::ORDER_LEVEL
&& $this->reduction_percent > 0.00
&& $this->reduction_product == 0) {
$order_products_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS, $package_products);
$order_shipping_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_SHIPPING, $package_products);
$order_total = $order_products_total + $order_shipping_total;
$reduction_value += $order_total * $this->reduction_percent / 100;
}
Key change: The percentage now applies to products + shipping costs instead of products only.
4.2 Fixed amount reduction — capped differently
9.0.x: Reduction amount is capped at product total.
9.1.x: For ORDER_LEVEL with feature flag enabled, the cap includes shipping:
if ($this->isDiscountFeatureFlagEnabled() && $this->getType() === DiscountType::ORDER_LEVEL) {
$max_reduction_amount = $this->reduction_tax
? $cart_amount_ti + $context->cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $package_products)
: $cart_amount_te + $context->cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $package_products);
$reduction_amount = min($reduction_amount, $max_reduction_amount);
}
4.3 Current cart amount calculation
When a fixed amount reduction is applied (reduction_amount > 0), the $current_cart_amount calculation now includes shipping costs for ORDER_LEVEL discounts:
if ($this->isDiscountFeatureFlagEnabled() && $this->getType() === DiscountType::ORDER_LEVEL) {
$current_cart_amount += $this->reduction_tax
? $context->cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $package_products)
: $context->cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $package_products);
}
5. Bug fix: variable inconsistency (PR #40424)
PR #40424 by @pjouglet fixes variable usage inconsistency throughout the file.
Before: Some variables were used inconsistently (e.g., mixing $cart_rule->name and htmlspecialchars($cart_rule->name)).
After: Normalization of variable usage throughout the file, particularly in voucher compatibility error messages:
// 9.1.x - with htmlspecialchars for XSS security
return (!$display_error) ? false : $this->trans(
'This voucher is not combinable with an other voucher already in your cart: %s',
[htmlspecialchars($cart_rule->name)],
'Shop.Notifications.Error'
);
6. New discount compatibility system
9.0.x
The compatibility system relies on the ps_cart_rule_combination table and the cart_rule_restriction field. Each forbidden combination is stored as a pair in the table, causing exponential data explosion with many rules.
9.1.x
The new system introduces compatibility by discount type:
- Discounts are no longer ordered by promo code vs. automatic
- Fixed application order:
- Catalog (product) → Cart (cart) → Free Shipping → Free Gift
- Within the same type: sorted by priority (lower = applied first), then by creation date
- Dynamic re-evaluation on each cart modification
7. Impact on CartRuleCalculator.php
While this file is separate from CartRule.php, the changes are intrinsically linked. The CartRuleCalculator (in src/Core/Cart/) integrates the same feature flag checks:
if ($cartRule->getType() === DiscountType::ORDER_LEVEL
&& (float) $cartRule->reduction_percent > 0
&& $cartRule->reduction_product == 0) {
if ($this->isDiscountFeatureFlagEnabled()) {
// New calculation: products + shipping
$initialShippingFees = $this->calculator->getFees()->getInitialShippingFees();
$productsTotal = $this->calculator->getRowTotal();
$orderTotal = $productsTotal->add($initialShippingFees);
// ...
}
}
8. Associated database changes
The 9.1.x introduces schema modifications to support the new discount system (visible in install-dev/upgrade/sql/9.1.0.sql). These changes are linked to the feature flag and don’t alter existing tables as long as the flag remains disabled.
9. Changes summary table
| Aspect | 9.0.x | 9.1.x |
|---|---|---|
| Imports | No Discount dependency | DiscountType, DiscountSettings, FeatureFlagSettings |
| Feature Flag | Absent | isDiscountFeatureFlagEnabled() present |
| Discount typing | Implicit (individual fields) | Explicit via getType() → DiscountType |
| % on ORDER_LEVEL | Products only | Products + shipping |
| Fixed amount cap | Product total | Product total + shipping (ORDER_LEVEL) |
| XSS security messages | $cart_rule->name |
htmlspecialchars($cart_rule->name) |
| Discount compatibility | cart_rule_combination table |
By type with fixed application order |
| Backward compatibility | — | ✅ Flag disabled = 9.0.x behavior |
10. Recommendations for module developers
-
Don’t override
CartRule.phpif you plan to support 9.1.x — the file is actively evolving. - Check the feature flag in your modules if you interact with discounts:
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class); if ($featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT)) { // New system logic } -
Test both modes (flag enabled and disabled) as merchants can switch between both.
-
Use
getType()instead of manually checkingreduction_product,free_shipping,gift_productetc. - Be careful with shipping calculation: if your module calculates discount totals, behavior changes based on discount type in 9.1.x.
Sources and references
- PrestaShop DevDocs — Changes in 9.1.x
- Blog — Improved Discounts System in PrestaShop 9.1
- Blog — PrestaShop 9.1 Beta
- Core Monthly January 2026
- GitHub — CartRule.php (develop/9.1.x)
- GitHub — CartRule.php (9.0.x)
- PR #40424 — Fixed inconsistency use of variable in CartRule.php
Articles Liés
Claude + MCP Tools Plus vs ChatGPT + MCP Tools Plus : Quel assistant IA pour piloter votre boutique PrestaShop en 2026 ?
Claude ou ChatGPT pour gérer votre boutique PrestaShop ? Test comparatif réel avec MCP Tools Plus sur 5 épreuves e-co...
5 révélations surprenantes de la méthode BMAD sur l'avenir du développement de modules
La méthode BMAD révèle des enseignements inattendus sur le futur du développement de modules. De l'IA comme équipe pr...
Analyse Comparative Approfondie des Paradigmes de Développement IA : Prompt Driven Development vs Méthodologie BMAD
L'avènement des Grands Modèles de Langage redéfinit le développement logiciel. Deux écoles s'affrontent : le Prompt D...
Fini le Codeur Solitaire : Pourquoi les Développeurs du Futur seront des Orchestrateurs d'IA (et comment s'y mettre sur PrestaShop)
L'ère du "Léviathan" (une seule IA géante qui fait tout) est une illusion. L'avenir du e-commerce et du développement...
PrestaShop vs Sylius : du module au produit, et si le futur était hybride ?
Un simple module PrestaShop peut révéler deux visions du e‑commerce moderne. En comparant l'approche PrestaShop et Sy...
Google UCP : La fin des marketplaces fermées ? Ce que ça change pour PrestaShop
Google vient de dévoiler le Universal Commerce Protocol (UCP). Une révolution qui permet aux IA d'acheter directement...
Découvrez mes autres articles
Guides e-commerce, tutoriels PrestaShop et bonnes pratiques pour développeurs
Voir tous les articlesPlanification LinkedIn
Date de publication : 5 mars 2026
Temps restant :