Add Free Shipping Progress Bar

Adds a progress bar showing how much more is needed to qualify for free shipping.

Display estimated delivery date
PHP
/**
 * Snippet Name:     Add Free Shipping Progress Bar
 * Snippet Author:   coding-bunny.com
 * Description:      Adds a progress bar showing how much more is needed to qualify for free shipping.
 * Version:          1.2.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'init', 'cbfpb_init_hooks' );

function cbfpb_init_hooks() {
    // Product page
    add_action( 'woocommerce_after_add_to_cart_button', 'cbfpb_display_product_bar', 25 );
    
    // Cart page
    add_action( 'woocommerce_cart_totals_after_order_total', 'cbfpb_display_cart_bar' );
    
    // Checkout page
    add_action( 'woocommerce_review_order_after_order_total', 'cbfpb_display_checkout_bar' );
}

function cbfpb_format_price( $amount ) {
    return wc_price( $amount );
}

function cbfpb_currency_symbol() {
    return html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' );
}

function cbfpb_render_bar( $context = 'general' ) {

    if ( ! class_exists( 'WooCommerce' ) ) {
        return;
    }

    $free_shipping_threshold = cbfpb_minimum_amount();
    
    if ( ! $free_shipping_threshold ) {
        return;
    }

    $current_cart_total = 0;
    if ( WC()->cart ) {
        $current_cart_total = WC()->cart->get_subtotal();
    }
    
    $remaining_amount = max( 0, $free_shipping_threshold - $current_cart_total );
    $progress_percentage = min( 100, ( $current_cart_total / $free_shipping_threshold ) * 100 );

    ?>
    <div class="free-shipping-unified <?php echo esc_attr( $context ); ?>-context" 
         data-threshold="<?php echo esc_attr( $free_shipping_threshold ); ?>"
         data-currency="<?php echo esc_attr( cbfpb_currency_symbol() ); ?>"
         data-currency-pos="<?php echo esc_attr( get_option( 'woocommerce_currency_pos' ) ); ?>"
         data-thousand-sep="<?php echo esc_attr( wc_get_price_thousand_separator() ); ?>"
         data-decimal-sep="<?php echo esc_attr( wc_get_price_decimal_separator() ); ?>"
         data-decimals="<?php echo esc_attr( wc_get_price_decimals() ); ?>">
        <?php if ( $remaining_amount > 0 ) : ?>
            <p class="shipping-message">
                <?php
                printf( 
                    esc_html__( 'Add %s more to get free shipping!', 'textdomain' ),
                    wp_kses_post( cbfpb_format_price( $remaining_amount ) )
                );
                ?>
            </p>
        <?php else : ?>
            <p class="shipping-message qualified">
                <?php esc_html_e( 'Free shipping qualified ✓', 'textdomain' ); ?>
            </p>
        <?php endif; ?>
        
        <div class="progress-track">
            <div class="progress-fill" style="width: <?php echo esc_attr( $progress_percentage ); ?>%;"></div>
        </div>
        
        <?php if ( $context === 'checkout' && $remaining_amount == 0 ) : ?>
            <p class="shipping-bonus">
                <?php esc_html_e( 'Free shipping will be applied at final step', 'textdomain' ); ?>
            </p>
        <?php endif; ?>
    </div>
    <?php
}

function cbfpb_display_product_bar() {
    cbfpb_render_bar( 'product' );
}

function cbfpb_display_cart_bar() {
    ?>
    <tr class="free-shipping-progress-row">
        <td colspan="2" class="free-shipping-cell">
            <?php cbfpb_render_bar( 'cart' ); ?>
        </td>
    </tr>
    <?php
}

function cbfpb_display_checkout_bar() {
    ?>
    <tr class="free-shipping-checkout-row">
        <td colspan="2" class="free-shipping-checkout-cell">
            <?php cbfpb_render_bar( 'checkout' ); ?>
        </td>
    </tr>
    <?php
}

function cbfpb_minimum_amount() {
    static $cached_amount = null;
    
    if ( $cached_amount !== null ) {
        return $cached_amount;
    }
    
    $shipping_zones = WC_Shipping_Zones::get_zones();
    $shipping_zones[] = array( 'zone_id' => 0 );
    
    foreach ( $shipping_zones as $zone ) {
        $zone_id = $zone['zone_id'];
        $shipping_zone = new WC_Shipping_Zone( $zone_id );
        $shipping_methods = $shipping_zone->get_shipping_methods( true );
        
        foreach ( $shipping_methods as $method ) {
            if ( 'free_shipping' === $method->id && 'yes' === $method->enabled ) {
                $min_amount = $method->get_option( 'min_amount' );
                
                if ( ! empty( $min_amount ) && is_numeric( $min_amount ) ) {
                    $cached_amount = (float) $min_amount;
                    return $cached_amount;
                }
            }
        }
    }
    
    $cached_amount = false;
    return false;
}

add_action( 'wp_head', 'cbfpb_styles' );

function cbfpb_styles() {
    if ( ! is_product() && ! is_cart() && ! is_checkout() ) {
        return;
    }
    ?>
    <style>
        .free-shipping-unified {
            margin: 0;
            padding: 12px;
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 6px;
            font-size: 14px;
        }
        
        .free-shipping-unified.product-context {
            margin: 0;
        }
        
        .free-shipping-unified.cart-context {
            margin: 0;
        }
        
        .free-shipping-unified.checkout-context {
            margin: 0;
        }
        
        .free-shipping-cell,
        .free-shipping-checkout-cell {
            padding: 15px 0 !important;
            border-top: 1px solid #e0e0e0;
        }
        
        .shipping-message {
            margin: 0 0 8px 0;
            font-weight: 400;
            text-align: center;
            font-size: 14px;
        }
        
        .shipping-message.qualified {
            color: #27ae60;
        }
        
        .shipping-bonus {
            margin: 8px 0 0 0;
            font-size: 12px;
            color: #27ae60;
            text-align: center;
            font-style: italic;
        }
        
        .progress-track {
            height: 6px;
            background: #e0e0e0;
            border-radius: 3px;
            overflow: hidden;
        }
        
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #27ae60 0%, #2ecc71 100%);
            border-radius: 3px;
            transition: width 0.3s ease;
        }
        
        .progress-fill.updating {
            transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
        }
        
        .shipping-message.updating {
            transition: color 0.3s ease;
        }
        
        .shipping-message .woocommerce-Price-amount {
            font-weight: 700;
        }
        
        @media (max-width: 768px) {
            .free-shipping-unified {
                font-size: 13px;
                padding: 10px;
            }
            
            .shipping-message {
                font-size: 13px;
            }
        }
        
        .woocommerce-cart .free-shipping-progress-row,
        .woocommerce-checkout .free-shipping-checkout-row {
            border-top: 1px solid #e0e0e0;
        }
    </style>
    <?php
}

add_action( 'wp_ajax_cbfpb_cart_total', 'cbfpb_ajax_cart_total' );
add_action( 'wp_ajax_nopriv_cbfpb_cart_total', 'cbfpb_ajax_cart_total' );

function cbfpb_ajax_cart_total() {
    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cbfpb_nonce' ) ) {
        wp_die( 'Security check failed' );
    }

    if ( ! class_exists( 'WooCommerce' ) || ! WC()->cart ) {
        wp_send_json_error( 'WooCommerce not available' );
    }
    
    $free_shipping_threshold = cbfpb_minimum_amount();
    
    if ( ! $free_shipping_threshold ) {
        wp_send_json_error( 'No free shipping available' );
    }
    
    $current_cart_total = WC()->cart->get_subtotal();
    $remaining_amount = max( 0, $free_shipping_threshold - $current_cart_total );
    $progress_percentage = min( 100, ( $current_cart_total / $free_shipping_threshold ) * 100 );
    
    $response = array(
        'remaining' => $remaining_amount,
        'remaining_formatted' => cbfpb_format_price( $remaining_amount ),
        'percentage' => $progress_percentage,
        'qualified' => $remaining_amount == 0,
        'current_total' => $current_cart_total,
        'threshold' => $free_shipping_threshold
    );
    
    wp_send_json_success( $response );
}

add_action( 'wp_footer', 'cbfpb_realtime_script' );

function cbfpb_realtime_script() {
    if ( ! is_product() && ! is_cart() && ! is_checkout() ) {
        return;
    }
    ?>
    <script>
        jQuery(document).ready(function($) {
            var updateTimeout;
            
            function formatPrice(amount, currency, currencyPos, thousandSep, decimalSep, decimals) {
                var formattedAmount = parseFloat(amount).toFixed(decimals);
                
                if (thousandSep) {
                    var parts = formattedAmount.split('.');
                    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSep);
                    formattedAmount = parts.join(decimalSep);
                } else if (decimalSep !== '.') {
                    formattedAmount = formattedAmount.replace('.', decimalSep);
                }
                
                switch(currencyPos) {
                    case 'left':
                        return currency + formattedAmount;
                    case 'right':
                        return formattedAmount + currency;
                    case 'left_space':
                        return currency + ' ' + formattedAmount;
                    case 'right_space':
                        return formattedAmount + ' ' + currency;
                    default:
                        return currency + formattedAmount;
                }
            }
            
            function updateProgressBarDisplay(data) {
                var $progressFill = $('.progress-fill');
                var $shippingMessage = $('.shipping-message');
                var $progressBar = $('.free-shipping-unified');
                
                $progressFill.addClass('updating');
                $shippingMessage.addClass('updating');
                
                var message;
                if (data.qualified) {
                    message = 'Free shipping qualified ✓';
                } else {

                    if (data.remaining_formatted) {
                        message = 'Add ' + data.remaining_formatted + ' more to get free shipping!';
                    } else {

                        var currency = $progressBar.data('currency');
                        var currencyPos = $progressBar.data('currency-pos');
                        var thousandSep = $progressBar.data('thousand-sep');
                        var decimalSep = $progressBar.data('decimal-sep');
                        var decimals = parseInt($progressBar.data('decimals'));
                        
                        var formattedPrice = formatPrice(data.remaining, currency, currencyPos, thousandSep, decimalSep, decimals);
                        message = 'Add ' + formattedPrice + ' more to get free shipping!';
                    }
                }
                
                $shippingMessage.html(message)
                    .toggleClass('qualified', data.qualified);
                
                $progressFill.css('width', data.percentage + '%');
                
                if ($('.checkout-context').length && data.qualified) {
                    if (!$('.shipping-bonus').length) {
                        $('.checkout-context .progress-track').after(
                            '<p class="shipping-bonus">Free shipping will be applied at final step</p>'
                        );
                    }
                } else {
                    $('.shipping-bonus').remove();
                }
                
                setTimeout(function() {
                    $progressFill.removeClass('updating');
                    $shippingMessage.removeClass('updating');
                }, 500);
            }
            
            function updateShippingProgress() {
                $.post('<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>', {
                    action: 'cbfpb_cart_total',
                    nonce: '<?php echo esc_js( wp_create_nonce( 'cbfpb_nonce' ) ); ?>'
                }, function(response) {
                    if (response.success) {
                        updateProgressBarDisplay(response.data);
                    }
                });
            }
            
            $(document.body).on('added_to_cart', function(event, fragments, cart_hash, button) {
                setTimeout(updateShippingProgress, 100);
            });
            
            $(document.body).on('updated_wc_div', function() {
                updateShippingProgress();
            });
            
            $(document.body).on('updated_cart_totals', function() {
                updateShippingProgress();
            });
            
            $(document.body).on('updated_checkout', function() {
                updateShippingProgress();
            });
            
            $(document).on('input change', 'input.qty', function() {
                clearTimeout(updateTimeout);
                updateTimeout = setTimeout(function() {
                    updateShippingProgress();
                }, 300);
            });
            
            $(document).on('click', 'a.remove', function() {
                setTimeout(updateShippingProgress, 800);
            });
            
            $('form.variations_form').on('found_variation', function() {
                setTimeout(updateShippingProgress, 100);
            });
            
            if ($('.product-context').length) {
                $(document).on('input change', 'input[name="quantity"]', function() {
                    updateShippingProgress();
                });
            }
            
            setInterval(updateShippingProgress, 30000);
            
            setTimeout(updateShippingProgress, 500);
        });
    </script>
    <?php
}
?>

How To Implement This Solution?

Leave a Reply

Your email address will not be published. Required fields are marked *

My Agile Privacy
This site uses technical and profiling cookies. You can accept, decline or customize cookies by pressing the desired buttons. By closing this policy you will continue without accepting.

Need help?

Choose one of the following options:

Powered by CodingBunny