phpwordpresswoocommercecartorders

WooCommerce cart item discount based on user role issue


I am using the following code in WooCommerce to add a discount based on user roles:

//DISCOUNT
add_filter('woocommerce_add_cart_item_data', 'add_items_default_price_as_custom_data', 20, 3 );
function add_items_default_price_as_custom_data( $cart_item_data, $product_id, $variation_id ){
   
  
   if ( !is_user_logged_in() ) { return;}
   
    $product_id = $variation_id > 0 ? $variation_id : $product_id;

    //get discount from role
    $user = wp_get_current_user(); 
    $user_role = $user->roles[0];
    
    $page = jet_engine()->options_pages->registered_pages['shop-opt'];

    // give a discount depending on the role
    switch ($user_role) {   
        case 'customer':          
            $discount_percentage = 0;               
            break;  
          
        case 'discount1':         
            $discount_percentage = $page->get( 'diskont-riven-1' );                        
            break;
            
        case 'discount2':
            $discount_percentage =  $page->get( 'diskont-riven-2' );      
            break; 

        case 'discount3':
            $discount_percentage =  $page->get( 'diskont-riven-3' );        
            break; 
    }
    
    // The WC_Product Object
    $product = wc_get_product($product_id);

    $price = (float) $product->get_price();

    // Set the Product default base price as custom cart item data
    $cart_item_data['base_price'] = $price;

    // Only for non on sale products
    if( ! $product->is_on_sale() ){
        // Set the Product discounted price as custom cart item data
        $cart_item_data['new_price'] = $price * (100 - $discount_percentage) / 100;

        // Set the percentage as custom cart item data
        $cart_item_data['percentage'] = $discount_percentage;
    }

    if( $product->is_on_sale() ){
        // Set the Product discounted price as custom cart item data
        $cart_item_data['new_price'] = $price;       
    }
    return $cart_item_data;
}

// Display the product name with the discount percentage
add_filter( 'woocommerce_cart_item_name', 'append_percetage_to_item_name', 20, 3 );
function append_percetage_to_item_name( $product_name, $cart_item, $cart_item_key ){
    if ( !is_user_logged_in() ) {
        return;
    }

    if( isset($cart_item['percentage']) && isset($cart_item['base_price']) ) {
        if( $cart_item['data']->get_price() != $cart_item['base_price'] )
            $product_name .= ' <em>(' . $cart_item['percentage'] . '% discount)</em>';
    }
    return $product_name;
}

add_action( 'woocommerce_before_calculate_totals', 'custom_discounted_cart_item_price', 20, 1 );
function custom_discounted_cart_item_price( $cart ) {
    if ( !is_user_logged_in() ) {
        return;
    }

    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
        return;

    // Loop through cart items
    foreach ( $cart->get_cart() as $cart_item ) {
        // Set cart item discounted price
        $cart_item['data']->set_price($cart_item['new_price']);
    }
}

// CHECK/ADD  ROLE after buuing
add_action( 'woocommerce_order_status_completed', 'order_completed', 1);
function order_completed($order_id) {
    $order = wc_get_order( $order_id );   
    $user_id = $order->get_customer_id();   

    if ( $user_id == ''){
        return ;
    }
    $user = new WP_User( $user_id);
    $user_role = $user->roles[0];

    switch ($user_role) {   
        case 'customer':          
            $user->set_role( 'discount1' );                       
            break;  
          
        case 'discount1':         
            $user->set_role( 'discount2' );                       
            break;
            
        case 'discount2':
            $user->set_role( 'discount3' );       
            break; 
    } 
}

All is mostly fine.

The issue I am facing:

Sometime, I get empty order, so I setup a logger:
It looks like the issue happen when a guest adds to cart some products, go to the checkout and see that he can get discount after first order but need to be logged.
Then the guest register on shop, go to the cart and his cart is empty.
And I receive an empty order, without email, name and product.


Solution

  • First, you should enable registration on checkout page, to avoid this kind of issue. This issue could belong to some other custom code you added or some 3rd Party plugin that is interfering.

    Now, there are some mistakes and complications in your current code.

    Try the following revised and optimized code replacement (commented):

    // Add discounted price as custom cart item data conditionally
    add_filter( 'woocommerce_add_cart_item_data', 'add_discounted_price_as_custom_cart_item_data', 20, 3 );
    function add_discounted_price_as_custom_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
        global $current_user;
    
        if ( !$current_user ) {
            return; // Exit if not logged
        }
        $product = wc_get_product($variation_id > 0 ? $variation_id : $product_id); // The WC_Product Object
    
        if ( ! $product->is_on_sale() ) {
            return; // Exit if product is on sale
        }
    
        $page = jet_engine()->options_pages->registered_pages['shop-opt']; 
    
        // Define each option for a user role in an array
        $discount_for_role   = array(
            'discount1' => $page->get('diskont-riven-1'),
            'discount2' => $page->get('diskont-riven-2'),
            'discount3' => $page->get('diskont-riven-3'),
        );
    
        $discount_percentage = 0; // Initialize
    
        // Loop through options for user roles array
        foreach ( $discount_for_role as $user_role => $option_percentage ) {
            // Check user role
            if ( wc_current_user_has_role($user_role) ) {
                $discount_percentage = (float) $option_percentage; // Define discount percentage
                break;
            }
        }
    
        $price = (float) $product->get_price(); // Get product active price
    
        if ( $discount_percentage > 0 && $price > 0 ) {
            $cart_item_data['base_price'] = $price;
            $cart_item_data['percentage'] = $discount_percentage;
            $cart_item_data['new_price']  = $price * (100 - $discount_percentage) / 100; // New Price
        }
        return $cart_item_data;
    }
    
    // Display the discount percentage conditionally
    add_filter( 'woocommerce_cart_item_name', 'append_discount_percentage_to_cart_item_name', 20, 3 );
    function append_discount_percentage_to_cart_item_name($product_name, $cart_item, $cart_item_key) {
        if (is_user_logged_in() && isset($cart_item['percentage']) ) { 
            $product_name .= ' <em>(' . $cart_item['percentage'] . '% discount)</em>'; 
        }
        return $product_name;
    }
    
    // Discount cart item price conditionally
    add_action( 'woocommerce_before_calculate_totals', 'custom_discounted_cart_item_price', 20 );
    function custom_discounted_cart_item_price( $cart ) {
        if ( !is_user_logged_in() ) {
            return;
        }
    
        if ( is_admin() && ! defined('DOING_AJAX') ) {
            return;
        }
    
        if (did_action('woocommerce_before_calculate_totals') >= 2) {
            return;
        }
    
        // Loop through cart items
        foreach ($cart->get_cart() as $item) {
            // Check for discounted price
            if ( isset($item['new_price']) ) {
                $item['data']->set_price($item['new_price']); // Set discounted price
            }
        }
    }
    
    // Check and change user role after a purchase
    add_action( 'woocommerce_order_status_completed', 'alter_user_role_on_order_completed', 10, 2 );
    function alter_user_role_on_order_completed( $order_id, $order ) {
        if ( !$order->get_user_id() ) {
            return;
        }
        // Array of user roles transitions
        $role_transition = array( 
            'customer'  => 'discount1',
            'discount1' => 'discount2',
            'discount2' => 'discount3',
        );
    
        $user = $order->get_user(); // Get The WP_User object
    
        // Loop through role transition array
        foreach ( $role_transition as $role_from => $role_to ) {
            // Check user role
            if ( wc_user_has_role( $order->get_user(), $role_from ) ) {
                $user->set_role( $role_to ); // set new user role
                break; // Stop the loop
            }
        }
    }
    

    It should work.


    Important advice:

    You should not remove the "customer" user role, as WooCommerce requires a valid user role. Instead, you should add the discount role as an additional role.

    The ideal solution would have been to add that allowed discount as custom user metadata, instead of changing the user role.

    Here is a modified code version, that add the "discount user role" instead of replacing "customer" role.

    Replace the last function with the following:

    // Check and add/update user discount role after a purchase
    add_action( 'woocommerce_order_status_completed', 'define_discount_user_role_on_order_completed', 10, 2 );
    function define_discount_user_role_on_order_completed( $order_id, $order ) {
        if ( !$order->get_user_id() ) {
            return;
        }
        // Array of user roles transitions
        $discount_roles = array( 'discount1', 'discount2', 'discount3' );
    
        $user = $order->get_user(); // Get The WP_User object
    
        // If user has already 'discount3' user role we exit
        if ( wc_user_has_role( $order->get_user(), end($discount_roles) ) ) {
            return; // Exit
        }
    
        // No discount role has been added
        if ( ! array_intersect( $discount_roles, $user->roles) ) {
            $user->add_role( reset($discount_roles) ); // add the first role
        } 
        // A discount role exist already
        else {
            // Loop through discount roles
            foreach ( $discount_roles as $key => $role ) {
                // Check role
                if ( wc_user_has_role( $order->get_user(), $role ) ) {
                    $user->remove_role( $role ); // Remove current discount role
                    $user->add_role( $discount_roles[$key+1] ); // Add the next discount role
                    break; // Stop the loop
                }
            }
        }
    }
    

    It should work.