Update Fee Dynamically Based on Radio Buttons in Woocommerce Checkout

Set a dynamic fee based on custom checkout radio buttons and text field in WooCommerce

The following is something advanced using Ajax and WC Sessions:

It will add a custom a tip (as a custom fee) based on selected radio buttons options: fixed percentages options or custom option that will show a text field to allow customer to input a fixed amount.

Displayed Fields are hand coded to get a better display than WooCommerce form fields for radio buttons (see the screenshots below).

Sample Image

How it works for the customer:

On checkout page load, a Tip of 5% (a fee) is applied (selected by default). When changing the selected option to something else, the applied fee changes.

If the "custom" option is selected, the fee is removed while a text field appears below:

Sample Image

Customer can input a fixed amount and a Tip (a fee) is applied with this amount.

Here is the code:

// Display custom checkout fields
add_action( 'woocommerce_after_checkout_billing_form', 'add_box_option_to_checkout' );
function add_box_option_to_checkout( ) {
// Here set your radio button options (Values / Labels pairs)
$options = array( '5' => '5%', '10' => '10%', '15' => '15%', 'custom' => __('Custom', 'woocommerce') );

// Radio button fields
echo '<style> #add_tip_field.form-row label { display:inline-block; margin-left:6px; } </style>
<p class="form-row form-row-wide" id="add_tip_field"><span class="woocommerce-input-wrapper">
<label for="add_tip"><strong>'.__('Add a tip', 'woocommerce'). ':</strong> </label>';

foreach ( $options as $value => $label_name ) {
$checked = $value == '5' ? ' checked="checked"' : '';

echo '<label for="add_tip_'.$value.'" class="radio ">
<input type="radio" class="input-radio " value="'.$value.'" name="add_tip" id="add_tip_'.$value.'"'.$checked.'> '.$label_name.'
</label>';
}
echo '</span></p>';

// Text field (hidden by default)
echo '<p class="form-row form-row-wide" id="custom_tip_field" style="display:none""><span class="woocommerce-input-wrapper">
<input type="text" class="input-text " name="custom_tip" id="custom_tip" value="" placeholder="'.__('Input a tip amount', 'woocommerce').'">
</span></p>';
}

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

var addTip = 'input[name="add_tip"]',
customTip = 'input[name="custom_tip"]'

function triggerAjaxEvent( amount, type = 'percent' ){
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'tip_fee',
'amount': amount,
'type' : type
},
success: function (result) {
$(document.body).trigger('update_checkout');
console.log(result);
},
});
}

triggerAjaxEvent( $(addTip+':checked').val() );

$('form.checkout').on('change', addTip, function() {
var textField = $('#custom_tip_field'),
percent = $(this).val();

if( percent === 'custom' && textField.css('display') === 'none' ) {
textField.show(200);
} else if ( percent !== 'custom' && textField.css('display') !== 'none' ) {
textField.hide(200, function(){
$(customTip).val('');
});
}
triggerAjaxEvent( percent );
});

$('form.checkout').on('input change', customTip, function() {
triggerAjaxEvent( $(this).val(), 'custom' );
});
});
</script>
<?php
}

// Get Ajax request and save data to WC session
add_action( 'wp_ajax_tip_fee', 'get_tip_fee' );
add_action( 'wp_ajax_nopriv_tip_fee', 'get_tip_fee' );
function get_tip_fee() {
if ( isset($_POST['amount']) && isset($_POST['type']) ) {
$fee = is_numeric($_POST['amount']) && $_POST['amount'] > 0 ? floatval($_POST['amount']) : 0;

WC()->session->set('fee_data', array(
'type' => esc_attr($_POST['type']),
'amount' => $fee
) );

print_r(WC()->session->get('fee_data'));
}
die();
}


// Add a dynamic fee from WC Session ajax data
add_action( 'woocommerce_cart_calculate_fees', 'checkout_custom_tip_fee' );
function checkout_custom_tip_fee( $cart ) {
if ( is_admin() && !defined('DOING_AJAX') )
return;

$data = WC()->session->get('fee_data');
$fee = $total = 0;

if ( isset($data['type']) && isset($data['amount']) ) {
// 1. Fixed Fee amount
if ( $data['type'] === 'custom' ) {
$text = $data['type'];
$fee = $data['amount'];
}
// 2. Calculated percentage Fee amount
elseif ( $data['type'] === 'percent' && $data['amount'] > 0 ) {
$text = $data['amount'] . '%';

// Get cart subtotal excl. Taxes (discounted)
foreach ( $cart->get_cart() as $cart_item ) {
$total = $cart_item['line_total'];
}
// Calculate fee
$fee = $total * $data['amount'] / 100 ;
}
// Add the fee
if ( $fee > 0 ) {
$cart->add_fee( sprintf( __('Tip (%s)', 'woocommerce' ), $text ), $fee );
}
}
}

Code goes in functions.php file of the active child theme (or active theme). Tested and works.


Addition related to your comment:

To use cart items total including taxes replace in last function:

            // Get cart subtotal excl. Taxes (discounted)
foreach ( $cart->get_cart() as $cart_item ) {
$total = $cart_item['line_total'];
}

with

            // Get cart subtotal Incl. Taxes (discounted)
foreach ( $cart->get_cart() as $cart_item ) {
$total = $cart_item['line_total'] + $cart_item['line_tax'];
}

WooCommerce checkout radio buttons that set a percentage fee based on specific items subtotal

To get a custom percentage fee based on specific cart items subtotal (for a defined set of product IDs) and on radio button (percentage warranty choice), use the following revisited code:

// Custom function to get related cart items subtotal for specific defined product Ids
function get_related_items_subtotal( $cart ) {
// HERE below define the related targeted products IDs in the array
$targeted_ids = array(29, 27, 28, 72, 84, 95);
$custom_subtotal = 0; // Initialize

// Loop through cart items
foreach ( $cart->get_cart() as $item ) {
if ( array_intersect($targeted_ids, array($item['product_id'], $item['variation_id']) ) ) {
$custom_subtotal += $item['line_subtotal'] + $item['line_subtotal_tax'];
}
}
return $custom_subtotal;
}

// 1 - Display custom checkout radio buttons fields
add_action( 'woocommerce_review_order_before_payment', 'display_custom_checkout_radio_buttons' );
function display_custom_checkout_radio_buttons() {
$custom_subtotal = get_related_items_subtotal( WC()->cart );

if ( $custom_subtotal > 0 ) {
$value = WC()->session->get( 'warranty' );
$value = empty( $value ) ? WC()->checkout->get_value( 'warranty' ) : $value;
$value = empty( $value ) ? '0' : $value;

echo '<div id="checkout-radio">
<h4>' . __("Choose your Warranty") .'</h4>';

woocommerce_form_field( 'warranty', array(
'type' => 'radio',
'class' => array( 'form-row-wide', 'update_totals_on_change' ),
'options' => array(
'0' => __( '1 Year Repair or Replace Warranty - Included', 'woocommerce' ),
'10' => __( '2 Years Extended Warranty', 'woocommerce' ) . ' (' . strip_tags( wc_price( 10 * $custom_subtotal / 100 ) ) . ')',
'15' => __( '3 Years Extended Warranty', 'woocommerce' ) . ' (' . strip_tags( wc_price( 15 * $custom_subtotal / 100 ) ) . ')',
),
), $value );

echo '</div>';
}


}

// 2 - Customizing Woocommerce checkout radio form field
add_filter( 'woocommerce_form_field_radio', 'custom_form_field_radio', 20, 4 );
function custom_form_field_radio( $field, $key, $args, $value ) {
if ( ! empty( $args['options'] ) && 'warranty' === $key && is_checkout() ) {
$field = str_replace( '</label><input ', '</label><br><input ', $field );
$field = str_replace( '<label ', '<label style="display:inline;margin-left:8px;" ', $field );
}
return $field;
}

// 3 - Add a percentage Fee based on radio buttons for specific defined product Ids
add_action( 'woocommerce_cart_calculate_fees', 'percentage_fee_based_on_radio_buttons', 20 );
function percentage_fee_based_on_radio_buttons( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;

$percentage = (float) WC()->session->get( 'warranty' );

if ( $percentage ) {
$custom_subtotal = get_related_items_subtotal( $cart );

if ( $custom_subtotal > 0 ) {
$label_text = sprintf( __('Extended Warranty %d years', 'woocommerce'), $percentage == 10 ? 2 : 3 );
$cart->add_fee( $label_text, $custom_subtotal * $percentage / 100 );
}
}
}

// 4 - Set chosen radio button value to a WC Session variable
add_action( 'woocommerce_checkout_update_order_review', 'chosen_input_radio_button_value_to_wc_session' );
function chosen_input_radio_button_value_to_wc_session( $posted_data ) {
parse_str( $posted_data, $fields );

if ( isset( $fields['warranty'] ) ){
WC()->session->set( 'warranty', $fields['warranty'] );
}
}

Code goes in functions.php file of the active child theme (or active theme). Tested and works.

Add a dynamic fee based on a select field in WooCommerce Checkout

I have made some changes to the original code that will:

  • Display a custom select field (instead of radio buttons input fields)
  • Display a custom error notice if customer has not selected a packing option
  • Display the selected packing type everywhere (on orders and email notifications)

The code:

// Add a custom select fields for packing option fee
add_action( 'woocommerce_review_order_after_shipping', 'checkout_shipping_form_packing_addition', 20 );
function checkout_shipping_form_packing_addition( ) {
$domain = 'woocommerce';

echo '<tr class="packing-select"><th>' . __('Packing options', $domain) . '</th><td>';

$chosen = WC()->session->get('chosen_packing');

// Add a custom checkbox field
woocommerce_form_field( 'chosen_packing', array(
'type' => 'select',
'class' => array( 'form-row-wide packing' ),
'options' => array(
'' => __("Choose a packing option ...", $domain),
'bag' => sprintf( __("In a bag (%s)", $domain), strip_tags( wc_price(3.00) ) ),
'box' => sprintf( __("In a gift box (%s)", $domain), strip_tags( wc_price(9.00) ) ),
),
'required' => true,
), $chosen );

echo '</td></tr>';
}

// jQuery - Ajax script
add_action( 'wp_footer', 'checkout_shipping_packing_script' );
function checkout_shipping_packing_script() {
// Only checkout page
if ( is_checkout() && ! is_wc_endpoint_url() ) :

WC()->session->__unset('chosen_packing');
?>
<script type="text/javascript">
jQuery( function($){
$('form.checkout').on('change', 'select#chosen_packing', function(){
var p = $(this).val();
console.log(p);
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'woo_get_ajax_data',
'packing': p,
},
success: function (result) {
$('body').trigger('update_checkout');
console.log('response: '+result); // just for testing | TO BE REMOVED
},
error: function(error){
console.log(error); // just for testing | TO BE REMOVED
}
});
});
});
</script>
<?php
endif;
}

// Php Ajax (Receiving request and saving to WC session)
add_action( 'wp_ajax_woo_get_ajax_data', 'woo_get_ajax_data' );
add_action( 'wp_ajax_nopriv_woo_get_ajax_data', 'woo_get_ajax_data' );
function woo_get_ajax_data() {
if ( isset($_POST['packing']) ){
$packing = sanitize_key( $_POST['packing'] );
WC()->session->set('chosen_packing', $packing );
echo json_encode( $packing );
}
die(); // Alway at the end (to avoid server error 500)
}

// Add a custom dynamic packaging fee
add_action( 'woocommerce_cart_calculate_fees', 'add_packaging_fee', 20, 1 );
function add_packaging_fee( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;

$domain = "woocommerce";
$packing_fee = WC()->session->get( 'chosen_packing' ); // Dynamic packing fee

if ( $packing_fee === 'bag' ) {
$label = __("Bag packing fee", $domain);
$cost = 3.00;
} elseif ( $packing_fee === 'box' ) {
$label = __("Gift box packing fee", $domain);
$cost = 9.00;
}

if ( isset($cost) )
$cart->add_fee( $label, $cost );
}

// Field validation, as this packing field is required
add_action('woocommerce_checkout_process', 'packing_field_checkout_process');
function packing_field_checkout_process() {
// Check if set, if its not set add an error.
if ( isset($_POST['chosen_packing']) && empty($_POST['chosen_packing']) )
wc_add_notice( __( "Please choose a packing option...", "woocommerce" ), 'error' );
}

Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Sample Image

The error message when customer hasn't chosen a packing option:

Sample Image

Additional radio button in a custom checkout field group that update a fee in Woocommerce

For your 3 radio button (3 options in the array) like:

'options' => array(
'bag' => __('In a bag '.wc_price(3.00), $domain),
'box' => __('In a gift box '.wc_price(9.00), $domain),
'speedboat' => __('In a speedboat '.wc_price(20.00), $domain),
),

You will have to replace this original code:

$packing_fee = WC()->session->get( 'chosen_packing' ); // Dynamic packing fee
$fee = $packing_fee == 'box' ? 9.00 : 3.00;
$cart->add_fee( __( 'Packaging fee', 'woocommerce' ), $fee );

with the following (for a third option):

$packing_fee = WC()->session->get( 'chosen_packing' ); // Dynamic packing fee

if( $packing_fee === 'box' )
$fee = 9.00;
else if( $packing_fee === 'speedboat' )
$fee = 20.00;
else
$fee = 3.00;

$cart->add_fee( __( 'Packaging fee', 'woocommerce' ), $fee );

or this similar in a more compact way:

$packing_fee = WC()->session->get( 'chosen_packing' ); // Dynamic packing fee
$fee = $packing_fee === 'bag' ? 3.00 : ( $packing_fee === 'box' ? 9.00 : 20.00 );
$cart->add_fee( __( 'Packaging fee', 'woocommerce' ), $fee );

Both should work.

WooCommerce checkout radio buttons that set a percentage fee based on the original price of a specific product

I added some code to get_related_items_subtotal() to check with SKU. Check the below code.

// Custom function to get related cart items subtotal for specific defined product Ids
function get_related_items_subtotal( $cart ) {
// HERE below define the related targeted products IDs in the array
$targeted_skus = array('BCA', 'ABC', 'MPN', 'A4545A', '5656FGDF', 'FDF4FD');
$custom_subtotal = 0; // Initialize

// Loop through cart items
foreach ( $cart->get_cart() as $item ) {

// Retrieve WC_Product object from the product-id:
$product = wc_get_product( $item["variation_id"] ? $item["variation_id"] : $item["product_id"] );

$sku = $product->get_sku();

if( is_string_contain_word( $sku, $targeted_skus ) ){
$custom_subtotal += $item['line_subtotal'] + $item['line_subtotal_tax'];
}

}
return $custom_subtotal;
}

// 1 - Display custom checkout radio buttons fields
add_action( 'woocommerce_review_order_before_payment', 'display_custom_checkout_radio_buttons' );
function display_custom_checkout_radio_buttons() {
$custom_subtotal = get_related_items_subtotal( WC()->cart );

if ( $custom_subtotal > 0 ) {
$value = WC()->session->get( 'warranty' );
$value = empty( $value ) ? WC()->checkout->get_value( 'warranty' ) : $value;
$value = empty( $value ) ? '0' : $value;

echo '<div id="checkout-radio">
<h4>' . __("Choose your Warranty") .'</h4>';

woocommerce_form_field( 'warranty', array(
'type' => 'radio',
'class' => array( 'form-row-wide', 'update_totals_on_change' ),
'options' => array(
'0' => __( '1 Year Repair or Replace Warranty - Included', 'woocommerce' ),
'10' => __( '2 Years Extended Warranty', 'woocommerce' ) . ' (' . strip_tags( wc_price( 10 * $custom_subtotal / 100 ) ) . ')',
'15' => __( '3 Years Extended Warranty', 'woocommerce' ) . ' (' . strip_tags( wc_price( 15 * $custom_subtotal / 100 ) ) . ')',
),
), $value );

echo '</div>';
}


}

// 2 - Customizing Woocommerce checkout radio form field
add_filter( 'woocommerce_form_field_radio', 'custom_form_field_radio', 20, 4 );
function custom_form_field_radio( $field, $key, $args, $value ) {
if ( ! empty( $args['options'] ) && 'warranty' === $key && is_checkout() ) {
$field = str_replace( '</label><input ', '</label><br><input ', $field );
$field = str_replace( '<label ', '<label style="display:inline;margin-left:8px;" ', $field );
}
return $field;
}

// 3 - Add a percentage Fee based on radio buttons for specific defined product Ids
add_action( 'woocommerce_cart_calculate_fees', 'percentage_fee_based_on_radio_buttons', 20 );
function percentage_fee_based_on_radio_buttons( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;

$percentage = (float) WC()->session->get( 'warranty' );

if ( $percentage ) {
$custom_subtotal = get_related_items_subtotal( $cart );

if ( $custom_subtotal > 0 ) {
$label_text = sprintf( __('Extended Warranty %d years', 'woocommerce'), $percentage == 10 ? 2 : 3 );
$cart->add_fee( $label_text, $custom_subtotal * $percentage / 100 );
}
}
}

// 4 - Set chosen radio button value to a WC Session variable
add_action( 'woocommerce_checkout_update_order_review', 'chosen_input_radio_button_value_to_wc_session' );
function chosen_input_radio_button_value_to_wc_session( $posted_data ) {
parse_str( $posted_data, $fields );

if ( isset( $fields['warranty'] ) ){
WC()->session->set( 'warranty', $fields['warranty'] );
}
}

function is_string_contain_word( $find, $string, $exact = false ){
if( is_array( $string ) ){
foreach ( $string as $key => $str ) {
if( !$exact ){
$find = strtolower( $find );
$str = strtolower( $str );
}
if ( preg_match( '/\b'.$find.'\b/', $str ) ) {
return true;
}
}
return false;
}else{
if( !$exact ){
$find = strtolower( $find );
$string = strtolower( $string );
}
if ( preg_match( '/\b'.$find.'\b/', $string ) ) {
return true;
}
return false;
}
}

Dynamic shipping fee based on custom radio buttons in Woocommerce


You should give all necessary related code in your question. Remember that "Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself".

So, in the code below, you will find a complete working solution, with additional custom radio buttons, that will add dynamically a delivery fee depending on the selected radio button and for "local pickup" shipping method.

Sample Image

The code (where you will need to define your targeted "local pickup" method ID):

// Enabling delivery options for a specific defined shipping method
function targeted_shipping_method(){
// HERE below define the shipping method Id that enable the custom delivery options
return 'local_pickup:5';
}

// Customizing Woocommerce checkout radio form field
add_action( 'woocommerce_form_field_radio', 'custom_form_field_radio', 20, 4 );
function custom_form_field_radio( $field, $key, $args, $value ) {
if ( ! empty( $args['options'] ) && is_checkout() ) {
$field = str_replace( '</label><input ', '</label><br><input ', $field );
$field = str_replace( '<label ', '<label style="display:inline;margin-left:8px;" ', $field );
}
return $field;
}

// Add a custom radio fields for packaging selection
add_action( 'woocommerce_review_order_after_shipping', 'checkout_shipping_form_delivery_addition', 20 );
function checkout_shipping_form_delivery_addition(){
$domain = 'wocommerce';

if ( WC()->session->get( 'chosen_shipping_methods' )[0] == targeted_shipping_method() ) :

echo '<tr class="delivery-radio"><th>' . __('Delivery options', $domain) . '</th><td>';

$chosen = WC()->session->get('chosen_delivery');
$chosen = empty($chosen) ? WC()->checkout->get_value('delivery') : $chosen;
$chosen = empty($chosen) ? 'regular' : $chosen;

// Add a custom checkbox field
woocommerce_form_field( 'radio_delivery', array(
'type' => 'radio',
'class' => array( 'form-row-wide' ),
'options' => array(
'regular' => __('Regular', $domain),
'premium' => __('Premium +'.wc_price(2.00), $domain),
),
'default' => $chosen,
), $chosen );

echo '</td></tr>';

endif;
}

// jQuery - Ajax script
add_action( 'wp_footer', 'checkout_delivery_script' );
function checkout_delivery_script() {
// Only checkout page
if ( ! is_checkout() ) return;
?>
<script type="text/javascript">
jQuery( function($){
if (typeof wc_checkout_params === 'undefined')
return false;

$('form.checkout').on('change', 'input[name=radio_delivery]', function(e){
e.preventDefault();
var d = $(this).val();
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'delivery',
'delivery': d,
},
success: function (result) {
$('body').trigger('update_checkout');
console.log(result); // just for testing | TO BE REMOVED
},
error: function(error){
console.log(error); // just for testing | TO BE REMOVED
}
});
});
});
</script>
<?php

}

// Get Ajax request and saving to WC session
add_action( 'wp_ajax_delivery', 'wc_get_delivery_ajax_data' );
add_action( 'wp_ajax_nopriv_delivery', 'wc_get_delivery_ajax_data' );
function wc_get_delivery_ajax_data() {
if ( isset($_POST['delivery']) ){
WC()->session->set('chosen_delivery', sanitize_key( $_POST['delivery'] ) );
echo json_encode( $delivery ); // Return the value to jQuery
}
die();
}

// Add a custom dynamic delivery fee
add_action( 'woocommerce_cart_calculate_fees', 'add_packaging_fee', 20, 1 );
function add_packaging_fee( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;

// Only for targeted shipping method
if ( WC()->session->get( 'chosen_shipping_methods' )[0] != targeted_shipping_method() )
return;

if( WC()->session->get( 'chosen_delivery' ) == 'premium' )
$cart->add_fee( __( 'Delivery fee', 'woocommerce' ), 2.00 );
}

Code goes in function.php file of your active child theme (or active theme). Tested and works.



Related Topics



Leave a reply



Submit