Home / Forums / WoodMart support forum / Dynamic discounts not applied across multiple products (combined quantity issue)
Home › Forums › WoodMart support forum › Dynamic discounts not applied across multiple products (combined quantity issue)
Dynamic discounts not applied across multiple products (combined quantity issue)
- This topic has 3 replies, 2 voices, and was last updated 2 months, 3 weeks ago by
Aizaz Imtiaz Awan.
-
AuthorPosts
-
April 6, 2026 at 10:10 pm #715273
user67844ParticipantHi,
I think there is an issue or limitation with the Dynamic Discounts feature in WoodMart.
I created a discount rule applied to multiple products. However, the discount only works when the required quantity is reached per individual product, not when the total quantity across different products meets the condition.Example:
Product A → quantity: 3
Product B → quantity: 3
Discount rule → apply discount starting from quantity 6I tested both options:
Individual product
Individual variationBut in both cases, quantities are not combined across products included in the same rule.
Is this a bug, or is this behavior intended?If intended, is there any way to:
– Apply discounts based on the total quantity of all products included in the rule, or
– Enable cumulative quantity calculation across multiple products?This feature would be very useful for bundle-like promotions (e.g. buy any 6 items from a selection and get a discount).
Thanks in advance for your help.
April 7, 2026 at 2:19 pm #715340Hello,
WoodMart Dynamic Discounts are designed to work on a per-product basis, meaning the quantity is calculated individually for each product. Even if multiple products are included in the same rule, their quantities are not combined.
Currently, WoodMart Dynamic Discounts do not support cumulative quantity across multiple products within a single rule.To achieve “Buy any 6 items from selected products” type logic, you would need a plugin for this purpose that best suits your requirements.
Best Regards
April 7, 2026 at 8:47 pm #715377
user67844ParticipantHi ! Thanks for clarification, it’s what I thought then, I don’t know If it’s allowed but I have made a custom code with a friend and Claude Code. If anyone want to achieve the same things :
<?php if ( ! defined( 'ABSPATH' ) ) { exit; } use XTS\Admin\Modules\Options\Metaboxes; use XTS\Modules\Dynamic_Discounts\Main; use XTS\Modules\Dynamic_Discounts\Manager; if ( ! class_exists( 'Custom_WoodMart_Combined_Dynamic_Discounts' ) ) { class Custom_WoodMart_Combined_Dynamic_Discounts { /** * Produits déjà traités pendant le cycle courant. * * @var array */ public $applied = array(); public function __construct() { add_action( 'init', array( $this, 'bootstrap' ), 200 ); } public function bootstrap() { if ( ! function_exists( 'woodmart_get_opt' ) || ! woodmart_get_opt( 'discounts_enabled' ) ) { return; } if ( ! class_exists( Main::class ) || ! class_exists( Manager::class ) || ! class_exists( Metaboxes::class ) ) { return; } $this->extend_native_discount_quantities_field(); $this->replace_front_discount_calculation(); } /** * Ajoute l'option au vrai champ natif WoodMart. */ private function extend_native_discount_quantities_field() { $metabox = Metaboxes::get_metabox( 'xts_woo_discounts_meta_boxes' ); if ( ! $metabox ) { return; } $fields = $metabox->get_fields(); if ( empty( $fields ) || ! is_array( $fields ) ) { return; } foreach ( $fields as $field ) { if ( empty( $field->args['id'] ) || 'discount_quantities' !== $field->args['id'] ) { continue; } if ( empty( $field->args['options'] ) || ! is_array( $field->args['options'] ) ) { $field->args['options'] = array(); } $field->args['options']['combined_matching_products'] = array( 'name' => esc_html__( 'Combined matching products', 'custom-woodmart' ), 'value' => 'combined_matching_products', ); if ( ! empty( $field->args['description'] ) ) { $field->args['description'] .= ' '; } $field->args['description'] .= esc_html__( 'Choose "Combined matching products" to add together all cart items matching the same WoodMart discount rule.', 'custom-woodmart' ); break; } } /** * Remplace le calcul natif WoodMart. */ private function replace_front_discount_calculation() { global $wp_filter; if ( isset( $wp_filter['woocommerce_before_calculate_totals'] ) ) { foreach ( $wp_filter['woocommerce_before_calculate_totals']->callbacks as $priority => $callbacks ) { foreach ( $callbacks as $callback ) { if ( isset( $callback['function'] ) && is_array( $callback['function'] ) && is_object( $callback['function'][0] ) && $callback['function'][0] instanceof Main && 'calculate_discounts' === $callback['function'][1] ) { remove_action( 'woocommerce_before_calculate_totals', array( $callback['function'][0], 'calculate_discounts' ), $priority ); } } } } add_action( 'woocommerce_before_calculate_totals', array( $this, 'calculate_discounts' ), 10, 1 ); } /** * Restaure les prix d'origine avant recalcul pour éviter l'application multiple. */ private function restore_original_prices( $cart ) { foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) { if ( isset( $cart_item['custom_wd_original_price'] ) && isset( $cart_item['data'] ) && is_a( $cart_item['data'], 'WC_Product' ) ) { $original_price = (float) $cart_item['custom_wd_original_price']; $cart->cart_contents[ $cart_item_key ]['data']->set_price( $original_price ); $cart->cart_contents[ $cart_item_key ]['data']->set_regular_price( $original_price ); $cart->cart_contents[ $cart_item_key ]['data']->set_sale_price( '' ); } } } /** * Copie exacte de la logique WoodMart avec un seul ajout : * le mode "combined_matching_products". * * @param WC_Cart $cart Cart. * @return void */ public function calculate_discounts( $cart ) { // Compatibilité WooCommerce Multilingual, identique à WoodMart. if ( class_exists( 'woocommerce_wpml' ) && ! defined( 'PAYPAL_API_URL' ) && doing_action( 'woocommerce_cart_loaded_from_session' ) ) { return; } if ( ! $cart || $cart->is_empty() ) { return; } $this->applied = array(); // Important : on repart toujours du prix d'origine. $this->restore_original_prices( $cart ); $variations_quantity = array(); $combined_rule_quantities = array(); /** * 1) Première passe : mêmes bases que WoodMart + calcul du total par règle. */ foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) { if ( empty( $cart_item['data'] ) || ! is_a( $cart_item['data'], 'WC_Product' ) ) { continue; } if ( isset( $cart_item['wd_is_free_gift'] ) || isset( $cart_item['wd_fbt_bundle_id'] ) ) { continue; } $product = $cart_item['data']; if ( 'variation' === $product->get_type() ) { if ( ! isset( $variations_quantity[ $cart_item['product_id'] ] ) ) { $variations_quantity[ $cart_item['product_id'] ] = 0; } $variations_quantity[ $cart_item['product_id'] ] += (int) $cart_item['quantity']; } $discount = Manager::get_instance()->get_discount_rules( $product ); if ( empty( $discount ) || empty( $discount['post_id'] ) ) { continue; } $mode = ! empty( $discount['discount_quantities'] ) ? $discount['discount_quantities'] : 'individual_variation'; if ( 'combined_matching_products' === $mode ) { $rule_id = (int) $discount['post_id']; if ( ! isset( $combined_rule_quantities[ $rule_id ] ) ) { $combined_rule_quantities[ $rule_id ] = 0; } $combined_rule_quantities[ $rule_id ] += (int) $cart_item['quantity']; } } /** * 2) Deuxième passe : logique WoodMart quasi inchangée. */ foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) { if ( empty( $cart_item['data'] ) || ! is_a( $cart_item['data'], 'WC_Product' ) ) { continue; } $product = $cart_item['data']; if ( isset( $cart_item['wd_is_free_gift'] ) || isset( $cart_item['wd_fbt_bundle_id'] ) ) { continue; } $item_quantity = $cart_item['quantity']; // On repart du prix courant restauré, exactement comme WoodMart. $product_price = apply_filters( 'woodmart_pricing_before_calculate_discounts', (float) $product->get_price( 'edit' ), $cart_item ); $original_price = $product_price; $discount = Manager::get_instance()->get_discount_rules( $product ); if ( empty( $product_price ) || empty( $discount ) || ( ! empty( $this->applied ) && in_array( $product->get_id(), $this->applied, true ) ) ) { continue; } // On mémorise le vrai prix d'origine pour éviter le double rabais au prochain recalcul. $cart->cart_contents[ $cart_item_key ]['custom_wd_original_price'] = $original_price; // Logique native WoodMart. if ( ! empty( $variations_quantity ) && 'individual_product' === $discount['discount_quantities'] && in_array( $product->get_parent_id(), array_keys( $variations_quantity ), true ) ) { $item_quantity = $variations_quantity[ $product->get_parent_id() ]; } // Notre seul ajout. if ( 'combined_matching_products' === $discount['discount_quantities'] && ! empty( $discount['post_id'] ) && isset( $combined_rule_quantities[ (int) $discount['post_id'] ] ) ) { $item_quantity = $combined_rule_quantities[ (int) $discount['post_id'] ]; } switch ( $discount['_woodmart_rule_type'] ) { case 'bulk': foreach ( $discount['discount_rules'] as $key => $discount_rule ) { if ( $discount_rule['_woodmart_discount_rules_from'] <= $item_quantity && ( $item_quantity <= $discount_rule['_woodmart_discount_rules_to'] || ( array_key_last( $discount['discount_rules'] ) === $key && empty( $discount_rule['_woodmart_discount_rules_to'] ) ) ) ) { $discount_type = $discount_rule['_woodmart_discount_type']; $discount_value = $discount_rule[ '_woodmart_discount_' . $discount_type . '_value' ]; // Compatibilité WPML identique à WoodMart. if ( class_exists( 'woocommerce_wpml' ) && 'amount' === $discount_type ) { $discount_value = apply_filters( 'woodmart_product_pricing_amount_discounts_value', $discount_value ); } $product_price = Manager::get_instance()->get_product_price( $product_price, array( 'type' => $discount_type, 'value' => $discount_value, ) ); // Normalement une seule règle match, donc on sort. break; } } break; } $product_price = apply_filters( 'woodmart_pricing_after_calculate_discounts', $product_price, $cart_item ); if ( $product_price < 0 ) { $product_price = 0; } if ( (float) $product_price === (float) $original_price ) { continue; } $product->set_regular_price( $original_price ); $product->set_price( $product_price ); $product->set_sale_price( $product_price ); $this->applied[] = $product->get_id(); } } } new Custom_WoodMart_Combined_Dynamic_Discounts(); }April 8, 2026 at 12:04 pm #715425Hello,
Thank you for the detailed update and for sharing your findings.
Feel free to write back anytime. If you need further assistance, we are always here to help you.
Thanks for contacting us.
Have a great day.Topic Closed.
Best Regards. -
AuthorPosts
The topic ‘Dynamic discounts not applied across multiple products (combined quantity issue)’ is closed to new replies.
- You must be logged in to create new topics. Login / Register