6

In WooCommerce I use the plugin [WooCommerce checkout addon][1] and I have added to checkout page an additional field made of radio buttons (multiple choice). I would like to change the tax rate when customer selects a specific choice on that radio buttons field.

Based on Change Woocommerce cart items tax class based on chosen payment method answer code that change tax rate for specific payment method, I have tried to customized the code:

// Change Tax
add_action( 'woocommerce_before_calculate_totals', 'change_tax_class_based_on_radio_choice', 10, 1 );
function change_tax_class_based_on_radio_choice( $cart ) {
    $installation = WC()->session->get('wc_checkout_add_ons') ; // This code must be change 
    $value = 'pas-dinstallation' ; // the value of the radio button "woocommerce checkout addon" 
    $value2 = 'bacs' ; // value of payement methode
    $payement_methode = WC()->session->get('chosen_payment_method') ;
    //if ( $payement_methode !== $value2 ) //this one is ok for change tax if payement methode is bank transfert
        //return;
    if ( $installation !== $value ) // here i try to set the same condition with one of radio button "woocommerce checkout addon" 
    

    return;
    
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
        return;

    // Loop through cart items
    foreach( $cart->get_cart() as $cart_item ){
        // We set "Zero rate" tax class
        $cart_item['data']->set_tax_class("Reduced rate");
    }
}

add_action('wp_footer', 'option_trigger_update_checkout');
function option_trigger_update_checkout() {
    if( is_checkout() && ! is_wc_endpoint_url() ) :
    ?>
    <script type="text/javascript">
        jQuery(function($){
            $( 'form.checkout' ).on('change', 'input[type="radio"]', function() {
                $(document.body).trigger('update_checkout');
            });
        });
    </script>
    <?php
    endif;
}

But I don't know how to make it work for my case yet. I understand that:

$installation = WC()->session->get('wc_checkout_add_ons')

is not well implemented. What I am missing?


Update - addition (related to the answer below):

As I don't need to payment method restrictions, I have removed this related line:

// Only for a specific defined payment method
if ( ! in_array( WC()->session->get('chosen_payment_method'), $payment_ids ) )
    return;

Then code works well indeed now as I expected.

However, with the "WooCommerce checkout addon" plugin, when a radio button is selected, I can see in administration in the summary of the order and in the email received, what choice has been made.

with this code, the tax is changed, but I have no information on the customer's radio choice in admin or by email.

Have you solution?


Something else:

I want to move the radio buttons before payment.

But when I replace this:

// Display radio buttons field (optional)
add_action( 'woocommerce_after_order_notes', 'installation_custom_radio_field' );
function installation_custom_radio_field( $checkout ) {

by

// Display radio buttons field (optional)
add_action( 'woocommerce_review_order_before_payment', 'installation_custom_radio_field' );
function installation_custom_radio_field( $checkout ) {

It doesn't work anymore. Can you explain why? How to make it work?

kevkak
  • 63
  • 1
  • 6

1 Answers1

6

Update 2: It's much more complicated to make it work as it requires also Ajax and much more additional code…

So the following will allow to change cart items tax class on checkout page, depending on:

  • (optional) The chosen payment gateway (Here it's disabled with an empty array).
  • The chosen radio button value (from custom radio buttons).

As people don't use your WooCommerce checkout Add-ons commercial plugin, the code below displays some radio buttons on checkout page.

To make the code more dynamic, we start with a custom function that will handle all required settings:

// Custom function that handle your settings
function change_tax_class_settings(){
    return array(
        'payment_ids'   => array(), // (optional) Your targeted payment method Id(s) | Leave an empty array to disable.
        'tax_class'     => 'Reduced rate', // The desired tax rate
        'field_id'      => 'additonal_services', // the Field Id (from property name ="?????")
        'field_value'   => 'no-dinstallation', // The field targetted option key (value)

        // The below lines are optional (used for the radio buttons field display)
        'field_type'    => 'radio', // Field type
        'field_title'   =>  __('Additional services', 'woocommerce'),
        'field_default' => 'basic-installation', // The field targetted option key (value)
        'field_options' => array(
            'basic-installation' => __('Basic Installation', 'woocommerce'),
            'premium-installation' => __('Premium Installation', 'woocommerce'),
            'no-dinstallation' => __('No Installation', 'woocommerce'),
        ),
    );
}

Now we can load that settings on any function where it's required.


Then the radio buttons displayed before payment methods checkout section:

// Display radio buttons field (optional)
add_action( 'woocommerce_review_order_before_payment', 'installation_custom_radio_field' );
function installation_custom_radio_field() {
    extract( change_tax_class_settings() ); // Load settings and convert them in variables

    echo "<style>.$field_id-wrapper{padding:1em 1.41575em;background-color:#f5f5f5;margin-bottom:24px;}
    .form-row.$field_id-$field_type span label{display:inline-block;margin:0 18px 0 6px;}
    .$field_id-wrapper h3{font-weight:bold;}</style>";
    echo '<div class="'.$field_id.'-wrapper">
    <h3>'.$field_title.'</h3>';

    // Get WC Session variable value
    $value = WC()->session->get($field_id);

    woocommerce_form_field( $field_id, array(
        'type'     => $field_type,
        'label'    => '',
        'class'    => array('form-row-wide ' . $field_id . '-' . $field_type ),
        'options'  => $field_options,
        'default'  => $field_default,
        'required' => true,
    ), empty($value) ? WC()->checkout->get_value('_'.$field_id) : $value );

    echo '</div>';
}

enter image description here


The Ajax Part (jQuery Ajax and PHP Admin Wordpress Ajax sender and receiver + WC Session variable):

// jQuery code (client side) - Ajax sender
add_action('wp_footer', 'installation_checkout_js_script');
function installation_checkout_js_script() {
    if( is_checkout() && ! is_wc_endpoint_url() ) :
    // Load settings and convert them in variables
    extract( change_tax_class_settings() );

    // jQuery Ajax code
    ?>
    <script type="text/javascript">
    jQuery( function($){
        if (typeof wc_checkout_params === 'undefined')
            return false;

        var field = '#<?php echo $field_id; ?>_field input', fchecked = field+':checked';

        // Function that sen the Ajax request
        function sendAjaxRequest( value ) {
            $.ajax({
                type: 'POST',
                url: wc_checkout_params.ajax_url,
                data: {
                    'action': '<?php echo $field_id; ?>',
                    'value': value
                },
                success: function (result) {
                    $(document.body).trigger('update_checkout'); // Refresh checkout
                }
            });
        }

        // On ready (DOM loaded)
        sendAjaxRequest( $(fchecked).val() );

        // On change event
        $(document.body).on( 'change', field, function(){
            sendAjaxRequest( $(fchecked).val() );
        });

        // Refresh checkout on payment method change
        $( 'form.checkout' ).on('change', 'input[name="payment_method"]', function() {
            $(document.body).trigger('update_checkout'); // Refresh checkout
        });
    });
    </script>
    <?php
    endif;
}

// The Wordpress Ajax PHP receiver
add_action( 'wp_ajax_additonal_services', 'get_additonal_services' );
add_action( 'wp_ajax_nopriv_additonal_services', 'get_additonal_services' );
function get_additonal_services() {
    if ( isset($_POST['value']) ){
        // Load settings and convert them in variables
        extract( change_tax_class_settings() );

        // Update session variable
        WC()->session->set($field_id, esc_attr($_POST['value']));

        // Send back the data to javascript (json encoded)
        echo $_POST['value']; // optional
        die();
    }
}

Then the function that change cart item tax class conditionally depending on customer choices:

// Change the tax class conditionally
add_action( 'woocommerce_before_calculate_totals', 'change_tax_class_conditionally', 1000 );
function change_tax_class_conditionally( $cart ) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
        return;

    extract( change_tax_class_settings() ); // Load settings and convert them in variables

    // Only for a specific defined payment methods (can be disabled in the settings, with an empty array)
    if ( ! empty($payment_ids) && ! in_array( WC()->session->get('chosen_payment_method'), $payment_ids ) )
        return;

    $choice = WC()->session->get($field_id);

    // Loop through cart items
    foreach( $cart->get_cart() as $cart_item ){
        if( $choice === $field_value ) {
            $cart_item['data']->set_tax_class($tax_class);
        }
    }
}

Addition: Saving the customer choice to the order and displaying that everywhere on orders in front end, admin and on email notifications:

// Save custom field as order meta data
add_action( 'woocommerce_checkout_create_order', 'save_additonal_services_as_order_meta' );
function save_additonal_services_as_order_meta( $order ) {
    // Load settings and convert them in variables
    extract( change_tax_class_settings() );

    $choice = WC()->session->get($field_id);

    if( ! empty( $choice ) ) {
        $order->update_meta_data( '_'.$field_id, $choice );
    }
}

// Display additonal services choice before payment method everywhere (orders and emails)
add_filter( 'woocommerce_get_order_item_totals', 'display_additonal_services_on_order_item_totals', 1000, 3 );
function display_additonal_services_on_order_item_totals( $total_rows, $order, $tax_display ){
    // Load settings and convert them in variables
    extract( change_tax_class_settings() );

    $choice = $order->get_meta( '_'.$field_id ); // Get additonal services choice

    if( ! empty($choice) ) {
        $new_total_rows = [];

        // Loop through order total rows
        foreach( $total_rows as $key => $values ) {
            // Inserting the pickp store under shipping method
            if( $key === 'payment_method' ) {
                $new_total_rows[$field_id] = array(
                    'label' => $field_title,
                    'value' => esc_html($field_options[$choice]),
                );
            }
            $new_total_rows[$key] = $values;
        }
        return $new_total_rows;
    }
    return $total_rows;
}


// Display additonal services choice in Admin order pages
add_action( 'woocommerce_admin_order_data_after_billing_address', 'admin_order_display_additonal_services', 1000 );
function admin_order_display_additonal_services( $order ) {
    // Load settings and convert them in variables
    extract( change_tax_class_settings() );

    $choice = $order->get_meta( '_'.$field_id ); // Get additonal services choice

    if( ! empty($choice) ) {
        // Display
        echo '<p><strong>' . $field_title . '</strong>: ' . $field_options[$choice] . '</p>';
    }
}

All code goes on functions.php file of your active child theme (or theme). Tested and works.


Displayed choice on Orders and Email notifications (here on Order received page)

enter image description here


On Admin Single Orders pages:

enter image description here

halfer
  • 19,824
  • 17
  • 99
  • 186
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • Ok sorry for double answer and thanks for the code. If you have any solutions for the update it will be great ;) – kevkak Aug 03 '20 at 00:37
  • thanks for the code, it works almost perfectly. just a little problem see update 3. thanks – kevkak Aug 03 '20 at 22:43
  • 1
    **For info** all translatable text strings are like `__('text to translate', 'woocommerce')` so using [WordPress `__()` function](https://developer.wordpress.org/reference/functions/__/). Now this thread is closed for me. – LoicTheAztec Aug 04 '20 at 01:57
  • Ok it work fine. just a bug with the plugin "Checkout Manager for WooCommerce". when i delete this plugin, all works perfeclty thanks – kevkak Aug 04 '20 at 12:29
  • 1
    @LoicTheAztec I have to say I'm humbled that you answered this question so elegantly, the code you have written is not trivial and must have taken you quite some time. Nice one geezer. – Chris Pink Apr 06 '22 at 17:41