instagram rss arrow-down

I’ve seen many people asking for a solution on How to add conditional checkout fields in WooCommerce. There are so many good WooCommerce Plugins to handle checkout fields, but there is always something missing. The closest match I could find that can handle WooCommerce conditional checkout fields is Conditional Woo Checkout Field Pro. But unfortunately, this plugin is lacking the price field.

For what can I use this script?

Most of the WordPress users are familiar with The Events Calendar Pro by Modern Tribes.  I had a request from my client who is selling tickets for B2B events with WooCommerce and The Events Calendar: WooCommerce Tickets. But there was a problem.

No matter how many tickets you add to your cart, checkout will only allow you to enter billing information of the buyer. In my case, the buyer is usually a person who’s buying the tickets for his employees. And he may or may not be the one who’s attending the event.

So, what this script does is following:

  • It adds an extra field to the Woocommerce checkout fields
  • Number of fields are based on the cart quantity. If the user has purchased 2 tickets, fields for 2 attendees will be created.
  • User can check if he is attending the event and if he does, fields for the First attendee will be auto populated with the values from billing section
  • User can also add extra options at Woocommerce checkout
  • WooCommerce cart is dynamically updated based on the options selected
  • Updates the order meta with the field value
  • Displays field value on the order edit page
  • Adds field values to email

In my case, these set of Woocommerce extra checkout fields solved my problem. I now have fully functional Conditional checkout fields with price in WooCommerce.

Where to add this code?

  • You would normally add this code to your themes functions.php file.
  • You will also need to add numeric.js before closing the body tag. </body> . Depending on the theme, your </body> tag can be found inside header.php or footer.php. 
  • You will need to create a folder named “js” inside your theme root folder, create file “numeric.js” and paste the content of the numeric.js from this tutorial.
  • You will also want to style your fields a bit, you can use your themes custom CSS for that

A word of caution:

The following example of Conditional Checkout Fields with price for Woocommerce will probably not work for you by just pasting the code. I had a very untypical WooCommerce setup with a lot of custom features, and needless to mention, all customizations were done with Child Theme. Use this following example only as a guidance and append the code to your needs.

This code snippet is fully functional, it’s active on my client website and it’s running on WordPress 4.7.2 with WooCommerce 3.0.5. I won’t go into details which part does what, as I believe that those of you who feel comfortable enough making this WooCommerce customization, have the basic understanding of PHP and WooCommerce actions and filters.

So let’s get started creating your first Conditional Checkout Fields with Price for WooCommerce!

How to add conditional checkout fields in WooCommerce

As mentioned before, my example has some extra checkout fields options with prices. So let’s define and add our checkout fields.

We are now creating custom checkout fields for extra attendees and for extra options with price. This piece of code creates a number of attendees based on the number of products in the cart. If the number of products in cart is not equal to 0, line 17 will echo how many tickets you have.

How to add conditional checkout fields with price in WooCommerce

This is how our fields are displayed on WooCommerce checkout

Note that the price of the extra fields is not defined in this snippet. These are only the labels displayed at the frontend.

/**
 * Add the fields to the checkout
 **/
add_action( 'woocommerce_before_order_notes', 'product_quantities_custom_checkout_fields' );

function product_quantities_custom_checkout_fields( $checkout ) {

    $extra_servsafe = 'extra_servsafe_'. $j;
    $extra_pdf = 'extra_pdf_'. $j;
    $extra_textbook = 'extra_textbook_'. $j;

 //Check for product quantities
    $quantity_of_products_in_cart = product_quantities_in_cart();

    if($quantity_of_products_in_cart != 0) {

    echo '<div id="quantity_checkout_field" ><h3 style="float:none;">' . __( 'You have tickets for ' ) . $quantity_of_products_in_cart . ' attendees' . '</h3>';

    echo '<div id="extra_materials" ><h4 style="float:none;">Please enter how many items of exta materials you would like to purchase</h3></div>';

    $attendee_name = 'attendee_name_'. $j;
    $attendee_last_name = 'attendee_last_name_'. $j;
    $attendee_phone = 'attendee_phone_'. $j;
    $attendee_email = 'attendee_email_'. $j;
    
     woocommerce_form_field( 'extra_servsafe', array(
    'type'          => 'text',
    'class'         => array('input-text'),
    'label'         => __('<div id="desc" style="font-size:13px;line-height: 12px;"> Option one - Servsafe textbook ($25.00)</div> '),
    'required'        => false,
    ), $checkout->get_value( 'extra_servsafe' ) );
    
    woocommerce_form_field( 'extra_pdf', array(
    'type'          => 'text',
    'class'         => array('input-text'),
    'label'         => __('<div id="desc" style="font-size:13px;line-height: 12px;"> Option two - Electronic PDF ($20.00)</div> '),
    'required'        => false,
    ), $checkout->get_value( 'extra_pdf' ) );
    
    woocommerce_form_field( 'extra_textbook', array(
    'type'          => 'text',
    'class'         => array('input-text'),
    'label'         => __('<div id="desc" style="font-size:13px;line-height: 12px;"> A New 6th Edition textbook 300+ pages ($70.00)</div> '),
    'required'        => false,
    ), $checkout->get_value( 'extra_textbook' ) );


    $i = 0;
    while ($i < $quantity_of_products_in_cart) {
        $j = $i + 1;

        echo '<p style="margin: 24px 0 0px; color: #222; display: table-cell; font-weight: bold;">Attendee ' . $j . '</p>';
        
         woocommerce_form_field( 'attendee_name' . $j, array(
         'type'  => 'text',
         'class' => array( 'attendee_name form-row-first' ),
         'required'  => true,
         'label' => __('First Name', 'woocommerce'),
         ), $checkout->get_value( 'attendee_name' . $j ) );

         woocommerce_form_field( 'attendee_last_name' . $j, array(
         'type'  => 'text',
         'class' =>array('attendee_last_name form-row-last'),
         'required'  => true,
         'label' => __('Last Name', 'woocommerce'),
         ), $checkout->get_value( 'attendee_last_name' . $j ) );

         woocommerce_form_field( 'attendee_phone' . $j, array(
         'type'  => 'tel',
         'class' => array( 'attendee_phone form-row-wide' ),
         'required'  => true,
         'label' => __('Phone', 'woocommerce'),
         ), $checkout->get_value( 'attendee_phone' . $j ) );

         woocommerce_form_field( 'attendee_email' . $j, array(
         'type'  => 'email',
         'class' => array( 'attendee_email form-row-wide' ),
         'required'  => true,
         'label' => __('Attendee Email', 'woocommerce'),
         ), $checkout->get_value( 'attendee_email' . $j) );
         


         $i++;
    }

         echo '</div>';
 }

}

You probably wonder why I didn’t just add a checkbox with extra options for each attendee – That was actually my first idea, but we can update cart on checkout based on checkbox value…but I have more than 1 checkbox with the same name and the same value (!).
PHP on the backend cannot process many fields with the same name as many fields. So if you check one checkbox, an extra fee is added. But if you check 2 checkboxes with the same name, eg. extra pdf, your cart fee is not updated. So I decided to go with numeric fields where user enters a number in the numeric field and on the backed we multiply number by the fee and get the cart updated.

conditional woocommerce checkout fields 2

Updated cart

//update cart when quantity is added
add_action( 'wp_footer', 'wc_add_servsafe_book' );
function wc_add_servsafe_book() {
    if (is_checkout()) {
    ?>
    <script type="text/javascript">
    jQuery( document ).ready(function( $ ) {
        jQuery("#extra_servsafe").addClass('extra_numeric');
        jQuery("#extra_servsafe").numeric();

        jQuery("#extra_pdf").addClass('extra_numeric');
        jQuery("#extra_pdf").numeric();

        jQuery("#extra_textbook").addClass('extra_numeric');
        jQuery("#extra_textbook").numeric();
        //jQuery("input[name=\"extra_servsafe\"]").click(function(){console.log('ok')})
        jQuery("#extra_servsafe").change(function(){
            if (jQuery(this).val()!=0) {
                jQuery('body').trigger('update_checkout');
            }
        });

        jQuery("#extra_textbook").change(function(){
            if (jQuery(this).val()!=0) {
                jQuery('body').trigger('update_checkout');
            }
        });  

        jQuery("#extra_pdf").change(function(){
            if (jQuery(this).val()!=0) {
                jQuery('body').trigger('update_checkout');
            }
        });              
    });
    </script>
    <?php
    }
}

This is how we calculate the fees. Pay attention on line 15, 21 and 27

$extracost = 25 * $post_data['extra_servsafe'];
$extracost = 70 * $post_data['extra_textbook'];
$extracost = 20 * $post_data['extra_pdf'];

This is the place where you set the actual fees for extra options and Woocommerce Cart calculations. Basically, change the number to your own price and don’t forget to update it as well on the labels in the first snippet!

If you need to change the wording on the “Your order” for the newly added items, you can change it on the lines 16, 22, 28.

add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' );
function woo_add_cart_fee( $cart ){
    if ( ! $_POST || ( is_admin() && ! is_ajax() ) ) {
    return;
    }

    if ( isset( $_POST['post_data'] ) ) {
        parse_str( $_POST['post_data'], $post_data );
    } else {
        $post_data = $_POST; // fallback for final checkout (non-ajax)
    }

    if (isset($post_data['extra_servsafe']) && $post_data['extra_servsafe']!=0  ) {
        $number = $post_data['extra_servsafe'];
        $extracost = 25 * $post_data['extra_servsafe']; 
        WC()->cart->add_fee( "Used ServSafe textbook ($number)", $extracost );
    }

    if (isset($post_data['extra_textbook'] )  && $post_data['extra_textbook']!=0 ) {
        $number = $post_data['extra_textbook'];
        $extracost = 70 * $post_data['extra_textbook']; 
        WC()->cart->add_fee( "New 6th Edition Textbook ($number)", $extracost );
    }

    if (isset($post_data['extra_pdf'])  && $post_data['extra_pdf']!=0  ) {
        $number = $post_data['extra_pdf'];
        $extracost = 20 * $post_data['extra_pdf']; 
        WC()->cart->add_fee( "Electronic PDF 70 page workbook ($number)", $extracost );
    }    
}

Now we are going to count how many attendees do we have and add error notices if the attendee fields are left blank.

conditional woocommerce checkout fields 3

Alert on empty checkout fields

/**
 * Process the checkout
 */
add_action('woocommerce_checkout_process', 'quantity_checkout_field_process');

function quantity_checkout_field_process() {
    // Check if set, if its not set add an error.

    //Check for product quantities
    $quantity_of_products_in_cart = product_quantities_in_cart();

    if($quantity_of_products_in_cart != 0) {
        $i = 0;
        while ($i < $quantity_of_products_in_cart) {
            $j = $i + 1;

            $attendee_name = 'attendee_name'. $j;
            $attendee_last_name = 'attendee_last_name'. $j;
            $attendee_phone = 'attendee_phone'. $j;
            $attendee_email = 'attendee_email'. $j;
            
            if ( ! $_POST[$attendee_name] )
                wc_add_notice( __( 'Please check on filling the First Name for attendee '. $j .'.' ), 'error' );

            if ( ! $_POST[$attendee_last_name] )
                wc_add_notice( __( 'Please check on filling the Last Name for attendee '. $j .'.' ), 'error' );

            if ( ! $_POST[$attendee_phone] )
                wc_add_notice( __( 'Please check on filling the Phone for attendee '. $j .'.' ), 'error' );

            if ( ! $_POST[$attendee_email] )
                wc_add_notice( __( 'Please check on filling the Email for attendee '. $j .'.' ), 'error' );

            $i++;
        }
    }
}

The next part for having your conditional checkout fields for WooCommerce complete, we need to add our entered values to the WooCommerce Order meta. This will display your conditional checkout fields on the backend inside your WooCommerce orders section.

conditional woocommerce checkout fields orders

This is how it looks inside the WooCommerce orders panel.

/**
 * Update the order meta with field value
 */
add_action( 'woocommerce_checkout_update_order_meta', 'quantity_checkout_field_update_order_meta' );

function quantity_checkout_field_update_order_meta( $order_id ) {

    $extra_servsafe_display = 'extra_servsafe';
    $extra_pdf_display = 'extra_pdf';
    $extra_textbook_display = 'extra_textbook';

    if ( ! empty( $_POST['extra_servsafe'] ) ) {
        update_post_meta( $order_id, $extra_servsafe_display, sanitize_text_field( $_POST['extra_servsafe'] ) );
    }
    
    if ( ! empty( $_POST['extra_pdf'] ) ) {
        update_post_meta( $order_id, $extra_pdf_display, sanitize_text_field( $_POST['extra_pdf'] ) );
    }
    
    if ( ! empty( $_POST['extra_textbook'] ) ) {
        update_post_meta( $order_id, $extra_textbook_display, sanitize_text_field( $_POST['extra_textbook'] ) );
    }

    $quantity_of_products_in_cart = product_quantities_in_cart();

    if($quantity_of_products_in_cart != 0) {
        $i = 0;
        while ($i < $quantity_of_products_in_cart) {
            $j = $i + 1;

            $attendee_name = 'attendee_name'. $j;
            $attendee_last_name = 'attendee_last_name'. $j;
            $attendee_phone = 'attendee_phone'. $j;
            $attendee_email = 'attendee_email'. $j;

            $attendee_name_display = 'First Name of Attendee '. $j;
            $attendee_last_name_display = 'Last Name of Attendee '. $j;
            $attendee_phone_display = 'Phone of Attendee '. $j;
            $attendee_email_display = 'Email of Attendee '. $j;
            
            if ( ! empty( $_POST[$attendee_name] ) ) {
                update_post_meta( $order_id, $attendee_name_display, sanitize_text_field( $_POST[$attendee_name] ) );
            }

            if ( ! empty( $_POST[$attendee_last_name] ) ) {
                update_post_meta( $order_id, $attendee_last_name_display, sanitize_text_field( $_POST[$attendee_last_name] ) );
            }

            if ( ! empty( $_POST[$attendee_phone] ) ) {
                update_post_meta( $order_id, $attendee_phone_display, sanitize_text_field( $_POST[$attendee_phone] ) );
            }

            if ( ! empty( $_POST[$attendee_email] ) ) {
                update_post_meta( $order_id, $attendee_email_display, sanitize_text_field( $_POST[$attendee_email] ) );
            }      
           $i++;
        }
    }
}

Now we will display field values on the order edit page just below “Item” section on edit order screen.

/**
 * Display field value on the order edit page
 */
add_action( 'woocommerce_admin_order_data_after_billing_address', 'quantity_checkout_field_display_admin_order_meta', 10, 1 );

function quantity_checkout_field_display_admin_order_meta($order){
    $order_id = $order->id;
    $extra_servsafe_number = get_post_meta($order_id, 'extra_servsafe', true);
    $extra_pdf_number = get_post_meta($order_id, 'extra_pdf', true);
    $extra_textbook_number = get_post_meta($order_id, 'extra_textbook', true);

    echo "<p><strong>Number of extra used ServSafe textbooks: </strong> $extra_servsafe_number </p>";
    echo "<p><strong>Number of extra ServSafe PDFs: </strong> $extra_pdf_number </p>";
    echo "<p><strong>Number of extra ServSafe new textbooks: </strong>  $extra_textbook_number </p>";

    $order_quantities = (int) $order->get_item_count();

    $i = 0;
    while ($i < $order_quantities) {
        $j = $i + 1;

        $attendee_name_display = 'First Name of Attendee '. $j;
        $attendee_last_name_display = 'Last Name of Attendee '. $j;
        $attendee_phone_display = 'Phone of Attendee '. $j;
        $attendee_email_display = 'Email of Attendee '. $j;

        echo '<p><strong>'.__($attendee_name_display).':</strong> ' . get_post_meta( $order->id, $attendee_name_display, true ) . '</p>';
        echo '<p><strong>'.__($attendee_last_name_display).':</strong> ' . get_post_meta( $order->id, $attendee_last_name_display, true ) . '</p>';
        echo '<p><strong>'.__($attendee_phone_display).':</strong> ' . get_post_meta( $order->id, $attendee_phone_display, true ) . '</p>';
        echo '<p><strong>'.__($attendee_email_display).':</strong> ' . get_post_meta( $order->id, $attendee_email_display, true ) . '</p>';

        $i++;
    }
}

 

/**
 * Check for product quantities in cart
 *
 * @return int
 */
function product_quantities_in_cart( ) {
 //Check to see if user has product in cart
 global $woocommerce;
 $quantity_of_products_in_cart = $woocommerce->cart->get_cart_contents_count();
 return $quantity_of_products_in_cart;
}

The last part is adding the field values to the emails. The fields are being added both to customer email and admin email. But note that in my example, I didn’t have a request to add the fields to the WooCommerce Ticket email as the client thought it’s enough to be shown on a email receipt.

add_action( 'woocommerce_email_after_order_table', 'wc_add_payment_type_to_emails', 15, 2 );

function wc_add_payment_type_to_emails( $order, $is_admin_email ) {
    $order_id = $order->id;
    $quantity_of_products_in_cart = $order->get_item_count();
    if($quantity_of_products_in_cart != 0) {
        $i = 0;
        while ($i < $quantity_of_products_in_cart) {
            $j = $i + 1;

            $attendee_name_display = 'First Name of Attendee '. $j;
            $attendee_last_name_display = 'Last Name of Attendee '. $j;
            $attendee_phone_display = 'Phone of Attendee '. $j;
            $attendee_email_display = 'Email of Attendee '. $j;

            echo '<p style="font-size:13px;">' .__ ( 'Attendee ' ) . $j .': ' . get_post_meta($order_id, $attendee_name_display, true) . ' ' . get_post_meta($order_id, $attendee_last_name_display, true) . ' | phone: ' . get_post_meta($order_id, $attendee_phone_display, true) . ' | email: ' . get_post_meta($order_id, $attendee_email_display, true) .'</p>';
            $i++;
        }
    }
}

That’s it! You have created your conditional checkout fields with pricing options in WooCommerce!

But there are still a few more things to do. I mentioned before that you’ll need to edit a couple of more files. Now it’s time to create a new folder inside your themes root and call it “js”. You will need a FTP access to your server for this or cPanel with file manager.

What you need to do is create a new file named numeric.js inside your js folder and paste the code below. This piece of javascript allows only valid characters to be entered into input boxes.

I have also appended this javascript file a bit. I mentioned before that I’ve placed a checkbox where buyer can select whether he is also attending event, and if checked, the Fields for the first attendee will be auto populated by the values from the billing section.

This is done by lines 11 – 18. You will need to run Chrome developer tools on your input fields and find the corresponding field value. In my case, values are as shown below.

function fillBilling(f) {
  if(f.autofill.checked == true) {
    f.attendee_name1.value = f.billing_first_name.value;
    f.attendee_last_name1.value = f.billing_last_name.value;
	f.attendee_phone1.value = f.billing_phone.value;
    f.attendee_email1.value = f.billing_email.value;
  } 
}
/*
 *
 * Copyright (c) 2006-2014 Sam Collett (http://www.texotela.co.uk)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version 1.4.1
 * Demo: http://www.texotela.co.uk/code/jquery/numeric/
 *
 */
function fillBilling(f) {
 if(f.autofill.checked == true) {
 f.attendee_name1.value = f.billing_first_name.value;
 f.attendee_last_name1.value = f.billing_last_name.value;
 f.attendee_phone1.value = f.billing_phone.value;
 f.attendee_email1.value = f.billing_email.value;
 } 
}
(function(factory){
	if(typeof define === 'function' && define.amd){
		define(['jquery'], factory);
	}else{
        factory(window.jQuery);
    }
}(function($) {
/*
 * Allows only valid characters to be entered into input boxes.
 * Note: fixes value when pasting via Ctrl+V, but not when using the mouse to paste
  *      side-effect: Ctrl+A does not work, though you can still use the mouse to select (or double-click to select all)
 *
 * @name     numeric
 * @param    config      { decimal : "." , negative : true }
 * @param    callback     A function that runs if the number is not valid (fires onblur)
 * @author   Sam Collett (http://www.texotela.co.uk)
 * @example  $(".numeric").numeric();
 * @example  $(".numeric").numeric(","); // use , as separator
 * @example  $(".numeric").numeric({ decimal : "," }); // use , as separator
 * @example  $(".numeric").numeric({ negative : false }); // do not allow negative values
 * @example  $(".numeric").numeric({ decimalPlaces : 2 }); // only allow 2 decimal places
 * @example  $(".numeric").numeric(null, callback); // use default values, pass on the 'callback' function
 *
 */
$.fn.numeric = function(config, callback)
{
	if(typeof config === 'boolean')
	{
		config = { decimal: config, negative: true, decimalPlaces: -1 };
	}
	config = config || {};
	// if config.negative undefined, set to true (default is to allow negative numbers)
	if(typeof config.negative == "undefined") { config.negative = true; }
	// set decimal point
	var decimal = (config.decimal === false) ? "" : config.decimal || ".";
	// allow negatives
	var negative = (config.negative === true) ? true : false;
    // set decimal places
	var decimalPlaces = (typeof config.decimalPlaces == "undefined") ? -1 : config.decimalPlaces;
	// callback function
	callback = (typeof(callback) == "function" ? callback : function() {});
	// set data and methods
	return this.data("numeric.decimal", decimal).data("numeric.negative", negative).data("numeric.callback", callback).data("numeric.decimalPlaces", decimalPlaces).keypress($.fn.numeric.keypress).keyup($.fn.numeric.keyup).blur($.fn.numeric.blur);
};

$.fn.numeric.keypress = function(e)
{
	// get decimal character and determine if negatives are allowed
	var decimal = $.data(this, "numeric.decimal");
	var negative = $.data(this, "numeric.negative");
    var decimalPlaces = $.data(this, "numeric.decimalPlaces");
	// get the key that was pressed
	var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
	// allow enter/return key (only when in an input box)
	if(key == 13 && this.nodeName.toLowerCase() == "input")
	{
		return true;
	}
	else if(key == 13)
	{
		return false;
	}
	//dont allow #, $, %
	else if(key == 35 || key == 36 || key == 37){
		return false;
	}
	var allow = false;
	// allow Ctrl+A
	if((e.ctrlKey && key == 97 /* firefox */) || (e.ctrlKey && key == 65) /* opera */) { return true; }
	// allow Ctrl+X (cut)
	if((e.ctrlKey && key == 120 /* firefox */) || (e.ctrlKey && key == 88) /* opera */) { return true; }
	// allow Ctrl+C (copy)
	if((e.ctrlKey && key == 99 /* firefox */) || (e.ctrlKey && key == 67) /* opera */) { return true; }
	// allow Ctrl+Z (undo)
	if((e.ctrlKey && key == 122 /* firefox */) || (e.ctrlKey && key == 90) /* opera */) { return true; }
	// allow or deny Ctrl+V (paste), Shift+Ins
	if((e.ctrlKey && key == 118 /* firefox */) || (e.ctrlKey && key == 86) /* opera */ ||
	  (e.shiftKey && key == 45)) { return true; }
	// if a number was not pressed
	if(key < 48 || key > 57)
	{
	  var value = $(this).val();
		/* '-' only allowed at start and if negative numbers allowed */
		if($.inArray('-', value.split('')) !== 0 && negative && key == 45 && (value.length === 0 || parseInt($.fn.getSelectionStart(this), 10) === 0)) { return true; }
		/* only one decimal separator allowed */
		if(decimal && key == decimal.charCodeAt(0) && $.inArray(decimal, value.split('')) != -1)
		{
			allow = false;
		}
		// check for other keys that have special purposes
		if(
			key != 8 /* backspace */ &&
			key != 9 /* tab */ &&
			key != 13 /* enter */ &&
			key != 35 /* end */ &&
			key != 36 /* home */ &&
			key != 37 /* left */ &&
			key != 39 /* right */ &&
			key != 46 /* del */
		)
		{
			allow = false;
		}
		else
		{
			// for detecting special keys (listed above)
			// IE does not support 'charCode' and ignores them in keypress anyway
			if(typeof e.charCode != "undefined")
			{
				// special keys have 'keyCode' and 'which' the same (e.g. backspace)
				if(e.keyCode == e.which && e.which !== 0)
				{
					allow = true;
					// . and delete share the same code, don't allow . (will be set to true later if it is the decimal point)
					if(e.which == 46) { allow = false; }
				}
				// or keyCode != 0 and 'charCode'/'which' = 0
				else if(e.keyCode !== 0 && e.charCode === 0 && e.which === 0)
				{
					allow = true;
				}
			}
		}
		// if key pressed is the decimal and it is not already in the field
		if(decimal && key == decimal.charCodeAt(0))
		{
			if($.inArray(decimal, value.split('')) == -1)
			{
				allow = true;
			}
			else
			{
				allow = false;
			}
		}
	}
	else
	{
		allow = true;
        // remove extra decimal places
 		if(decimal && decimalPlaces > 0)
 		{
            var selectionStart = $.fn.getSelectionStart(this);
            var selectionEnd = $.fn.getSelectionEnd(this);
            var dot = $.inArray(decimal, $(this).val().split(''));
            if (selectionStart === selectionEnd && dot >= 0 && selectionStart > dot && $(this).val().length > dot + decimalPlaces) {
                allow = false;
            }
        }

	}
	return allow;
};

$.fn.numeric.keyup = function(e)
{
	var val = $(this).val();
	if(val && val.length > 0)
	{
		// get carat (cursor) position
		var carat = $.fn.getSelectionStart(this);
		var selectionEnd = $.fn.getSelectionEnd(this);
		// get decimal character and determine if negatives are allowed
		var decimal = $.data(this, "numeric.decimal");
		var negative = $.data(this, "numeric.negative");
        var decimalPlaces = $.data(this, "numeric.decimalPlaces");

		// prepend a 0 if necessary
		if(decimal !== "" && decimal !== null)
		{
			// find decimal point
			var dot = $.inArray(decimal, val.split(''));
			// if dot at start, add 0 before
			if(dot === 0)
			{
				this.value = "0" + val;
				carat++;
            			selectionEnd++;
			}
			// if dot at position 1, check if there is a - symbol before it
			if(dot == 1 && val.charAt(0) == "-")
			{
				this.value = "-0" + val.substring(1);
				carat++;
            			selectionEnd++;
			}
			val = this.value;
		}

		// if pasted in, only allow the following characters
		var validChars = [0,1,2,3,4,5,6,7,8,9,'-',decimal];
		// get length of the value (to loop through)
		var length = val.length;
		// loop backwards (to prevent going out of bounds)
		for(var i = length - 1; i >= 0; i--)
		{
			var ch = val.charAt(i);
			// remove '-' if it is in the wrong place
			if(i !== 0 && ch == "-")
			{
				val = val.substring(0, i) + val.substring(i + 1);
			}
			// remove character if it is at the start, a '-' and negatives aren't allowed
			else if(i === 0 && !negative && ch == "-")
			{
				val = val.substring(1);
			}
			var validChar = false;
			// loop through validChars
			for(var j = 0; j < validChars.length; j++)
			{
				// if it is valid, break out the loop
				if(ch == validChars[j])
				{
					validChar = true;
					break;
				}
			}
			// if not a valid character, or a space, remove
			if(!validChar || ch == " ")
			{
				val = val.substring(0, i) + val.substring(i + 1);
			}
		}
		// remove extra decimal characters
		var firstDecimal = $.inArray(decimal, val.split(''));
		if(firstDecimal > 0)
		{
			for(var k = length - 1; k > firstDecimal; k--)
			{
				var chch = val.charAt(k);
				// remove decimal character
				if(chch == decimal)
				{
					val = val.substring(0, k) + val.substring(k + 1);
				}
			}
		}

        // remove extra decimal places
        if(decimal && decimalPlaces > 0)
        {
            var dot = $.inArray(decimal, val.split(''));
            if (dot >= 0)
            {
                val = val.substring(0, dot + decimalPlaces + 1);
                selectionEnd = Math.min(val.length, selectionEnd);
            }
        }
		// set the value and prevent the cursor moving to the end
		this.value = val;
		$.fn.setSelection(this, [carat, selectionEnd]);
	}
};

$.fn.numeric.blur = function()
{
	var decimal = $.data(this, "numeric.decimal");
	var callback = $.data(this, "numeric.callback");
	var negative = $.data(this, "numeric.negative");
	var val = this.value;
	if(val !== "")
	{
		var re = new RegExp("^" + (negative?"-?":"") + "\\d+$|^" + (negative?"-?":"") + "\\d*" + decimal + "\\d+$");
		if(!re.exec(val))
		{
			callback.apply(this);
		}
	}
};

$.fn.removeNumeric = function()
{
	return this.data("numeric.decimal", null).data("numeric.negative", null).data("numeric.callback", null).data("numeric.decimalPlaces", null).unbind("keypress", $.fn.numeric.keypress).unbind("keyup", $.fn.numeric.keyup).unbind("blur", $.fn.numeric.blur);
};

// Based on code from http://javascript.nwbox.com/cursor_position/ (Diego Perini <dperini@nwbox.com>)
$.fn.getSelectionStart = function(o)
{
	if(o.type === "number"){
		return undefined;
	}
	else if (o.createTextRange && document.selection)
	{
        var r = document.selection.createRange().duplicate();
        r.moveEnd('character', o.value.length);
		if (r.text == '') return o.value.length;

		return Math.max(0, o.value.lastIndexOf(r.text));
	} else {
        try { return o.selectionStart; }
        catch(e) { return 0; }
    }
};

// Based on code from http://javascript.nwbox.com/cursor_position/ (Diego Perini <dperini@nwbox.com>)
$.fn.getSelectionEnd = function(o)
{
	if(o.type === "number"){
		return undefined;
	}
	else if (o.createTextRange && document.selection) {
		var r = document.selection.createRange().duplicate()
		r.moveStart('character', -o.value.length)
		return r.text.length
	} else return o.selectionEnd
}

// set the selection, o is the object (input), p is the position ([start, end] or just start)
$.fn.setSelection = function(o, p)
{
	// if p is number, start and end are the same
	if(typeof p == "number") { p = [p, p]; }
	// only set if p is an array of length 2
	if(p && p.constructor == Array && p.length == 2)
	{
		if(o.type === "number") {
			o.focus();
		}
		else if (o.createTextRange)
		{
			var r = o.createTextRange();
			r.collapse(true);
			r.moveStart('character', p[0]);
			r.moveEnd('character', p[1] - p[0]);
			r.select();
		}
		else {
            o.focus();
            try{
                if(o.setSelectionRange)
                {
                    o.setSelectionRange(p[0], p[1]);
                }
            } catch(e) {
            }
        }
	}
};

}));

Finally, add the script somewhere before closing your body tag </body>. Check if the script is being loaded with your Chrome developer tools.

<script src = "<?php echo get_stylesheet_directory_uri();?>/js/numeric.js"></script>

We also need to add a small function inside your functions.php, I’ve placed it on the very top of my functions.php file.

Autofill in action

/** Adding a checkbox for autofill **/
add_action('woocommerce_after_checkout_billing_form', 'my_custom_checkout_field');
 
function my_custom_checkout_field( $checkout ) {

    echo '<input type="checkbox" name="autofill" onclick="fillBilling(this.form)">';
    echo '<em> Are you attending purchased classes? Check to autofill the data.</em>';   
}

I’m actually better in doing than explaining, but I truly hope that this tutorial will be useful to someone, with slight code modifications.

If you want to learn how to remove billing fields in Woocommerce, follow this article.

Thank you for reading my post on How to add conditional checkout fields with price in WooCommerce 3.0! Please comment and make suggestions on how to improve the code below!

Support New Articles with a Sweet Tee

One comment on “How to add conditional checkout fields with price in WooCommerce 3.0

  • Nick
    May 6, 2017 | 8:44 am

    Thank you! Great tutorial. I’m working on a very similar thing but I got stuck on updating the cart part. I used your code and modified it and it finally works!

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

*

*