Unveiling the Future of Discounts: A Deep Dive into PrestaShop 9.1.x's CartRule.php Evolution
PrestaShop is on the cusp of a major transformation with its 9.1.x branch, particularly concerning how discounts are managed. A brand-new discount system is being introduced, currently safeguarded by a feature flag. This significant architectural shift is meticulously implemented within the classes/CartRule.php file, which forms the bedrock of this modern system while carefully preserving compatibility with existing cart rules.
This article explores the pivotal enhancements within CartRule.php as PrestaShop transitions from version 9.0.x to 9.1.x. We'll examine new dependencies, the integration of feature flags, refined discount typing, and crucial updates to discount calculation methods. This guide is essential for any PrestaShop developer aiming to understand and adapt to these forthcoming changes.
Key Information Sources: PrestaShop DevDocs on 9.1.x changes, Official Blog's insights on the improved discount system, PrestaShop 9.1 Beta announcement, January 2026 Core Monthly, and the GitHub develop branch for
CartRule.php.
1. Essential New Imports and Dependencies
The first indication of a significant shift lies in the file's header.
9.0.x Perspective
In PrestaShop 9.0.x, the CartRuleCore class relied on the standard PrestaShop imports, lacking any direct links to a dedicated discount framework.
9.1.x (Develop Branch) Perspective
The 9.1.x branch introduces several vital use statements at the top of the file, signaling a deeper integration with new core functionalities:
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;
What this means for developers: The CartRule class now seamlessly connects with:
-
DiscountType: A specialized Value Object to precisely categorize discount application levels (e.g.,ORDER_LEVELfor entire cart,PRODUCT_LEVELfor specific items). -
DiscountApplicationService: The central service orchestrating how discounts are applied within this enhanced system. -
FeatureFlagSettings/FeatureFlagStateCheckerInterface: Components enabling dynamic checks to determine if the new discount system is actively enabled.
2. Integrating the Discount Feature Flag
A cornerstone of the 9.1.x update is the strategic use of a feature flag, allowing the new system to coexist with the old one during the transition.
Introducing: isDiscountFeatureFlagEnabled()
A crucial private method has been added in 9.1.x to verify the status of this feature flag:
private function isDiscountFeatureFlagEnabled(): bool
{
return $this->getFeatureFlagManager() !== null
&& $this->getFeatureFlagManager()->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT);
}
Its function: This method effectively serves as a toggle throughout the CartRule.php file. When the flag is inactive (which is the default state), the system's behavior mirrors that of 9.0.x. However, once activated, the advanced new discount logic takes precedence.
The Helper Method: getFeatureFlagManager()
A protected method is now available to retrieve an instance of FeatureFlagStateCheckerInterface from the Symfony container:
protected function getFeatureFlagManager(): ?FeatureFlagStateCheckerInterface
3. The getType() Method: A New Approach to Discount Typing
PrestaShop 9.1.x streamlines how cart rules are categorized through the introduction of a dedicated method.
Added in 9.1.x: getType()
The new getType() method provides a structured way to classify cart rules within the updated discount framework, defining four distinct types:
| Type | Description |
|---|---|
Catalog (PRODUCT_LEVEL) |
Discounts applied to specific products or product categories. |
Cart (ORDER_LEVEL) |
Discounts applied to the overall cart total (excluding shipping). |
| Free Shipping | Offers free shipping for an order. |
| Free Gift | Adds a complimentary product to the order. |
This method yields a DiscountType Value Object, determined by the cart rule's internal setup (e.g., reduction_product, free_shipping, gift_product fields).
Developer impact: Developers can now leverage $cartRule->getType() for querying discount types, simplifying logic that previously required manual checks of various individual fields.
4. Significant Adjustments within getContextualValue()
The getContextualValue() method, central to how discounts are computed, has received some of the most profound modifications.
4.1 Order-Level Percentage Reductions (ORDER_LEVEL)
In 9.0.x: Percentage-based reductions were exclusively applied to the total value of products in the cart.
In 9.1.x: With the feature flag enabled and the discount classified as ORDER_LEVEL, the calculation expands:
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;
}
Crucial update: The percentage discount now encompasses both product totals and shipping expenses, a significant departure from applying only to products.
4.2 Fixed Amount Reduction — Revised Capping Logic
In 9.0.x: The maximum reduction for a fixed amount was capped at the product total.
In 9.1.x: For ORDER_LEVEL discounts with the feature flag active, the cap now includes shipping costs:
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 Refinement
When applying a fixed amount reduction (reduction_amount > 0), the calculation for $current_cart_amount now incorporates 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. Addressing Variable Inconsistency (PR #40424)
A notable bug fix, introduced by PR #40424 from @pjouglet, resolved inconsistencies in variable usage throughout the CartRule.php file.
Before this fix: Developers might have encountered mixed usage of variables, such as $ cart_rule->name and htmlspecialchars($cart_rule->name).
After the fix: Variable usage across the file is normalized, particularly within voucher compatibility error messages, enhancing security against XSS:
// 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. A Revamped Discount Compatibility System
The method for managing discount combinations has undergone a significant overhaul.
9.0.x Compatibility
The previous system relied on the ps_cart_rule_combination table and the cart_rule_restriction field. This approach involved storing each forbidden combination as a pair, which could lead to an exponential increase in data as the number of rules grew.
9.1.x Compatibility
The new system simplifies and enhances compatibility by categorizing discounts by their type:
- Discounts are no longer prioritized based on whether they are promo codes or automatically applied.
- A fixed application order is enforced:
- Catalog (product-level) → Cart (order-level) → Free Shipping → Free Gift.
- Within the same type: Discounts are sorted by priority (lower values apply first), then by their creation date.
- The system performs dynamic re-evaluation with every modification to the cart.
7. Implications for CartRuleCalculator.php
While CartRuleCalculator.php is distinct from CartRule.php, the changes discussed are deeply interconnected. The CartRuleCalculator (found in src/Core/Cart/) now incorporates 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. Corresponding Database Schema Updates
The 9.1.x release also brings database schema modifications to underpin this new discount system. These changes are detailed in install-dev/upgrade/sql/9.1.0.sql and are inherently linked to the feature flag. Notably, they will not alter existing tables as long as the feature flag remains disabled.
9. Snapshot of Key Changes
Here's a concise overview of the differences between the two versions:
| Aspect | PrestaShop 9.0.x | PrestaShop 9.1.x |
|---|---|---|
| Dependencies | No new Discount System tie-ins |
DiscountType, DiscountSettings, FeatureFlagSettings
|
| Feature Flag Support | Absent |
isDiscountFeatureFlagEnabled() available |
| Discount Categorization | Implicit (relying on individual fields) | Explicitly via getType() returning a DiscountType
|
% on ORDER_LEVEL |
Applies only to products | Applies to products + shipping costs |
| Fixed Amount Cap | Limited to product total | Includes product total + shipping (for ORDER_LEVEL) |
| XSS Security (Messages) | Uses $cart_rule->name directly |
Employs htmlspecialchars($cart_rule->name)
|
| Discount Compatibility | Relies on ps_cart_rule_combination
|
By type, with a fixed application order and priority |
| Backward Compatibility | — | ✅ 9.0.x behavior maintained when flag is disabled |
10. Essential Guidance for Module Developers
These changes necessitate a careful approach for anyone developing or maintaining PrestaShop modules. Here are some key recommendations:
- Avoid Overriding
CartRule.php: Given the rapid evolution of this file, directly overridingCartRule.phpfor 9.1.x compatibility is highly discouraged. It's an active development area with frequent updates. -
Incorporate Feature Flag Checks: If your module interacts with discounts, explicitly check the feature flag status:
$featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class); if ($featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT)) { // Implement logic for the new discount system } Thoroughly Test Both Modes: Merchants can toggle the discount feature flag. Ensure your module functions correctly when the flag is both enabled and disabled.
Leverage
getType(): Opt for$cartRule->getType()instead of manually inspecting fields likereduction_product,free_shipping, orgift_product. This will simplify your code and improve future compatibility.Re-evaluate Shipping Calculations: If your module calculates discount totals, be aware that shipping cost inclusion in calculations changes based on the discount type in 9.1.x. Adjust your logic accordingly.
The upcoming changes in PrestaShop 9.1.x, particularly within CartRule.php, represent a significant step towards a more robust and flexible discount management system. By understanding and adapting to these updates, module developers can ensure their creations remain compatible, secure, and performant within the evolving PrestaShop ecosystem.
Stay Ahead in PrestaShop Development!
Want to deepen your understanding of PrestaShop's core mechanics and master development best practices?
Subscribe to Nicolas Dabène's YouTube channel for insightful tutorials and updates: Nicolas Dabène on YouTube
Connect with Nicolas and other industry professionals on LinkedIn to discuss PrestaShop, e-commerce, and development challenges: Nicolas Dabène on LinkedIn
Top comments (0)