Search code examples
phpwordpresswoocommerceproducthook-woocommerce

WooCommerce: Change Shipping Class conditionally for specific Zone


I'm having a problem with this code snippet. I'm on the latest version of WooCommerce (8.6.1).

I am trying to achieve the following:

During checkout, when the customer is in a specicific shipping zone (ID 29), and there are at least 2 products in the cart with a specific shipping class (ID 343), I want their shipping classes to be changed to another (ID 344). The below code, however, does not not seem to do anything. What could be the problem?

// Custom function to change shipping class if conditions are met
function adjust_shipping_class_based_on_quantity() {
    // Get the current cart contents
    $cart = WC()->cart->get_cart();

    // Check if the cart contains items and the first item has a destination
    if ( ! empty( $cart ) && isset( $cart[0]['destination'] ) && is_array( $cart[0]['destination'] ) ) {
        // Get the shipping zone ID
        $zone_id = WC_Shipping_Zones::get_zone_matching_package( array( 'contents' => $cart ) )->get_id();

        // Check if the shipping zone ID is 29
        if ( $zone_id == 29 ) {
            // Initialize variables to count 'Letter box' items
            $letter_box_count = 0;

            // Loop through cart items to count 'Letter box' items
            foreach( $cart as $item ) {
                if ( isset( $item['data'] ) && $item['data']->get_shipping_class_id() == 343 ) {
                    // Increment count for each 'Letter box' item
                    $letter_box_count += $item['quantity'];
                }
            }

            // If there are at least 2 'Letter box' items, change shipping class of all 'Letter box' items to 344
            if ( $letter_box_count >= 2 ) {
                // Loop through cart items again to change shipping class if shipping class ID is 343
                foreach( $cart as $key => $item ) {
                    if ( isset( $item['data'] ) && $item['data']->get_shipping_class_id() == 343 ) {
                        // Change the shipping class of the item to 'regular-package'
                        $cart[$key]['data']->set_shipping_class_id(344);
                    }
                }
            }

            // Set the modified cart contents
            WC()->cart->set_cart( $cart );
        }
    }
}
add_action( 'woocommerce_cart_loaded_from_session', 'adjust_shipping_class_based_on_quantity' );

I added the above code snippet to my functions.php file, and to the FluentSnippets plugin. Neither got me the desired result. I had also checked for the following incompatibilities, that did not seem to be a problem:

  • Plugin incompatibility
  • Incompatibility with other code snippets
  • Theme incompatibility

Solution

  • There are some mistakes in your code and you are not using the right hook. Try the following:

    add_action( 'woocommerce_before_calculate_totals', 'adjust_shipping_class_based_on_quantity' );
    function adjust_shipping_class_based_on_quantity( $cart ) {
        if ( is_admin() && ! defined( 'DOING_AJAX' ) )
            return; // Exit
    
        if ( ! $cart->needs_shipping() ) 
            return; // Exit
    
        // Get Shipping Packages and the Shipping Zone ID
        $shipping_packages = $cart->get_shipping_packages();
        $shipping_zone     = WC_Shipping_Zones::get_zone_matching_package( current($shipping_packages) );
    
        if ( empty( $shipping_zone ) ) 
            return; // Exit
    
        // Check if the shipping zone ID is 29
        if ( $shipping_zone->get_id() === 29 ) {
            $count = 0; // Initialize
    
            // Count items from a specific shipping class
            foreach( $cart->get_cart() as $item ) {
                if ( $item['data']->get_shipping_class_id() === 343 ) {
                    // Increment count for each 'Letter box' item
                    $count += $item['quantity'];
                }
            }
    
            // Check that there are at least 2 items from the specific shipping class
            if ( $count < 2 ) 
                return; // Exit
            
            // Loop through cart items to change the shipping class of items with the targeted shipping class
            foreach( $cart->get_cart() as $item ) {
                if ( $item['data']->get_shipping_class_id() === 343 ) {
                    $item['data']->set_shipping_class_id(344); // Change the shipping class
                }
            }
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). Tested and works.