Add “spend xx more and get free shipping” message on top of cart page

Updated Sep.10, 2025

The previous code has bugs, as my client said “I noticed a glitch where if I go to the cart page after clicking “View Cart” from the product page(1st screenshot) it doesn’t show the “spend xx more to get free shipping” message(2nd screenshot).When I updated the quantity, then it showed up(3rd screenshot). Can you look into this?”

Here’s the updated one:

/**
 * Free shipping threshold notice.
 *
 * On the cart+checkout show a 'Spend $x more to get free shipping!' notice.
 *
 * Related post: https://aceplugins.com/how-to-add-a-free-shipping-threshold-notice-in-woocommerce/
 */


function ace__free_shipping_threshold_notice() {
    // Only run on cart and checkout pages
    if ( ! is_cart() && ! is_checkout() ) {
        return;
    }
    
    // Additional check to ensure we're not on admin pages or other contexts
    if ( is_admin() || ! function_exists( 'WC' ) || ! WC()->cart ) {
        return;
    }



    // Let JavaScript handle duplicate prevention instead of PHP static variables

    // Check if we already have a free shipping notice to prevent duplicates
    $notices = wc_get_notices( 'notice' );
    $has_free_shipping_notice = false;
    if ( ! empty( $notices ) ) {
        foreach ( $notices as $notice ) {
            if ( strpos( $notice['notice'], 'free shipping' ) !== false ) {
                $has_free_shipping_notice = true;
                break;
            }
        }
    }

    // If we already have a notice, return early
    if ( $has_free_shipping_notice ) {
        return;
    }

    $packages = WC()->cart->get_shipping_packages();
    $package  = reset( $packages );
    $zone     = wc_get_shipping_zone( $package );

    $cart_total = WC()->cart->get_displayed_subtotal();
    $deduct     = ( WC()->cart->get_discount_total() + ( WC()->cart->display_prices_including_tax() ? WC()->cart->get_discount_tax() : 0 ) );
    $cart_total = round( $cart_total - $deduct, wc_get_price_decimals() );

    foreach ( $zone->get_shipping_methods( true ) as $method ) {
        if ( $method->id !== 'free_shipping' ) continue;

        $min_amount = $method->get_option( 'min_amount' );
        if ( ! empty( $min_amount ) ) {
            if ( $cart_total < $min_amount ) {
                // Cart total is below threshold - show notice
                $remaining = $min_amount - $cart_total;
                // Create a dismissible notice with close button
                $message = sprintf( 
                    '<div id="free-shipping-notice" style="position: relative; padding-right: 30px; display: flex; align-items: center; justify-content: space-between; cursor: pointer;">'
                    . '<span>Spend %s more to get free shipping!</span>'
                    . '<button type="button" style="'
                    . 'background: transparent; border: none; '
                    . 'width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; '
                    . 'cursor: pointer; color: rgba(0,0,0,0.4); font-size: 22px; font-weight: normal;font-weight:900; '
                    . 'transition: all 0.2s ease; margin-left: 10px; flex-shrink: 0; '
                    . 'border-radius: 3px; line-height: 1;'
                    . '" onmouseover="this.style.background=\'rgba(0,0,0,0.1)\'; this.style.color=\'rgba(0,0,0,0.7)\'" '
                    . 'onmouseout="this.style.background=\'transparent\'; this.style.color=\'rgba(0,0,0,0.4)\'" '
                    . 'title="关闭此通知">&times;</button></div>', 
                    wc_price( $remaining ) 
                );
                wc_add_notice( $message, 'notice' );
            } else {
                // Cart total meets or exceeds threshold - remove any existing free shipping notices
                $notices = wc_get_notices( 'notice' );
                if ( ! empty( $notices ) ) {
                    $updated_notices = array();
                    foreach ( $notices as $notice ) {
                        if ( strpos( $notice['notice'], 'free shipping' ) === false ) {
                            $updated_notices[] = $notice;
                        }
                    }
                    // Clear all notices and re-add only non-free-shipping ones
                    wc_clear_notices();
                    foreach ( $updated_notices as $notice ) {
                        wc_add_notice( $notice['notice'], 'notice' );
                    }
                }
            }
            break; // Only process one free shipping method
        }
    }
}

// Clear the static flag when cart is updated so notice can show again if needed
function ace__clear_free_shipping_notice_flag() {
    // Static variables are automatically reset on new requests
    // This function is kept for potential future enhancements
}

// Add JavaScript for dismissing the notice and preventing duplicates
function ace__free_shipping_notice_script() {
    // Load script on all pages to handle notice removal on non-cart/checkout pages
    if ( ! is_admin() ) {
        ?>
        <script type="text/javascript">
        // Track if free shipping notice has been added
        window.freeShippingNoticeAdded = window.freeShippingNoticeAdded || false;
        
        document.addEventListener('DOMContentLoaded', function() {
            // Check if we're on cart or checkout page
            var isCartPage = document.body.classList.contains('woocommerce-cart') || 
                            document.body.classList.contains('page-id-cart') ||
                            window.location.href.indexOf('/cart') !== -1;
            var isCheckoutPage = document.body.classList.contains('woocommerce-checkout') || 
                                document.body.classList.contains('page-id-checkout') ||
                                window.location.href.indexOf('/checkout') !== -1;
            
            // Remove free shipping notices if not on cart/checkout pages
            if (!isCartPage && !isCheckoutPage) {
                jQuery('.woocommerce-info').each(function() {
                    if (jQuery(this).text().indexOf('free shipping') !== -1) {
                        jQuery(this).remove();
                    }
                });
                return; // Exit early if not on cart/checkout
            }
            
            // Remove duplicate notices (both free shipping and other WooCommerce notices)
            function removeDuplicateNotices() {
                var notices = document.querySelectorAll('.woocommerce-info, .woocommerce-message, .woocommerce-error');
                var seenNotices = {};
                
                notices.forEach(function(notice) {
                    var noticeText = notice.textContent.trim();
                    
                    if (seenNotices[noticeText]) {
                        notice.remove();
                    } else {
                        seenNotices[noticeText] = true;
                    }
                });
            }
            
            // Run on page load
            removeDuplicateNotices();
            
            // Run after AJAX updates - handle cart changes dynamically without page reload
            jQuery(document.body).on('updated_checkout updated_cart_totals', function() {
                setTimeout(function() {
                    // Remove duplicate notices after AJAX updates
                    removeDuplicateNotices();
                    
                    // Check if cart is empty
                    var cartEmpty = jQuery('.cart-empty').length > 0 || 
                                   jQuery('.woocommerce-info:contains("Your cart is currently empty")').length > 0 ||
                                   jQuery('body').hasClass('woocommerce-cart-empty');
                    
                    // If cart is empty, hide free shipping notices
                    if (cartEmpty) {
                        jQuery('.woocommerce-info').each(function() {
                            if (jQuery(this).text().indexOf('free shipping') !== -1) {
                                jQuery(this).remove();
                            }
                        });
                    } else {
                        // Check if threshold is met by looking for "Congratulations" or absence of "Spend" message
                        // If no "Spend more" notice exists but we have other notices, threshold might be met
                        var hasSpendMoreNotice = false;
                        jQuery('.woocommerce-info').each(function() {
                            if (jQuery(this).text().indexOf('Spend') !== -1 && jQuery(this).text().indexOf('free shipping') !== -1) {
                                hasSpendMoreNotice = true;
                            }
                        });
                        
                        // If cart is not empty but no "spend more" notice, remove any lingering free shipping notices
                        if (!hasSpendMoreNotice) {
                            jQuery('.woocommerce-info').each(function() {
                                if (jQuery(this).text().indexOf('free shipping') !== -1) {
                                    jQuery(this).remove();
                                }
                            });
                        }
                    }
                }, 200);
             });
            
            // Handle notice dismissal
            document.addEventListener('click', function(e) {
                var target = e.target;
                var woocommerceInfo = target.closest('.woocommerce-info');
                
                if (woocommerceInfo && woocommerceInfo.innerHTML.indexOf('free shipping') !== -1) {
                    woocommerceInfo.style.display = 'none';
                }
            });
        });
        </script>
        <?php
    }
}
add_action( 'wp_footer', 'ace__free_shipping_notice_script' );



// Clear notice flags when cart changes to allow fresh notice calculation
add_action( 'woocommerce_cart_updated', 'ace__clear_free_shipping_notice_flag' );
add_action( 'woocommerce_add_to_cart', 'ace__clear_free_shipping_notice_flag' );
add_action( 'woocommerce_cart_item_removed', 'ace__clear_free_shipping_notice_flag' );



// Hook into essential cart and checkout events to ensure the notice shows consistently
add_action( 'woocommerce_before_cart', 'ace__free_shipping_threshold_notice' );
add_action( 'woocommerce_before_checkout_form', 'ace__free_shipping_threshold_notice' );
add_action( 'woocommerce_cart_updated', 'ace__free_shipping_threshold_notice' );
// Removed template_redirect hook to prevent notice from showing on non-cart/checkout pages

————— Old post below

free-shipping notice

Here’s the code snippet to add a “spend xx more and get free shipping” message on top of cart page or the checkout page:

<?php

// Define a function that will check and display a notice about free shipping threshold
function ace__free_shipping_threshold_notice() {
    // Check if we're on the cart page - is_cart() is a WooCommerce function that returns true only on cart page
    if ( is_cart() ) {
        // WC() is the main WooCommerce class instance
        // ->cart accesses the cart object
        // ->get_shipping_packages() gets all items in cart that need shipping
        $packages = WC()->cart->get_shipping_packages();
        
        // reset() gets the first element of an array
        // We only need the first package as shipping is usually calculated per cart
        $package  = reset( $packages );
        
        // Get the shipping zone based on customer's address
        // Shipping zones determine available shipping methods for the customer's location
        $zone     = wc_get_shipping_zone( $package );

        // Get the cart subtotal (before shipping)
        $cart_total = WC()->cart->get_displayed_subtotal();
        
        // Calculate any discounts that need to be deducted
        // If prices include tax, we also deduct the discount tax
        $deduct     = ( WC()->cart->get_discount_total() + 
                       ( WC()->cart->display_prices_including_tax() ? 
                         WC()->cart->get_discount_tax() : 0 ) );
        
        // Calculate the final cart total after deducting discounts
        // wc_get_price_decimals() gets the number of decimal places set in WooCommerce
        $cart_total = round( $cart_total - $deduct, wc_get_price_decimals() );

        // Loop through all enabled shipping methods in this zone
        foreach ( $zone->get_shipping_methods( true ) as $method ) {
            // Skip if this is not a free shipping method
            if ( $method->id !== 'free_shipping' ) continue;

            // Get the minimum amount required for free shipping
            $min_amount = $method->get_option( 'min_amount' );
            
            // If there is a minimum amount set AND cart total is less than that
            if ( ! empty( $min_amount ) && $cart_total < $min_amount ) {
                // Calculate how much more customer needs to spend
                $remaining = $min_amount - $cart_total;
                
                // Add a notice to the cart page
                // wc_price() formats the price according to store settings
                // 'notice' is the type of message (others could be 'error' or 'success')
                wc_add_notice( sprintf( 'Spend %s more to get free shipping!', 
                             wc_price( $remaining ) ), 'notice' );
            }
        }
    }
}

// Hook our function to run before the cart is displayed
// 'woocommerce_before_cart' is a WooCommerce action hook that fires at the start of cart page
add_action( 'woocommerce_before_cart', 'ace__free_shipping_threshold_notice' );

It will show like this

free-shipping threshold messages

Support My Work

If you enjoy my content, consider buying me a coffee or shopping through my Rakuten link to support me!

Victor

Founder of WPUtopia.com🕵️I would appreciate it if you could leave me a comment!

Send Message

Chat with me

Start a Conversation

Hi! Let's connect on your preferred platform.