2

I am adding a custom fee to the cart by hooking into the woocommerce_cart_calculate_fees action like so:

    if (isset($_GET['discount'])) {



        add_action('woocommerce_cart_calculate_fees', 'add_loyalty_discounts');
    }

    function add_loyalty_discounts(WC_Cart $cart)
    {
        $total = 0;

        global $woocommerce;

        foreach (WC()->cart->get_cart() as $cart_item_key => $item) {

            $total += $item['data']->regular_price;

            $woocommerce->cart->cart_contents[$cart_item_key]['discount_amount'] = $_GET['discount'];
        }

        if ($total < $_GET['discount']) {
            wc_add_notice('You must spend more than €' . $_GET['discount'] . ' to use your loyalty discount.', 'error');
        } else {

            $current_user = wp_get_current_user();

            $loyalty_points = empty(get_user_meta($current_user->ID, 'loyalty_points', true)) ? 0 : get_user_meta($current_user->ID, 'loyalty_points', true);

            $loyalty_points  = 100;

            if ($loyalty_points < $_GET['discount']) {
                wc_add_notice('You do not have enough loyalty points to apply this discount.', 'error');
            } else {

                $cart->add_fee('Loyalty Discount',   -intval($_GET['discount']), true);
            }
        }
    }

The fee appears on the cart page but not persist onto the checkout and order pages.

I've tried using $cart->set_session() but this had no effect.

Update: I have created a fresh WP site with a blank theme (blankslate), and only the latest version of WooCommerce installed and I still get the error.

franka
  • 33
  • 6
  • As this seems a bug related to something else, first make a database backup… Enable WP debug (as explained [**here**](https://stackoverflow.com/a/61754061/3730754)), browse your site with the fee enabled and then check in the debug.log file. Then try to switch to Storefront theme (to see if that is not related to your theme). If it's not the case try to disable all plugins except wooCommerce and check. If the problem is gone, a plugin is making trouble: enable back the plugins one by one and check each time if the fee works… – LoicTheAztec Aug 28 '23 at 11:10
  • 1
    @LoicTheAztec thanks, I have scanned all the plugins and the theme for functions which could effect the cart and have not found anything. I will make a copy of the site and try disabling everything. – franka Aug 28 '23 at 12:42
  • OK, I have created a fresh theme, disabled all the plugins except the latest version of WooCommerce, and it is still not persisting. – franka Aug 30 '23 at 08:37
  • So something is wrong in that fresh theme. Try to upload Storefront theme, and enable it. Add your code in the functions.php file… It should work. – LoicTheAztec Aug 30 '23 at 08:41
  • It's a blank theme, there is nothing in it apart from my code. I will try store front now. – franka Aug 30 '23 at 08:43
  • Ah I've got it, I take it `woocommerce_cart_calculate_fees` runs on the checkout page as well as the cart page, I am only adding the fee on the cart page. – franka Aug 30 '23 at 08:52
  • @LoicTheAztec Does that sound right to you? – franka Aug 30 '23 at 08:53
  • It doesn't make sense to me that the added fee would not persist and need to be readded. – franka Aug 30 '23 at 09:01
  • Maybe I should suggest it to Automattic. – franka Aug 30 '23 at 09:02
  • @LoicTheAztec well no, it wasn't a problem with the theme, it was a miss understanding of how the `WC_Cart` persists fee data. – franka Aug 30 '23 at 09:03
  • I've added a feature request on the WooCommerce github repo. – franka Aug 30 '23 at 09:13
  • The problem comes from the usage of `$_GET['discount']`. I have updated my code, with your newly added code… please have a look to it and try. – LoicTheAztec Aug 30 '23 at 09:44
  • Another quick question: Do you know why `cart->get_total` returns an different amount to the sum of the individual cart item prices? – franka Aug 30 '23 at 12:07
  • You can't use it in that hook, as you will get an infinite loop or a wrong amount, because the fee is added to that total. Always use a subtotal – LoicTheAztec Aug 30 '23 at 12:52

3 Answers3

2

Update (based on your newly added code)

You can't use $_GET, $_POST or $_REQUEST in woocommerce_cart_calculate_fees hook, as those variables will be lost. Instead, you should store your $_GET['discount'] in a WC_Session variable.

Now, your code is outdated, there are some mistakes and missing things.

Try the following revisited code instead:

add_action('template_redirect', 'save_discount_in_session');
function save_discount_in_session() {
    if ( isset($_GET['discount']) ) {
        WC()->session->set('loyalty_discount', floatval($_GET['discount']));
    }
}

add_action('woocommerce_cart_calculate_fees', 'add_loyalty_discounts');
function add_loyalty_discounts($cart){
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    $discount = (float) WC()->session->get('loyalty_discount');
    $subtotal = (float) WC()->cart->subtotal;

    if ( $discount > 0 ) {
        if ( $subtotal >= $discount ) {
            $loyalty_points = (int) get_user_meta(get_current_user_id(), 'loyalty_points', true);
    
            if ( $loyalty_points >= $discount ) {
                $cart->add_fee( __('Loyalty Discount'), -$discount );
            } else {
                wc_clear_notices();
                wc_add_notice( __('You do not have enough loyalty points to apply this discount.'), 'error');
            }
        } else {
            wc_clear_notices();
            wc_add_notice( sprintf( __('You must spend more than %s to use your loyalty discount.'), wc_price($discount) ), 'error');
        }
    }
}

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


To apply the fee only in checkout page, you will use:

add_action('woocommerce_cart_calculate_fees', 'add_loyalty_discounts');
function add_loyalty_discounts($cart) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    if ( is_cart() )
        return;
        
    $cart->add_fee('Loyalty Discount', -100);
}

Related:

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • That doesn't look right to me, why is it returning if it's not admin and not doing ajax? I am testing it using an admin account and it is not running in an ajax call. – franka Aug 28 '23 at 09:49
  • On all that kind of hooks that can be triggered by Ajax, we use that to prevent an error on the backend... See [all that threads using this hook](https://stackoverflow.com/search?q=woocommerce_cart_calculate_fees), most of them include that. Also I know what I am talking about As I have nearly 200 answers using that hook. – LoicTheAztec Aug 28 '23 at 10:08
  • Sorry, I am sure your right, your answers have helped me many times. I just didn't understand why it was returning if an admin user was logged in as I do want the function to execute if I am an admin user. – franka Aug 28 '23 at 10:16
  • Anyway, do you have any idea why I am get an empty array on the checkout page even after updating the `add_fee` function with the `true`. – franka Aug 28 '23 at 10:17
  • The [conditional function `is_admin()`](https://developer.wordpress.org/reference/functions/is_admin/) target the backend, not the "administrator" users. Now in `add_fee()` method the 3rd argument that you have set to `true` is related to taxes (taxable). I can't guess by magic about that issue you are facing, sorry. You may have to enable WP_DEBUG temporarily, as explained [in here](https://stackoverflow.com/a/61754061/3730754), to see if there is not something else making trouble. – LoicTheAztec Aug 28 '23 at 10:43
  • I see, my mistake, I really should know that as I've just used `current_user_can('manage_options')` to check if it is an admin user. You live and you learn. – franka Aug 28 '23 at 14:30
  • Thanks for the update Loic, that is a neat solution to the problem. But I really do think that WooCommerce should persist the fees. Will your code update the Order total or do I need to apply the discount on Order creation? – franka Aug 30 '23 at 11:52
  • And thanks for filling in the missing elements and updating the code. It really is appreciated. – franka Aug 30 '23 at 12:03
  • My thinking is that once the fee has been set it should persist to the other cart stages. I understand that the $_GET variable is not permanent, but if the cart fee persisted in the cart meta data (rather than having to readd the fee at each stage) then there would be no need to store the $_GET variable in the session. – franka Aug 30 '23 at 14:02
  • I'm sorry I cannot make sense of this statement : `but if you add before an if statement that don't persist, the fee doesn't persist either`, surely once the fee is set it should persist. On the cart I am setting a fee in the cart page which is set with the `$_GET` ,that fee does not exist in the checkout page and needs to be readded, I don't think that it should need to be added again. – franka Aug 30 '23 at 14:48
0

I tested and it works fine with me on both cart and checkout page

function add_loyalty_discounts(WC_Cart $cart) {
        $cart->add_fee('Loyalty Discount', -intval(100), true);
}

add_action('woocommerce_cart_calculate_fees', 'add_loyalty_discounts');

0

The reason is was not adding the fee is because I only added the fee on the cart page. The WC_Cart class does not persist fee's.

franka
  • 33
  • 6