skip to Main Content

I’m working on a WordPress site with WooCommerce and facing a challenge in applying discounts to the cart based on specific product attributes and user roles. The discounts should apply to products with a custom size attribute (e.g., ‘S (23 x 15 CM)’, ‘M (50 x 15 CM)’, etc.) and only for users with specific roles (‘distributor’ and ‘dr52’).

I need to apply different discount prices based on the total quantity of products in the cart and their size. For example, if there are more than 20 products in total (which could be of different sizes), a specific price should be applied.

I’ve attempted several solutions to modify prices in the cart, including directly changing the product prices based on total quantity and size. However, the prices in the cart are not updating as expected.

$size_based_discounts = [
    'S (23 x 15 CM)' => [7.80, 7.02, 6.38, 5.85],
    'M (50 x 15 CM)' => [10.10, 9.09, 8.26, 7.57],
    'L (28 x 26 CM)' => [11.01, 9.91, 9.01, 8.26],
    'Baby (25 x 12 CM)' => [7.80, 7.02, 6.38, 5.85]
];

function is_distributor_or_dr52() {
    $user = wp_get_current_user();
    return in_array('distributor', $user->roles) || in_array('dr52', $user->roles);
}

function count_products_by_size_in_cart($size_based_discounts) {
    $total_quantity = 0;
    foreach ( WC()->cart->get_cart() as $cart_item ) {
        if ( ! isset( $cart_item['data'] ) ) {
            continue;
        }
        $product = $cart_item['data'];
        $size = $product->get_attribute('pa_tamano');
        if ( array_key_exists( $size, $size_based_discounts ) ) {
            $total_quantity += $cart_item['quantity'];
        }
    }
    return $total_quantity;
}

function apply_discount_by_quantity($cart) {
    if ( ! is_distributor_or_dr52() ) {
        return;
    }

    global $size_based_discounts;
    $total_quantity = count_products_by_size_in_cart($size_based_discounts);

    $discount_range = 0;
    if ( $total_quantity > 49 ) {
        $discount_range = 3;
    } elseif ( $total_quantity > 27 ) {
        $discount_range = 2;
    } elseif ( $total_quantity > 13 ) {
        $discount_range = 1;
    }

    foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
        $product = $cart_item['data'];
        if ( $product->is_type( 'variable' ) ) {
            $size = $product->get_attribute('pa_tamano');
            if ( array_key_exists( $size, $size_based_discounts ) ) {
                $discounted_price = $size_based_discounts[$size][$discount_range];
                $cart_item['data']->set_price( $discounted_price );
            }
        }
    }
}
add_action( 'woocommerce_before_calculate_totals', 'apply_discount_by_quantity', 10, 1 );

function show_original_price_struck_in_cart($price, $cart_item, $cart_item_key) {
    if ( ! is_distributor_or_dr52() ) {
        return $price;
    }

    if ( isset( $cart_item['original_price'] ) ) {
        $original_price = $cart_item['original_price'];
        $discounted_price = $cart_item['data']->get_price();
        if ( $original_price > $discounted_price ) {
            return '<del>' . wc_price( $original_price ) . '</del> ' . wc_price( $discounted_price );
        }
    }
    return $price;
}
add_filter( 'woocommerce_cart_item_price', 'show_original_price_struck_in_cart', 10, 3 );

While the logic to calculate the total quantity and discount range seems to work (as per debugging logs), the prices in the WooCommerce cart do not reflect the calculated discounts. Interestingly, a test with a simple function to set all prices in the cart to a fixed value (e.g., 1€) worked correctly, suggesting that modifying prices in the cart is feasible in the current setup.

Attempts at Resolution:

Review of discount logic and attribute verification.
Use of different WooCommerce hooks (woocommerce_before_calculate_totals, woocommerce_cart_calculate_fees).
Testing with simplified logic to rule out conflicts with other plugins or the theme.

Is there anything in my current approach that I might be overlooking or that could be causing the prices not to update correctly in the cart?

Are there any known interactions or limitations when modifying prices in the WooCommerce cart that might be affecting this functionality?

2

Answers


  1. Chosen as BEST ANSWER

    I'm back to share the successful resolution of my WooCommerce pricing challenge, thanks in part to the valuable input from @LoicTheAztec. The key issue was correctly identifying the product type in our logic.

    The main problem was in handling product types. Our initial approach incorrectly checked for products of type 'variable' when it should have been 'variation', as the discounts were applicable to product variations based on size attributes.

    The critical change was in apply_discount_by_quantity, where we switched the check from 'variable' to 'variation':

    function size_based_discounts() {
        return [
            'S (23 x 15 CM)' => [7.80, 7.02, 6.38, 5.85],
            'M (50 x 15 CM)' => [10.10, 9.09, 8.26, 7.57],
            'L (28 x 26 CM)' => [11.01, 9.91, 9.01, 8.26],
            'Bebé (25 x 12 CM)' => [7.80, 7.02, 6.38, 5.85]
        ];
    }
    
    function is_allowed_user_role() {
        if (!is_user_logged_in()) return false;
        $user = wp_get_current_user();
        return in_array('distribuidores', $user->roles) || in_array('dr52', $user->roles);
    }
    
    function count_products_by_size_in_cart() {
        $total_quantity = 0;
        foreach (WC()->cart->get_cart() as $item) {
            $size = $item['data']->get_attribute('pa_tamano');
            if (array_key_exists($size, size_based_discounts())) {
                $total_quantity += $item['quantity'];
            }
        }
        return $total_quantity;
    }
    
    function get_discount_range() {
        $total_quantity = count_products_by_size_in_cart();
    
        if ($total_quantity > 149) {
            return 3;
        } elseif ($total_quantity > 49) {
            return 2;
        } elseif ($total_quantity > 27) {
            return 1;
        } elseif ($total_quantity > 13) {
            return 0;
        } else {
            return -1;
        }
    }
    
    add_action('woocommerce_before_calculate_totals', 'apply_discount_by_quantity', 10, 1);
    function apply_discount_by_quantity($cart) {
        if (is_admin() && !defined('DOING_AJAX')) return;
        if (!is_allowed_user_role()) return;
    
        $size_based_discounts = size_based_discounts();
        $discount_range = get_discount_range();
    
        foreach ($cart->get_cart() as $item) {
            $product = $item['data'];
            if ($product->is_type('variation')) {
                $size = $product->get_attribute('pa_tamano');
                if (array_key_exists($size, $size_based_discounts) && $discount_range >= 0) {
                    $product->set_price($size_based_discounts[$size][$discount_range]);
                }
            }
        }
    }
    
    add_filter('woocommerce_cart_item_price', 'show_original_price_struck_in_cart', 10, 3);
    function show_original_price_struck_in_cart($price, $cart_item, $cart_item_key) {
        if (!is_allowed_user_role()) return $price;
    
        $product = $cart_item['data'];
        $original_price = $product->get_regular_price();
        $discounted_price = $product->get_price();
    
        if ($original_price > $discounted_price) {
            return '<del>' . wc_price($original_price) . '</del> ' . wc_price($discounted_price);
        }
    
        return $price;
    }
    

    A huge shoutout to @LoicTheAztec for pointing me in the right direction. Your insights on WooCommerce internals were incredibly helpful.

    This solution is now working flawlessly on my site, applying discounts correctly to variable product variations and displaying prices as intended. I hope this helps anyone facing a similar issue!


  2. Try the following revisited code instead (untested):

    function size_based_discounts() {
        return [
            'S (23 x 15 CM)' => [7.80, 7.02, 6.38, 5.85],
            'M (50 x 15 CM)' => [10.10, 9.09, 8.26, 7.57],
            'L (28 x 26 CM)' => [11.01, 9.91, 9.01, 8.26],
            'Baby (25 x 12 CM)' => [7.80, 7.02, 6.38, 5.85]
        ];
    } 
    
    function is_allowed_user_role() {
        if ( ! is_user_logged_in() ) return false;
        global $current_user;
        return count( array_intersect(['distributor', 'dr52'], $current_user->roles) ) > 0;
    }
    
    function count_products_by_size_in_cart() {
        $total_quantity = 0;
    
        foreach ( WC()->cart->get_cart() as $item ) {
            $size = $item['data']->get_attribute('pa_tamano');
    
            if ( array_key_exists( $size, size_based_discounts() ) ) {
                $total_quantity += $item['quantity'];
            }
        }
        return $total_quantity;
    }
    
    function get_discount_range() {
        $total_quantity = count_products_by_size_in_cart();
    
        if ( $total_quantity > 49 ) {
            return 3;
        } elseif ( $total_quantity > 27 ) {
            return 2;
        } elseif ( $total_quantity > 13 ) {
            return 1;
        } else return 0;
    }
    
    add_action( 'woocommerce_before_calculate_totals', 'apply_discount_by_quantity', 10, 1 );
    function apply_discount_by_quantity( $cart ) {
        if ( is_admin() && !defined('DOING_AJAX') )
            return;
    
        if ( ! is_allowed_user_role() ) 
            return;
    
        $size_based_discounts = size_based_discounts();
        $discount_range = get_discount_range();
    
        foreach ( $cart->get_cart() as $item ) {
            if ( $item['data']->is_type( 'variable' ) ) {
                $size = $item['data']->get_attribute('pa_tamano');
    
                if ( array_key_exists( $size, $size_based_discounts ) ) {
                    $item['data']->set_price( $size_based_discounts[$size][$discount_range] );
                }
            }
        }
    }
    
    add_filter( 'woocommerce_cart_item_price', 'show_original_price_struck_in_cart', 10, 2 );
    function show_original_price_struck_in_cart( $price, $item ) {
    
        if ( is_allowed_user_role() && $item['data']->is_type( 'variable' ) ) {
            $size_based_discounts = size_based_discounts();
            $discount_range = get_discount_range();
            $size = $item['data']->get_attribute('pa_tamano');
    
            if ( array_key_exists( $size, $size_based_discounts ) ) {
                $args = array('price' => floatval($size_based_discounts[$size][$discount_range]));
                if ('incl' === get_option('woocommerce_tax_display_cart')) {
                    $discounted_price = wc_get_price_including_tax($item['data'], $args);
                    $original_price   = wc_get_price_including_tax($item['data']);
                } else {
                    $discounted_price = wc_get_price_excluding_tax($item['data'], $args);
                    $original_price   = wc_get_price_excluding_tax($item['data']);
                }
                if ( $original_price > $discounted_price ) {
                    $price = wc_format_sale_price( $original_price, $discounted_price );
                }
            }
        }
        return $price;
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). It should work.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search