phpwordpresswoocommercecartfee

WooCommerce programmatically added fee don't persists


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.


Solution

  • 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: