Search code examples
phpwordpresswoocommerceproductcheckout

Allow Free shipping based on shippable items minimal subtotal with progress bar


I would like to separate different output conditions, by using a snippet that filter price total amount and product type.

I edited the code from a previous answer to my question by adding some different conditions at the end:

  • Check if there are ONLY APPOINTMENT in the checkout to show nothing or just a thank you message
  • If there is at least one normal product in the cart (variable or simple) check if this is <50 (free shipping text) or >=50 to show the progress bar

I am trying to add some extra conditions, to not show the progress bar (for the amount that remain for the free shipping) while there are only phisical products with an amount <=50 euro, if it less but with some normal phisical products in the cart (included eventually some appointment or virtual products) the bar will showed, in the case that there are only appointment type products or virtual i would like to sho only a thank you message.

Here is my code attempt:

// Conditional function that checks if there are only "appointment" product types in cart
function has_only_appointments() {
    $others_found = false; // Initializing

    // Loop through cat items
    foreach ( WC()->cart->get_cart() as $item ) {
        if ( ! $item['data']->is_type('custom') ) {
            $others_found = true;
            break;
        }
    }
    return !$others_found;
}

// Filter shipping  methods
add_filter( 'woocommerce_package_rates', 'ecommercehints_free_shipping_for_specific_products', 10, 2 );
function ecommercehints_free_shipping_for_specific_products( $rates, $package ) {
    // check if there are only "appointment" product types in cart
    if( has_only_appointments()  ){
        return $rates;
    }
    
    // If there is shippable products remove free shipping if cart subtotal is under specific amount 
    if ( WC()->cart->get_subtotal() < 50 ) {
        unset( $rates['free_shipping:21'] ); // The targeted Free shipping method rate ID
    }
    return $rates;
}

// Show 'Spend another X amount' on checkout.
add_filter( 'woocommerce_checkout_before_order_review', 'ecommercehints_checkout_page_progress_bar', 10 );
function ecommercehints_checkout_page_progress_bar() {
    $subtotal = WC()->cart->get_subtotal();

    // check if there are only "appointment" product types in cart
    if( has_only_appointments( WC()->cart->get_cart() )  ){
        echo __("Thank you for your order", "woocommerce");
    } 
    // HERE I WOULD CHECK IF THERE ARE NORMAL PRODUCT AND THE TOTAL AMOUNT OF THE NORMAL PRODUCTS (anything could be shipped) IS MORE THEN 50, FOR THE FREE SHIPPING
    elseif  ( $subtotal >= 50 ) {
        echo __("Free shipping available", "woocommerce");
    }  
    // HERE THE LAST TO SHOW THE PROGRESS BAR TO REACH THE FREE SHIPPING AMOUNT (again by checking if there are only phisical products)
    elseif  ( $subtotal < 50 ) {
        printf( __('Spend another %s for free shipping!', 'woocommerce'), wc_price( 50 - $subtotal ) );
        printf('<br><progress id="freeshippingprogress" max="50" value="%d"></progress>', intval($subtotal) );
    }    
}

Solution

  • You need to get the cart items subtotal for physical products (shippable products), so:

    • if there are only appointments items in cart, nothing is displayed in checkout (as you don't really need to display anything for this case).
    • If there are shippable items or mixed items:
      • if the minimal amount has not been reached (for shippable items subtotal), we display a message with a progress bar, showing the remaining amount to get free shipping
      • if the minimal amount has been reached (for shippable items subtotal), we allow free shipping, displaying a message.

    Here is the code:

    // Conditional function that checks if there are only "appointment" product types in cart
    function has_only_appointments() {
        $others_found = false; // Initializing
    
        // Loop through cat items
        foreach ( WC()->cart->get_cart() as $item ) {
            if ( ! $item['data']->is_type('custom') ) {
                $others_found = true;
                break;
            }
        }
        return !$others_found;
    }
    
    // Utility function: Get the cart subtotal excl taxes from shippable cart items (physical products)
    function get_shippable_subtotal() {
        $subtotal = 0; // Initializing
    
        // Loop through cat items
        foreach ( WC()->cart->get_cart() as $item ) {
            if ( ! $item['data']->is_type('custom') ) {
                $subtotal += $item['line_total'];
            }
        }
        return $subtotal;
    }
    
    // Filter shipping  methods
    add_filter( 'woocommerce_package_rates', 'ecommercehints_free_shipping_for_specific_products', 10, 2 );
    function ecommercehints_free_shipping_for_specific_products( $rates, $package ) {
        // check if there are only "appointment" product types in cart
        if( has_only_appointments()  ){
            return $rates;
        }
        $min_amount = 50; // <== Define the minimal amount to be spent
        
        // If there is shippable products remove free shipping if cart subtotal is under specific amount 
        if ( get_shippable_subtotal() < $min_amount ) {
            unset( $rates['free_shipping:21'] ); // The targeted Free shipping method rate ID
        }
        return $rates;
    }
    
    // Show 'Spend another X amount' on checkout.
    add_filter( 'woocommerce_checkout_before_order_review', 'ecommercehints_checkout_page_progress_bar', 10 );
    function ecommercehints_checkout_page_progress_bar() {
    
        // If there are physical products in cart or mixed items
        if ( ! has_only_appointments() ) {
            $min_amount = 50; // <== Define the minimal amount to be spent
            $subtotal   = get_shippable_subtotal(); // Get shippable items subtotal
    
            // SHOW THE PROGRESS BAR TO REACH THE FREE SHIPPING AMOUNT
            if ( $subtotal < $min_amount ) {
                printf( __( 'Spend another %s for free shipping!', 'woocommerce' ), wc_price($min_amount- $subtotal ) );
                printf( '<br><progress id="freeshippingprogress" max="50" value="%d"></progress>', intval($subtotal) );
            } 
            // SHIPPABLE PRODUCTS TOTAL AMOUNT IS UP TO 50: ALLOW FREE SHIPPING
            else {
                echo __("Free shipping available", "woocommerce");
            }   
        } 
        else {
            // Only Appointments in cart (we dont display anything)
        } 
    }
    

    It should work.


    If you want to use the subtotal from all items, try the following instead:

    // Conditional function that checks if there are only "appointment" product types in cart
    function has_only_appointments() {
        $others_found = false; // Initializing
    
        // Loop through cat items
        foreach ( WC()->cart->get_cart() as $item ) {
            if ( ! $item['data']->is_type('custom') ) {
                $others_found = true;
                break;
            }
        }
        return !$others_found;
    }
    
    // Filter shipping  methods
    add_filter( 'woocommerce_package_rates', 'ecommercehints_free_shipping_for_specific_products', 10, 2 );
    function ecommercehints_free_shipping_for_specific_products( $rates, $package ) {
        // check if there are only "appointment" product types in cart
        if( has_only_appointments()  ){
            return $rates;
        }
        $min_amount = 50; // <== Define the minimal amount to be spent
        
        // If there is shippable products remove free shipping if cart subtotal is under specific amount 
        if ( WC()->cart->get_subtotal() < $min_amount ) {
            unset( $rates['free_shipping:21'] ); // The targeted Free shipping method rate ID
        }
        return $rates;
    }
    
    // Show 'Spend another X amount' on checkout.
    add_filter( 'woocommerce_checkout_before_order_review', 'ecommercehints_checkout_page_progress_bar', 10 );
    function ecommercehints_checkout_page_progress_bar() {
    
        // If there are physical products in cart or mixed items
        if ( ! has_only_appointments() ) {
            $min_amount = 50; // <== Define the minimal amount to be spent
            $subtotal   = WC()->cart->get_subtotal(); // Get cart items subtotal
    
            // SHOW THE PROGRESS BAR TO REACH THE FREE SHIPPING AMOUNT
            if ( $subtotal < $min_amount ) {
                printf( __( 'Spend another %s for free shipping!', 'woocommerce' ), wc_price($min_amount- $subtotal ) );
                printf( '<br><progress id="freeshippingprogress" max="50" value="%d"></progress>', intval($subtotal) );
            } 
            // TOTAL AMOUNT IS UP TO 50: ALLOW FREE SHIPPING
            else {
                echo __("Free shipping available", "woocommerce");
            }   
        }  
        else {
            // Only Appointments in cart (we dont display anything)
        } 
    }
    

    It should work.