phpjquerywordpresswoocommerceproduct-variations

Custom Field Options Price Update Issue on WooCommerce Variation Change


I am trying to add custom fields to my WooCommerce site to update the price based on the warranty status selected by the customer when choosing a product variation. Specifically, I want the price to adjust according to whether the warranty is expired, within 1-6 months, over 6 months, or unactivated.

screenshot

I have implemented the following code, which correctly displays the warranty options and the adjusted price on the page. However, the price does not update correctly when changing variations, leading to calculation errors. Here’s my code:

add_action('woocommerce_single_variation', 'add_custom_options', 5);
function add_custom_options() {
    ?>
    <div class="custom-options-wrapper" style="width: 100%;">
        <h4>Great, let's talk about the more details:</h4>
        <div class="custom-warranty-option" style="margin-bottom: 20px;">
            <label>1. Is it still under warranty?</label>
            <label style="display: block;">
                <input type="radio" name="warranty_status" value="no" required> No, the warranty has expired.
            </label>
            <label style="display: block;">
                <input type="radio" name="warranty_status" value="1-6months" required> Yes, 1-6 months warranty
            </label>
            <label style="display: block;">
                <input type="radio" name="warranty_status" value="over6months" required> Yes, over 6 months warranty
            </label>
            <label style="display: block;">
                <input type="radio" name="warranty_status" value="unactivated" required> Yes, 1 year warranty & unactivated
            </label>
        </div>
    </div>

    <script type="text/javascript">
    jQuery(document).ready(function($) {
        var originalPrice;

        function getOriginalPrice() {
            var priceText = $('.woocommerce-variation-price .woocommerce-Price-amount.amount').first().text();
            return parseFloat(priceText.replace(/[^\d.]/g, ''));
        }

        function updatePrice() {
            if (isNaN(originalPrice)) {
                originalPrice = getOriginalPrice();
            }

            var warrantyMultiplier = 1.00;

            if ($('input[name="warranty_status"]:checked').val() === 'no') {
                warrantyMultiplier = 0.90; // No warranty, deduct 10%
            } else if ($('input[name="warranty_status"]:checked').val() === '1-6months') {
                warrantyMultiplier = 0.92; // 1-6 months warranty, deduct 8%
            } else if ($('input[name="warranty_status"]:checked').val() === 'over6months') {
                warrantyMultiplier = 0.95; // Over 6 months warranty, deduct 5%
            }

            var newPrice = Math.round(originalPrice * warrantyMultiplier);

            $('.woocommerce-variation-price .woocommerce-Price-amount.amount').html('<bdi><span class="woocommerce-Price-currencySymbol">$</span>' + newPrice + '</bdi>');

            // Update hidden input to ensure the new price is used when added to the cart
            $('#custom_price').val(newPrice);
        }

        // Clear the selected state of all custom options
        function resetCustomOptions() {
            $('input[name="warranty_status"]').prop('checked', false);
        }

        $('form.variations_form').on('woocommerce_variation_has_changed', function() {
            resetCustomOptions();  // Reset custom options
            originalPrice = getOriginalPrice();  // Get new price each time a variation is switched
            updatePrice();
        });

        $('input[name="warranty_status"]').change(function() {
            updatePrice();
        });

        originalPrice = getOriginalPrice();
        updatePrice();
    });
    </script>
   
    <?php
}

Main Issue: When a user switches between variations, the price does not correctly update based on the selected warranty status. How can I ensure the price is recalculated and updated when the variation changes? I would appreciate any suggestions to resolve this issue! Thank you!


Solution

  • First, it is better to include the variations warranty prices HTML to the variable product data form. Then you should use WooCommerce variation JS events to change your prices.

    Try the following revised code:

    // Add variation warranty prices html to the variable product data form
    add_action( 'woocommerce_available_variation', 'add_variation_custom_warranty_prices_html', 10, 3 );
    function add_variation_custom_warranty_prices_html( $variation_data, $product, $variation ) {
        $display_price         = (float) $variation_data['display_price'];
        $display_regular_price = (float) $variation_data['display_regular_price'];
    
        // Variation is on sale
        if ( $display_price !== $display_regular_price ) {
            $variation_data['warranty_prices'] = array(
                'no'            => wc_format_sale_price($display_regular_price * 0.9, $display_price * 0.9),
                '1-6months'     => wc_format_sale_price($display_regular_price * 0.92, $display_price * 0.92),
                'over6months'   => wc_format_sale_price($display_regular_price * 0.95, $display_price * 0.95),
                'inactivated'   => wc_format_sale_price($display_regular_price, $display_price),
            );
        } 
        // Variation is not on sale
        else {
            $variation_data['warranty_prices'] = array(
                'no'            => wc_price($display_price * 0.9),
                '1-6months'     => wc_price($display_price * 0.92),
                'over6months'   => wc_price($display_price * 0.95),
                'inactivated'   => wc_price($display_price),
            );
        }
        return  $variation_data;
    }
    
    add_action( 'woocommerce_single_variation', 'add_custom_warranty_options', 5 );
    function add_custom_warranty_options() {
        // HTML output
        echo '<div class="custom-options-wrapper" style="width: 100%;">
            <h4>'. esc_html__("Great, let's talk about the more details:") . '</h4>
            <div class="custom-warranty-option" style="margin-bottom: 20px;">
                <label>'. esc_html__("1. Is it still under warranty?") . '</label>
                <label style="display: block;">
                    <input type="radio" name="warranty_status" value="no" required> '. esc_html__("No, the warranty has expired.") . '
                </label>
                <label style="display: block;">
                    <input type="radio" name="warranty_status" value="1-6months" required> '. esc_html__("Yes, 1-6 months warranty") . '
                </label>
                <label style="display: block;">
                    <input type="radio" name="warranty_status" value="over6months" required> '. esc_html__("Yes, over 6 months warranty") . '
                </label>
                <label style="display: block;">
                    <input type="radio" name="warranty_status" value="inactivated" required> '. esc_html__("Yes, 1 year warranty & inactivated") . '
                </label>
            </div>
        </div>';
    
        // Enqueued Javascript (jQuery ready event is included)
        wc_enqueue_js( "var warrantyPrices = undefined, selectedWarranty = undefined;
        function updateVariationPriceHtml( warrantyPrices, selectedWarranty ) {
            $.each( warrantyPrices, function( warrantyOption, priceHtml ){
                if( warrantyPrices === undefined || selectedWarranty === undefined ) {
                    return false;
                }
                if ( selectedWarranty === warrantyOption ) {
                    $('.woocommerce-variation-price > .price').html(priceHtml);
                }
            });
        }
    
        $('form.variations_form').on('show_variation', function(event, data){ 
            warrantyPrices = data.warranty_prices;
            updateVariationPriceHtml( warrantyPrices, selectedWarranty );
        }).on('hide_variation', function() {
            warrantyPrices = selectedWarranty = undefined;
            $('input[name=warranty_status]').prop('checked', false);
        }).on('change', 'input[name=warranty_status]', function(){
            selectedWarranty = $(this).val();
            updateVariationPriceHtml( warrantyPrices, selectedWarranty );
        });" );
    }
    

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

    Now it will work without any issue.


    Addition:

    Reset the Warranty selected option on selected variation change

    To reset the Warranty selected option on selected variation change, you can change the JavaScript code block inside the 2nd function like:

        // Enqueued Javascript (jQuery ready event is included)
        wc_enqueue_js( "var warrantyPrices = undefined, selectedWarranty = undefined;
        $('form.variations_form').on('show_variation', function(event, data){ 
            warrantyPrices = data.warranty_prices;
            selectedWarranty = undefined;
            $('input[name=warranty_status]').prop('checked', false);
        }).on('hide_variation', function() {   
            warrantyPrices = selectedWarranty = undefined;
            $('input[name=warranty_status]').prop('checked', false);
        }).on('change', 'input[name=warranty_status]', function(){
            selectedWarranty = $(this).val();
            $.each( warrantyPrices, function( warrantyOption, priceHtml ){
                if( warrantyPrices === undefined || selectedWarranty === undefined ) {
                    return false;
                }
                if ( selectedWarranty === warrantyOption ) {
                    $('.woocommerce-variation-price > .price').html(priceHtml);
                }
            });
        });" );