0

I need to make discounts to certain user roles of some products, when a certain number of this product is purchased.

The discount should only be applied to the number of products that we have chosen in:

Between 1 and 9 products: no discount.

Product 10: 5% discount on 10 products.

Between 11 & 19 products: 5% discount on first 10 products, no discount for product 11 to 19

20 products: 5% discount on 20 products.

Between 21 & 29 products: 5% discount on first 20 products, no discount for product 21 to 29

30 products : 5% discount on 30 products.

and so on always in batches of 10 (10, 20, 30, 40, ..multiples of 10)

For example: User buys 14 televisions, at a price of €19.00, a 5% discount will be applied to the first 10 units, the remaining 4 do not receive the discount.


I EDIT THE QUESTION

With the help of user @yash's answer, I've managed to get what I'm looking for working somewhat.

Only one type of discount can be applied and to specific products.

As I need to make several types of discounts (buy 10 products and get 5% discount - buy 15 products and get 7% discount - buy 12 products and get 4% discount)

I've tried to clone the function in this answer by changing the name and discount data, but I can't get it to work.

How can I do to achieve this?

I have some other option that always displays a text for the discount, but I can't get the echo discount to save. I want to say that , when I go up from 10 products, the discount disappears until I add more products until I reach 20 products, that the discount corresponding to that amount is added, but in between products from 10 to 20, the discount disappears 5% made to the first 10 products

The discount already obtained must be maintained if we increase the products until we reach the next number that is a multiple of 10, which we would add another 5%. I cannot find the correct form. This next function manages to keep the text in the checkout, but when we have quantities of products that are between 10 and 20, are between 20 and 30, are between 30 and 40, etc., the text is set to zero, without saving the discount already obtained

It's the closest we've come to achieving the goal.

See what I'm doing wrong:

function apply_discount_for_quantity($cart)
{
  if (is_admin()) return;
  global $woocommerce;
  $user = wp_get_current_user();
  $user_roles = $user->roles;
  $discount_products = array(383, 411, 412); // productos elegibles para descuento
  $discount_role = 'wholesale_customer'; // rol de usuario elegible para descuento
  $subtotal = $woocommerce->cart->cart_contents_total;
  $count = $woocommerce->cart->cart_contents_count;
  $discount_percent = 0;
  $eligible_products_count = 0; // variable para contar la cantidad de productos elegibles para descuento
  $discount_applied = 0; // variable para guardar el descuento aplicado

  // cycle through the products in the cart to count thecantidad de productos elegibles
  foreach ($cart->cart_contents as $product) {
    if (in_array($product['product_id'], $discount_products)) {
      $eligible_products_count += $product['quantity'];
    }
  }

  if (in_array($discount_role, $user_roles) && $eligible_products_count > 0) {
    // Change the conditions to apply the discount only in multiples of 10
    if ($eligible_products_count % 10 == 0) {
      $discount_percent = 5;
    }
  }

  // Save the applied discount in a variable
  $discount_applied = $subtotal * ($discount_percent / 100);

  // Add the discount to the cart to always show it, even if it does not apply to quantities that are not multiples of 10
  $cart->add_fee('Descuento por cantidad', -$discount_applied);

  //Apply the discount if necessaryo
  if ($discount_percent > 0) {
    $cart->add_fee('Descuento por cantidad', -$discount_applied);
  }
}
add_action('woocommerce_cart_calculate_fees', 'apply_discount_for_quantity');
unity-student
  • 223
  • 1
  • 13
  • 1
    I don't see a clear [mcve] here. – mickmackusa Apr 20 '23 at 20:30
  • Thanks for the comment. I don't know what else I can add. I'm running this example in my theme's function.php file. There it works, it just doesn't do what I need. You can try this code snippet on any wordpress site, maybe you have a better idea? I have reviewed and I don't understand how I can improve my example. In fact, I have two different examples. tell me if i can do something else – unity-student Apr 20 '23 at 21:29
  • The examples only cause more confusion. You talk about 5%'s and then you start talking about 6%'s and 4%'s. I have no idea what is required. Remove all of the irrelevant WP gobbledygook. Show us a few different, legitimate sample data payloads declared as PHP variables, then show us the exact desired result for each. Simpler for everyone to read and understand. – mickmackusa Apr 20 '23 at 23:10

3 Answers3

2

New answer

I am not removing old answer , so you can compare and understand changes

<?php
/**
 * Applies discounts to specific products in the cart based on quantity and user role.
 *
 * @param WC_Cart $cart The cart object to apply discounts to.
 */
function apply_discount_for_quantity($cart)
{
    // Check if user is an administrator and exit early if true
    if (is_admin()) {
        return;
    }

    // Get the current user's role
    $current_user = wp_get_current_user();
    $user_roles = $current_user->roles;
    
    // Set the user role that is eligible for discounts
    $discount_role = 'wholesale_customer';

    // Product IDs of that has discount on bunch of 9 products
    $discount_products_of_9 = array(383, 411, 412);
    $discount_percentage_for_9 = 4; // change here for discount percentage

    // Product IDs of that has discount on bunch of 10 products
    $discount_products_of_10  = array(384, 421, 422);
    $discount_percentage_for_10 = 5; // change here for discount percentage

    // Product IDs of that has discount on bunch of 12 products
    $discount_products_of_12 = array(385, 432, 432);
    $discount_percentage_for_12 = 6; // change here for discount percentage
    
    // Initialize arrays to hold eligible product data for each discount category
    $eligible_products_data_of_9 = $eligible_products_data_of_10 = $eligible_products_data_of_12 = array();

    $total_discount_amount = 0;
    
    // Loop through cart items to find eligible products
    foreach ($cart->cart_contents as $item) {
        
        if (in_array($item["product_id"], $discount_products_of_9 )) {
            // Calculate eligible quantity and price for discount
            $eligible_products_data_of_9[$item["product_id"]]["price"] = $item[ "data" ]->get_price();
            $eligible_products_data_of_9[$item["product_id"]]["qty"] = $item["quantity"] - ($item["quantity"] % 9); // change 9 with different number you want
        }

        if (in_array($item["product_id"], $discount_products_of_10 )) {
            // Calculate eligible quantity and price for discount
            $eligible_products_data_of_10[$item["product_id"]]["price"] = $item[ "data" ]->get_price();
            $eligible_products_data_of_10[$item["product_id"]]["qty"] = $item["quantity"] - ($item["quantity"] % 10); // change 10 with different number you want
        }

        if (in_array($item["product_id"], $discount_products_of_12 )) {
            // Calculate eligible quantity and price for discount
            $eligible_products_data_of_12[$item["product_id"]]["price"] = $item[ "data" ]->get_price();
            $eligible_products_data_of_12[$item["product_id"]]["qty"] = $item["quantity"] - ($item["quantity"] % 12); // change 12 with different number you want
        }
    }

    // Apply discount if user is eligible and there are eligible products for each discount category
    if (  in_array($discount_role, $user_roles ) && !empty($eligible_products_data_of_9) ) {
        foreach ($eligible_products_data_of_9 as $eligible_product) {
            $total_discount_amount += ($eligible_product["qty"] * $eligible_product["price"] * $discount_percentage_for_9) / 100;
        }
    }
    if (  in_array($discount_role, $user_roles ) && !empty($eligible_products_data_of_10) ) {
        foreach ($eligible_products_data_of_10 as $eligible_product) {
            $total_discount_amount += ($eligible_product["qty"] * $eligible_product["price"] * $discount_percentage_for_10) / 100;
        }
    }
    if (  in_array($discount_role, $user_roles ) && !empty($eligible_products_data_of_12) ) {
        foreach ($eligible_products_data_of_12 as $eligible_product) {
            $total_discount_amount += ($eligible_product["qty"] * $eligible_product["price"] * $discount_percentage_for_12) / 100;
        }
    }

    // Add discount fee to the cart
    $cart->add_fee("Descuento por cantidad", -$total_discount_amount);
}

// Hook the function to apply the discount on cart calculation
add_action( "woocommerce_cart_calculate_fees",  "apply_discount_for_quantity" );

change in product count and discount percentage as per your requerements

I try to make code more understandable , but let me know for any help or any bug/error .

This answer gives final one discount count but has multiple different calculation OR you want different discounts with separate calculation , In both way total sill remains same, only difference is how you show discount to user , if you want second (separate) way then you divide this into different parts that may help & i'm not very strong at testing so bear with me XD :)



Old answer

<?php
/**
 * Applies discount based on the quantity of eligible products in the cart
 *  @param object $cart Cart object
 */
function apply_discount_for_quantity($cart)
{
    if (is_admin()) {
        return;
    }

    $current_user = wp_get_current_user();
    $user_roles = $current_user->roles;

    $discount_products = array(383, 411, 412);
    $discount_role = 'wholesale_customer';
    $discount_percentage = 5;
    $total_discount_amount = 0;
    $eligible_products_data = array();

    // Loop through cart items to find eligible products
    foreach ($cart->cart_contents as $item) {
        if (in_array($item["product_id"], $discount_products )) {
            $eligible_products_data[$item["product_id"]]["price"] = $item[ "data" ]->get_price();
            $eligible_products_data[$item["product_id"]]["qty"] = $item["quantity"] - ($item["quantity"] % 10);
        }
    }

    // Apply discount if user is eligible
    if (  in_array($discount_role, $user_roles ) && !empty($eligible_products_data) ) {
        foreach ($eligible_products_data as $eligible_product) {
            $total_discount_amount += ($eligible_product["qty"] * $eligible_product["price"] * $discount_percentage) / 100;
        }
    }

    // Add discount fee to the cart
    $cart->add_fee("Descuento por cantidad", -$total_discount_amount);
}

// Hook the function to apply the discount on cart calculation
add_action( "woocommerce_cart_calculate_fees",  "apply_discount_for_quantity" );

This are test result of my answer for discount(5%) on different quantity :

Yash
  • 1,020
  • 1
  • 15
  • For some strange reason when I add your example to my function.php, when trying to add products to the cart, it crashes, it doesn't keep loading without adding the products. Until I reload the page. I don't know why this happens, but I can't use your example – unity-student Apr 21 '23 at 23:16
  • 1
    @unity-student , sorry for inconvenience, my answer had wrong function name in `add_action` so now i make it right now. you can check now and one more thing those images example in my answer, are those right or not ? – Yash Apr 22 '23 at 09:00
  • 1
    Thanks for your correction. Yes, the images are correct, that is what we are looking for. It seems that the logic so that the products are not mixed also works. I need to do more testing as If this works I need to clone this feature, we have other different discounts on different products. (for example, if you buy 8 washing machines, you get a 7% discount). I hope this can work and it doesn't create a mess or a mess when several offers accumulate. Do you think there may be a problem when we make several different offers at the same time? I'll do some tests and let you know thanks – unity-student Apr 22 '23 at 10:07
  • 1
    If all requirements are clear then it's possible to " make several different offers at the same time " as you say " different discounts on different products ". After my edit in question , if you need to add new details add those at end, without change in upper part of question [only if possible , this is your question after all :) ]. – Yash Apr 22 '23 at 11:29
  • I have a problem friend @Yash. I need to do other types of discounts,**( 12 products => 5% discount - 9 products => 5% discount)** To achieve this, I have duplicated all the code you have created and renamed the function `apply_discount_for_quantity_second()`, Also in the filter, of course. But the discounts of the second function do not work, only the discount of the first function works: `apply_discount_for_quantity()`. What should I do to correct this? How do I create different types of discounts? – unity-student Apr 24 '23 at 22:24
  • 1
    Can you help me with this error that I mentioned in the previous message? I've done more tests but can't successfully duplicate the functions so that with other discount rates and other products at the same time. I don't know what I can do to duplicate this function that you have created – unity-student Apr 26 '23 at 11:00
  • 1
    @unity-student , pardon me for my late response , checking this right now, if I get solution then i'll update my answer – Yash Apr 26 '23 at 17:07
  • Thank you @yash. You don't have to apologize, I should only apologize for my audacity. Your function works, but I can't duplicate it, I've changed variable names, everything that could cause a conflict when duplicating the function. I have even put the duplicate code in a different file. all unsuccessful – unity-student Apr 26 '23 at 17:42
  • You are amazing @Yash . I still have to test it, but it seems to work. If there are any problems, I tell you where they occur. Thanks for your dedication – unity-student Apr 26 '23 at 22:58
  • I have a question @Yash. Apart from what the code already shows, I also need to add two more types of discount for "1 - 15 products, 5% discount" and another type would be "8 products, 5% discount", can I do it by doubling a of variables like `$discount_products_of_12` for example ? Or is more change needed? Thank you – unity-student Apr 27 '23 at 09:47
  • 1
    @unity-student , yes you archive it by same way , although if you stuck anywhere point out specific error/difficulty. – Yash Apr 27 '23 at 16:45
  • If I'm stuck @Yash. I have doubled the variables `$discount_products_of_10` and also `$discount_percentage_for_10` twice and changed the numbers to 15 and 8 which is the amount for discount. But the problem is generated with three other functions that I have to generate quantity discounts, but in this case the discount is based on a quantity. From 15, from 10, from 12 products, 5% is made to the assigned products. The problem is to join the discounts in `$cart->add_fee("Amount discount", -$total_discount_amount);` and display all the results in this variable. Do you have any idea to do this? – unity-student Apr 27 '23 at 17:51
  • 1
    Check in 'new answer' code where all discount are added in one variable using `+=` operation , is that what you want ? I did not fully understand what you are saying. In "new answer" code you found discount on 9 product, 10 product and 12 product and you can also set percentage for that individually. "Give me product numbers and their discount I'll made change in code according to that" because sometimes you say 8 product sometime 12 product so I'm confused – Yash Apr 28 '23 at 17:25
  • Thanks for your support. I have created three new discounts (only for certain products) that have other rules and are the following: (buy 15 or more, 5% discount) - (buy 12 units or more, 5% discount) - (buy 10 units or more, discount of 5%). These discounts are shown in a different cell of the Checkout than the discounts that you have created. [A catch](https://snipboard.io/wZrYb0.jpg). And you can see the code that I am using. Can your code create problems with mine? Would it be better to join it? what do you think ? [look at the code i use](https://rextester.com/KEBC85326) – unity-student Apr 28 '23 at 23:13
  • 1
    My code have no effect on your code and in my opinion both are working then we don't need to join it because both discount logic work differently , if we join them maintain code in future will be difficult because of upcoming changes. – Yash Apr 29 '23 at 10:03
  • Thank you, is it possible to show the result of your discounts, only if they add products that have a discount and only if there are the necessary units to activate the discount? Now the text: "Descuento por cantidad" is always shown at checkout. This is not voluntary, we must avoid showing users who do not have a `'wholesale_customer'` role. Ideally, this user role should only display `'wholesale_customer'`, and only when adding readable discount products. Shall I ask a new question? I have tried to modify it myself, following the way my discount code has, but it doesn't work. Thank you – unity-student Apr 29 '23 at 22:31
  • I think I've fixed it @Yash, with the variable $total_discount_amount like this, what do you think, is it safe like this? `if ( $total_discount_amount > 0) { // Agregar tarifa de descuento al carrito $cart->add_fee("Descuento por cantidad", -$total_discount_amount); }` – unity-student Apr 30 '23 at 00:14
0

This is the way I would go about this problem:

  1. Gather the numbers of items to be discounted
  2. Integer divide the total number of items by $required_quantity
  3. Take and multiply that number by recalculated value of the discount for each set of required quantity.

I would also determine if they were a subscriber before passing the cart to the apply_discount function.

A simple version of this would be:

function apply_discount($cart): float {
    $required_quantity = 11; 
    // for this case I am going to assume €19.00 each at 4% discount.
    $block_discount = 19 * 11 * 0.04;
                
    foreach( $cart->get_cart() as $cart_item ){
        $discount = 0
        if(in_array( $cart_item['product_id'], $product_ids )) {
            $discount = $block_discount * intdiv($cart_item['quantity']), $required_quantity);
        }
        if($discount > 0 ){
            $cart->add_fee( 'Discount', -$discount, true, 'standard' );
        }
    }    
}

As for the €88 discount that is roughly 10x what it should be. My guess is $discount = $cart_item['quantity'] * 10;

Meechew
  • 49
  • 5
  • Thanks for your answer @meechew. I'm looking for the for to add to neither example and I don't see how. In your example, I can't find a way to show the user role, nor distinguish the products that should carry the discount. I can't really do anything with it. As you can see, I've added a new example that a friend created, and it's kind of what we need. – unity-student Apr 20 '23 at 06:56
0

As I can see in your last exemple

First you can break the logic earlier for

if (!in_array($discount_role, $user_roles)) { return ;}

As you will not apply discount for them

Then you do :

if ($eligible_products_count % 10 == 0) {
      $discount_percent = 5;
    }

If you have 31 eligible products you will got 1 == 0 // false and your discount percent will not change and stay to 0

In your code, only some product can be eligible to trigger the discount, it's look like you will apply the discount to the not eligible product. You should put some of then in your test cart.

So look like to apply more than one discount like Meechew do.

Here my try :

function apply_discount_for_quantity($cart)
        {
            // For sample, I let you choose of to define the rules and send it here
            $discount_rules = [
                ['eligible_product_ids' => [383, 411], 'required_count' => 12, 'discount_percent' => 4],
                ['eligible_product_ids' => [412], 'required_count' => 10, 'discount_percent' => 5],
            ];
            
            $user = wp_get_current_user();
            $user_roles = $user->roles;
            $discount_role = 'wholesale_customer'; // rol de usuario elegible para descuento

            if (!in_array($discount_role, $user_roles)) {
                return;
            }
            
            // Will use counter per lligible product
            $discounts_counter_cache = [];
            
            // cycle through the products in the cart to count thecantidad de productos elegibles
            foreach ($cart->cart_contents as $product) {
                
                //You can put this one on another function to make it more readble)
                foreach ($discount_rules as $rule) {
                    // if product is eligible to one of our discount rule
                    if (in_array($product['product_id'], $rule['eligible_product_ids'])) {

                        // let's count of many product whe got 
                        $discounts_counter_cache[ $product['product_id'] ] = ($discounts_counter_cache[ $product['product_id'] ] ?? 0) + 1;

                        // Apply per elligible product only when it's reach the limit
                        if ($discounts_counter_cache[ $product['product_id'] ] == $rule['required_count']) {
                            // We trigger the discount 
                            $cart->add_fee(
                                'Descuento por cantidad: ' . $rule['discount_percent'] .'% por '. $rule['required_count'] .'x'. $product['product_id'],
                                // You can also set the value in the rule 
                                -($product['product_price'] * $rule['required_count'] * ($rule['discount_percent'] / 100))
                            );
                            
                            // Reset counter
                            $discounts_counter_cache[ $product['product_id'] ] = 0;
                        }
                    }
                }
            }
        }

hope it will help you to found what's you need

Here sample test (test it: https://3v4l.org/ot8A5h):

<?php

class Cart 
{
    protected array $cart = [];
    protected array $fees = [];
    
    public function __construct()
    {
        // generate test Cart
        $testProducts = [
            ['id' => 1 , 'nb' => 22, 'price' => 23],
            ['id' => 383 , 'nb' => 38, 'price' => 19],
            ['id' => 412, 'nb' => 11, 'price' => 34],
        ];
        foreach ($testProducts as $testProduct) {
            while ($testProduct['nb']-- > 0) {
                $this->addProduct($testProduct['id'], $testProduct['price']);
            }
        }
    }
    
    public function cart_contents(): array
    {
        return $this->cart;
    }
    
    public function addProduct(int $id, int $price)
    {
        $this->cart[] = ['product_id' => $id, 'price' => $price];
    }
    
    public function add_fee(string $txt, float $price)
    {
        $this->fees[] = ['txt' => $txt, 'price' => $price];
    }
    
    function apply_discount_for_quantity()
    {
            // For sample, I let you choose of to define the rules and send it here
            $discount_rules = [
                ['eligible_product_ids' => [383, 411], 'required_count' => 12, 'discount_percent' => 4],
                ['eligible_product_ids' => [412], 'required_count' => 10, 'discount_percent' => 5],
            ];
            
            // Will use counter per lligible product
            $discounts_counter_cache = [];
            
            // cycle through the products in the cart to count thecantidad de productos elegibles
            foreach ($this->cart_contents() as $product) {
                
                foreach ($discount_rules as $rule) {
                    // if product is eligible to one of our discount rule
                    if (!in_array($product['product_id'], $rule['eligible_product_ids'])) {
                        // product not eligible for a discount
                        continue;
                    }

                    // let's count of many product whe got 
                    $discounts_counter_cache[ $product['product_id'] ] = ($discounts_counter_cache[ $product['product_id'] ] ?? 0) + 1;

                    // Apply per elligible product only when it's reach the limit
                    if ($discounts_counter_cache[ $product['product_id'] ] == $rule['required_count']) {
                        echo sprintf(' -- Found discount of %d%% to apply on the batch of %d product (id %d) in the cart' . PHP_EOL,
                            $rule['discount_percent'],
                            $rule['required_count'],
                            $product['product_id']
                        );
                        // We trigger the discount 
                        $this->add_fee(
                            'Descuento por cantidad: ' . $rule['discount_percent'] .'% por '. $rule['required_count'] .'x'. $product['product_id'],
                            // You can also set the value in the rule 
                            -($product['price'] * $rule['required_count'] * ($rule['discount_percent'] / 100))
                        );
                        
                        // Reset counter
                        $discounts_counter_cache[ $product['product_id'] ] = 0;
                    }
                    
                }
            }
            
        }
    
    public function show_cart()
    {
        
        $products = [];
        foreach ($this->cart as $entry) {
            if (!isset($products[$entry['product_id']])) {
                $products[$entry['product_id']] = [
                    'id' => $entry['product_id'],
                    'nb' => 0,
                    'price' => $entry['price'],
                    'total' => $entry['price'],
                ];
            }
            $products[$entry['product_id']]['nb']++;
            $products[$entry['product_id']]['price'] += $entry['price'];
        }
        echo 'Cart:'. PHP_EOL;
        foreach ($products as $product) {
            echo sprintf(
                '> Product Id:"%d" - %d$ x %d = %d$'.PHP_EOL,
                $product['id'],
                $product['price'],
                $product['nb'],
                $product['total']
            );
           
        }
        echo PHP_EOL;
        if (!empty($this->fees)) {
            echo 'Fees:'.PHP_EOL;
            $totalFees = ['nb' => 0, 'total' => 0];
            foreach ($this->fees as $fee) {
                $totalFees['nb']++;
                $totalFees['total']+= $fee['price'];
                echo sprintf(
                 '> %d$ - %s' . PHP_EOL,
                 $fee['price'],
                 $fee['txt']
                );
            }
            echo sprintf(
                 '>> Total of %d discounts for %d$' . PHP_EOL,
                 $totalFees['nb'],
                 $totalFees['total']
                );
        }
        
    }
            
}
        
        
 $cart = new Cart();
 echo 'Before discount:'.PHP_EOL;
 echo $cart->show_cart();
 $cart->apply_discount_for_quantity();
 echo PHP_EOL.'After discount:'.PHP_EOL;
 echo $cart->show_cart();

Result:

Before discount: Cart:

Product Id:"1" - 529$ x 22 = 23$ Product Id:"383" - 741$ x 38 = 19$ Product Id:"412" - 408$ x 11 = 34$

-- Found discount of 4% to apply on the batch of 12 product (id 383) in the cart -- Found discount of 4% to apply on the batch of 12 product (id 383) in the cart -- Found discount of 4% to apply on the batch of 12 product (id 383) in the cart -- Found discount of 5% to apply on the batch of 10 product (id 412) in the cart

After discount: Cart:

Product Id:"1" - 529$ x 22 = 23$ Product Id:"383" - 741$ x 38 = 19$ Product Id:"412" - 408$ x 11 = 34$

Fees:

-9$ - Descuento por cantidad: 4% por 12x383 -9$ - Descuento por cantidad: 4% por 12x383 -9$ - Descuento por cantidad: 4% por 12x383 -17$ - Descuento por cantidad: 5% por 10x412

Total of 4 discounts for -44$

Gectou4
  • 219
  • 1
  • 5
  • I did tests with your sample and it does not give any discount. For some reason it doesn't work for me. What we are looking for is to discount quantities that are multiples of 10, there is no discount for the rest, but the discount obtained, for example, when they reach 20 products, must be maintained, even if the sum of products eligible for discount is 11, 12, 15, 19, etc, or when the amount stops at 20, the discount made to the amount of 20 must be maintained, even if it exceeds 21, 22, 24, 25, etc, etc... – unity-student Apr 21 '23 at 11:22
  • Here is test with sample result https://3v4l.org/ot8A5h May you need to adapt input data to your WP oocommerce ? Or may our sample data isn't the good one, you can edit it and check input data from the linked test on 3V4L and let us know what we miss – Gectou4 Apr 21 '23 at 13:27