I put a radio button in the checkout page with the code below.
add_action( 'woocommerce_before_checkout_shipping_form', 'custom_shipping_radio_button', 10, 1 );
function custom_shipping_radio_button( $checkout ) {
woocommerce_form_field( 'shipping_type', array(
'type' => 'radio',
'class' => array( 'form-row-wide' ),
'label' => __('收件方式 *'),
'options' => array(
'shipping_1' => __('全家店到店'),
'shipping_2' => __('指定地址'),
'shipping_3' => __('自行取貨'),
),
), $checkout->get_value( 'shipping_type' ) );
}
I want to hide the option based on shipping method. For example, if the customers choose local pickup, options, shipping_1 and shipping_2 will disappear. I searched some information and tried to make the codes as below.
add_action( 'woocommerce_after_checkout_form', 'hide_shipping_type' );
function hide_shipping_type( $available_gateways ) {
global $woocommerce;
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_no_ajax = $chosen_methods[0];
if ( 0 === strpos( $chosen_shipping_no_ajax, 'local_pickup' ) ) {
?>
<script type="text/javascript">
jQuery('#shipping_type_shipping_1,#shipping_type_shipping_2').fadeOut();
</script>
<?php
}
?>
<script type="text/javascript">
jQuery('form.checkout').on('change','input[name^="shipping_method"]',function() {
var val = jQuery( this ).val();
if (val.match("^local_pickup")) {
jQuery('#shipping_type_shipping_1,#shipping_type_shipping_2').fadeOut();
} else {
jQuery('#shipping_type_shipping_1,#shipping_type_shipping_2').fadeIn();
}
});
</script>
<?php
}
I found that the labels for the options cannot be hidden. I thought the problem may be caused by jQuery script. However, I cannot make it better.
Does anybody have idea about this problem?
UPDATE
I got a new idea about unsetting shipping method in cart page and hiding shipping method in checkout page based on shipping type radio button. As a result, I tried to write the code as below. These codes can work and the label of shipping method can disappear. However, after selecting the one of shipping methods, the other hide shipping methods will fade in. Is there any solution?
//Unset shipping method in cart page
add_filter( 'woocommerce_cart_ready_to_calc_shipping', 'disable_shipping_calc_on_cart', 99 );
function disable_shipping_calc_on_cart( $show_shipping ) {
if( is_cart() ) {
return false;
}
return $show_shipping;
}
//Hide shipping method in checkout page based on the selection of radio button.
add_action( 'woocommerce_before_checkout_shipping_form', 'custom_shipping_radio_button', 10, 1 );
function custom_shipping_radio_button( $checkout ) {
woocommerce_form_field( 'shipping_type', array(
'type' => 'radio',
'class' => array( 'form-row-wide' ),
'label' => __('收件方式 *'),
'options' => array(
'shipping_1' => __('全家店到店'),
'shipping_2' => __('指定地址'),
'shipping_3' => __('自行取貨'),
),
), $checkout->get_value( 'shipping_type' ) );
?>
<script type="text/javascript">
jQuery(function($){
$("input[name=shipping_type]").on("change",function(){
if($("#shipping_type_shipping_1").is(":checked")) {
$("#add_familimart,#shipping_first_name_field,#shipping_last_name_field,#shipping_city_field,#shipping_company_field,#shipping_method_0_flat_rate9,label[for='shipping_method_0_flat_rate9']").fadeIn();
} else {
$("#add_familimart,#shipping_first_name_field,#shipping_last_name_field,#shipping_city_field,#shipping_company_field,#shipping_method_0_flat_rate9,label[for='shipping_method_0_flat_rate9']").fadeOut();
}
if($("#shipping_type_shipping_2").is(":checked")) {
$("#shipping_postcode_field,#shipping_address_1_field,#shipping_method_0_flat_rate10,#shipping_method_0_flat_rate11,#shipping_method_0_flat_rate12,label[for='shipping_method_0_flat_rate12'],label[for='shipping_method_0_flat_rate11'],label[for='shipping_method_0_flat_rate10']").fadeIn();
} else {
$("#shipping_postcode_field,#shipping_address_1_field,#shipping_method_0_flat_rate10,#shipping_method_0_flat_rate11,#shipping_method_0_flat_rate12,label[for='shipping_method_0_flat_rate12'],label[for='shipping_method_0_flat_rate11'],label[for='shipping_method_0_flat_rate10']").fadeOut();
}
if($("#shipping_type_shipping_3").is(":checked")) { $("#shipping_address_2_field,#shipping_method_0_local_pickup8,label[for='shipping_method_0_local_pickup8']").fadeIn();
} else {
$("#shipping_address_2_field,#shipping_method_0_local_pickup8,label[for='shipping_method_0_local_pickup8']").fadeOut();
}
});
});
</script>
<?php
}
You can merge all your code in the first function and it work as well. Now you should need to add jQuery ready() function at start.
It's quiet simple and easy to target <label> tags with a "for" attribute using in your case label[for="shipping_type_shipping_1"] and label[for="shipping_type_shipping_2"]…
I have revisited and compacted your code in one unique hooked function:
add_action( 'woocommerce_before_checkout_shipping_form', 'custom_shipping_radio_buttons', 10, 1 );
function custom_shipping_radio_buttons( $checkout ) {
woocommerce_form_field( 'shipping_type', array(
'type' => 'radio',
'class' => array( 'form-row-wide' ),
'label' => __('收件方式 *'),
'options' => array(
'shipping_1' => __('全家店到店'),
'shipping_2' => __('指定地址'),
'shipping_3' => __('自行取貨'),
),
), $checkout->get_value( 'shipping_type' ) );
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' )[0];
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
var a = 'shipping_type_shipping_',
b = 'label[for="'+a+'1"],label[for="'+a+'2"],#'+a+'1,#'+a+'2';
<?php if ( 0 === strpos( $chosen_shipping_methods, 'local_pickup' ) ): ?>
$(b).fadeOut(); // Once DOM is loaded
<?php endif; ?>
// On live "change event
$('form.checkout').on('change','input[name^="shipping_method"]',function() {
var c = $(this).val();
if ( c.match('^local_pickup') )
$(b).fadeOut();
else
$(b).fadeIn();
});
});
</script>
<?php
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works. It shows / hide the 2 radio buttons + their labels, depending if "local_pickup" is the chosen Shipping Method…
Update (related to your comment)
May be you should try something like this:
add_action( 'woocommerce_before_checkout_shipping_form', 'custom_shipping_radio_buttons', 10, 1 );
function custom_shipping_radio_buttons( $checkout ) {
woocommerce_form_field( 'shipping_type', array(
'type' => 'radio',
'class' => array( 'form-row-wide' ),
'label' => __('收件方式 *'),
'options' => array(
'shipping_1' => __('全家店到店'),
'shipping_2' => __('指定地址'),
'shipping_3' => __('自行取貨'),
),
), $checkout->get_value( 'shipping_type' ) );
// HERE below define your shipping "flat rates" method IDs in the array
$other_method_ids = array( 'flat_rate:09', 'flat_rate:10', 'flat_rate:11', 'flat_rate:12' );
$local_pickup = 'local_pickup';
// Get the chosen shipping method
$chosen_shipping = WC()->session->get( 'chosen_shipping_methods' )[0];
// Get the chosen shipping method ID
$chosen_shipping_expl = explode( ':', $chosen_shipping );
$chosen_method_id = $chosen_shipping_expl[0];
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
var a = 'shipping_type_shipping_',
b = 'label[for="'+a+'1"],label[for="'+a+'2"],#'+a+'1,#'+a+'2',
c = <?php echo json_encode( $other_method_ids ); ?>; // array of shipping methods ids
// Once DOM is loaded
<?php if ( $chosen_method_id === $local_pickup || in_array( $chosen_shipping, $other_method_ids) ): ?>
$(b).fadeOut();
<?php endif; ?>
// On live "change event
$('form.checkout').on('change','input[name^="shipping_method"]',function() {
var d = $(this).val();
console.log(e);
if ( e.match('^local_pickup') || $.inArray(d, c) !== -1 )
$(b).fadeOut();
else
$(b).fadeIn();
});
});
</script>
<?php
}
Tested and works.
I tried to add the codes for the other options with your codes. However, it cannot works well.
$chosen_shipping_methods_2 = WC()->session->get( 'chosen_shipping_methods' )[0];
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
var d = 'shipping_type_shipping_',
e ='label[for="'+d+'2"],label[for="'+d+'3"],#'+d+'2,#'+d+'3';
<?php if ( 0 === strpos( $chosen_shipping_methods_2, 'flat_rate:9' ) ): ?>
$(e).fadeOut(); // Once DOM is loaded
<?php endif; ?>
// On live "change event
$('form.checkout').on('change','input[name^="shipping_method"]',function() {
var f = $(this).val();
if ( f.match('^flat_rate:9') )
$(e).fadeOut();
else
$(e).fadeIn();
});
});
</script>
<?php
$chosen_shipping_methods_3 = WC()->session->get( 'chosen_shipping_methods' )[0];
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
var g = 'shipping_type_shipping_',
h = 'label[for="'+g+'1"],label[for="'+g+'3"],#'+g+'1,#'+g+'3';
<?php if ( 0 === strpos( $chosen_shipping_methods_3, 'flat_rate:10', 'flat_rate:11', 'flat_rate:12' ) ): ?>
$(h).fadeOut(); // Once DOM is loaded
<?php endif; ?>
// On live "change event
$('form.checkout').on('change','input[name^="shipping_method"]',function() {
var i = $(this).val();
if ( i.match('^flat_rate:10', '^flat_rate:11', '^flat_rate:12') )
$(h).fadeOut();
else
$(h).fadeIn();
});
});
</script>
<?php
Related
Inspired from Shipping carrier custom fields validation in Woocommerce checkout page answer code, I use following code which displays a select field with shipping companies (this field is displayed only when I choose a specific shipping method):
add_action( 'woocommerce_after_shipping_rate', 'carrier_custom_fields', 20, 2 );
function carrier_custom_fields( $method, $index ) {
if( ! is_checkout()) return; // Only on the checkout page
$customer_carrier_method = 'flat_rate:14';
if( $method->id != $customer_carrier_method ) return; // Mostrar solo para "flat_rate:14"
$chosen_method_id = WC()->session->chosen_shipping_methods[ $index ];
// If the chosen shipping method is 'flat_rate: 14', we will show
if($chosen_method_id == $customer_carrier_method ):
echo '<div class="custom-carrier2">';
woocommerce_form_field( 'carrier_name1', array(
'type' => 'select',
'class' => array('carrier_name2-class form-row-wide'),
'label' => __('<strong>Shipping Company</strong>'),
'required' => 'true',
'options' => array(
'1' => '', // no data means that the field is not selected
'Shipping Company 1' => 'Shipping Company 1',
'Shipping Company 2' => 'Shipping Company 2',
'Shipping Company 3' => 'Shipping Company 3',
'Shipping Company 4' => 'Shipping Company 4'
)
), WC()->checkout->get_value( 'carrier_name1' ) );
echo '</div>';
endif;
}
// Validate the custom selection field
add_action('woocommerce_checkout_process', 'carrier_checkout_process');
function carrier_checkout_process() {
if( isset( $_POST['carrier_name1'] ) && empty( $_POST['carrier_name1'] ) )
wc_add_notice( ( "<strong>Shipping Company</strong> it is a required field." ), "error" );
}
// Save custom fields to sort metadata
add_action( 'woocommerce_checkout_update_order_meta', 'carrier_update_order_meta', 30, 1 );
function carrier_update_order_meta( $order_id ) {
if( isset( $_POST['carrier_name1'] ))
update_post_meta( $order_id, 'carrier_name1', sanitize_text_field( $_POST['carrier_name1'] ) );
}
The problem is that it's only displayed on the checkout page and I would like it to show it in cart page, keeping the selected vale on cart page to checkout page.
I think I have found something that says that this transfer of the selected data between the cart and the payment page is done through Ajax, but I am not skilled with Ajax and I don't know how to make that work.
To make that work on cart and checkout pages, you will need some additional code using jQuery, Ajax and WC Session variable:
Final update - 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 carrier_settings(){
return array(
'targeted_methods' => array('flat_rate:14'), // Your targeted shipping method(s) in this array
'field_id' => 'carrier_name', // Field Id
'field_type' => 'select', // Field type
'field_label' => '', // Leave empty value if the first option has a text (see below).
'label_name' => __("Carrier company","woocommerce"), // for validation and as meta key for orders
'field_options' => array(
// The option displayed at first ( or keep an empty value '',)
__("Choose a carrier company", "woocommerce"),
// The carrier companies below (one by line)
'Company name 1',
'Company name 2',
'Company name 3',
'Company name 4',
),
);
}
Then we can load that settings on any function where it's needed.
Now the Select field with carrier companies displayed for a specific shipping method on cart and checkout pages:
// Display the custom checkout field
add_action( 'woocommerce_after_shipping_rate', 'carrier_company_custom_select_field', 20, 2 );
function carrier_company_custom_select_field( $method, $index ) {
extract( carrier_settings() ); // Load settings and convert them in variables
$chosen = WC()->session->get('chosen_shipping_methods'); // The chosen methods
$value = WC()->session->get($field_id);
$value = WC()->session->__isset($field_id) ? $value : WC()->checkout->get_value('_'.$field_id);
$options = array(); // Initializing
if( ! empty($chosen) && $method->id === $chosen[$index] && in_array($method->id, $targeted_methods) ) {
echo '<div class="custom-carrier">';
// Loop through field otions to add the correct keys
foreach( $field_options as $key => $option_value ) {
$option_key = $key == 0 ? '' : $key;
$options[$option_key] = $option_value;
}
woocommerce_form_field( $field_id, array(
'type' => $field_type,
'label' => '', // Not required if the first option has a text.
'class' => array('form-row-wide ' . $field_id . '-' . $field_type ),
'required' => true,
'options' => $options,
), $value );
echo '</div>';
}
}
The Ajax part: The jQuery sender + PHP WordPress admin Ajax receiver code for the selected carrier company:
// jQuery code (client side) - Ajax sender
add_action( 'wp_footer', 'carrier_company_script_js' );
function carrier_company_script_js() {
// Only cart & checkout pages
if( is_cart() || ( is_checkout() && ! is_wc_endpoint_url() ) ):
// Load settings and convert them in variables
extract( carrier_settings() );
$js_variable = is_cart() ? 'wc_cart_params' : 'wc_checkout_params';
// jQuery Ajax code
?>
<script type="text/javascript">
jQuery( function($){
if (typeof <?php echo $js_variable; ?> === 'undefined')
return false;
$(document.body).on( 'change', 'select#<?php echo $field_id; ?>', function(){
var value = $(this).val();
$.ajax({
type: 'POST',
url: <?php echo $js_variable; ?>.ajax_url,
data: {
'action': 'carrier_name',
'value': value
},
success: function (result) {
console.log(result); // Only for testing (to be removed)
}
});
});
});
</script>
<?php
endif;
}
// The Wordpress Ajax PHP receiver
add_action( 'wp_ajax_carrier_name', 'set_carrier_company_name' );
add_action( 'wp_ajax_nopriv_carrier_name', 'set_carrier_company_name' );
function set_carrier_company_name() {
if ( isset($_POST['value']) ){
// Load settings and convert them in variables
extract( carrier_settings() );
if( empty($_POST['value']) ) {
$value = 0;
$label = 'Empty';
} else {
$value = $label = esc_attr( $_POST['value'] );
}
// Update session variable
WC()->session->set( $field_id, $value );
// Send back the data to javascript (json encoded)
echo $label . ' | ' . $field_options[$value];
die();
}
}
Then on checkout page, the field validation and saving the chosen carrier company to the order:
// Conditional function for validation
function has_carrier_field(){
$settings = carrier_settings();
return array_intersect(WC()->session->get( 'chosen_shipping_methods' ), $settings['targeted_methods']);
}
// Validate the custom selection field
add_action('woocommerce_checkout_process', 'carrier_company_checkout_validation');
function carrier_company_checkout_validation() {
// Load settings and convert them in variables
extract( carrier_settings() );
if( has_carrier_field() && isset( $_POST[$field_id] ) && empty( $_POST[$field_id] ) )
wc_add_notice(
sprintf( __("Please select a %s as it is a required field.","woocommerce"),
'<strong>' . $label_name . '</strong>'
), "error" );
}
// Save custom field as order meta data
add_action( 'woocommerce_checkout_create_order', 'save_carrier_company_as_order_meta', 30, 1 );
function save_carrier_company_as_order_meta( $order ) {
// Load settings and convert them in variables
extract( carrier_settings() );
if( has_carrier_field() && isset( $_POST[$field_id] ) && ! empty( $_POST[$field_id] ) ) {
$order->update_meta_data( '_'.$field_id, $field_options[esc_attr($_POST[$field_id])] );
WC()->session->__unset( $field_id ); // remove session variable
}
}
Display the selected carrier on admin order pages, on customer orders and email notifications:
// Display custom field in admin order pages
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'admin_order_display_carrier_company', 30, 1 );
function admin_order_display_carrier_company( $order ) {
// Load settings and convert them in variables
extract( carrier_settings() );
$carrier = $order->get_meta( '_'.$field_id ); // Get carrier company
if( ! empty($carrier) ) {
// Display
echo '<p><strong>' . $label_name . '</strong>: ' . $carrier . '</p>';
}
}
// Display carrier company after shipping line everywhere (orders and emails)
add_filter( 'woocommerce_get_order_item_totals', 'display_carrier_company_on_order_item_totals', 1000, 3 );
function display_carrier_company_on_order_item_totals( $total_rows, $order, $tax_display ){
// Load settings and convert them in variables
extract( carrier_settings() );
$carrier = $order->get_meta( '_'.$field_id ); // Get carrier company
if( ! empty($carrier) ) {
$new_total_rows = [];
// Loop through order total rows
foreach( $total_rows as $key => $values ) {
$new_total_rows[$key] = $values;
// Inserting the carrier company under shipping method
if( $key === 'shipping' ) {
$new_total_rows[$field_id] = array(
'label' => $label_name,
'value' => $carrier,
);
}
}
return $new_total_rows;
}
return $total_rows;
}
All code goes on functions.php file of your active child theme (or theme). Tested and works.
Other related threads:
Enable delivery time options for a specific state in Woocommerce checkout
Update cart shipping data with AJAX in WooCommerce
On cart page (for chosen specific shipping method):
On checkout page page (for chosen specific shipping method):
On customer orders (email notifications and admin order pages too):
I am working on multi countries for tax field. Below is my code in woocommerce but it seems not working. However, if i put single country, it works.
Below is the code.
// Add field
function filter_woocommerce_billing_fields( $fields ) {
$fields['billing_vat'] = array(
'label' => 'Tax ID',
'required' => false,
'type' => 'text',
'class' => array( 'form-row-wide' ),
'priority' => 35,
);
return $fields;
}
add_filter( 'woocommerce_billing_fields', 'filter_woocommerce_billing_fields', 10, 1 );
Validate, i put multi countries here, am i doing wrong?
function action_woocommerce_after_checkout_validation( $data, $error ) {
if ( $data['billing_country'] == 'TW','CN','IE' && empty( $data['billing_vat'] ) ) {
$error->add( 'validation', 'Required based on country.' );
}
}
add_action('woocommerce_after_checkout_validation', 'action_woocommerce_after_checkout_validation', 10, 2 );
// jQuery
function action_woocommerce_after_order_notes( $checkout ) {
?>
<script>
(function($) {
$(document).ready(function () {
required_or_optional(); //this calls it on load
$( '#billing_country' ).change( required_or_optional );
// i put multi countries here with "or", am i doing wrong?
function required_or_optional() {
if ( $( '#billing_country' ).val() == 'TW' or 'CN' or 'IE' {
// Required
$( '#billing_vat' ).prop( 'required', true );
$( 'label[for="billing_vat"] .optional' ).remove();
$( 'label[for="billing_vat"]' ).append( '<abbr class="required" title="required">*</abbr>' );
} else {
$( '#billing_vat' ).removeProp( 'required' );
$( 'label[for="billing_vat"] .required' ).remove();
// Avoid append this multiple times
if ( $( 'label[for="billing_vat"] .optional' ).length == 0 ) {
$( 'label[for="billing_vat"]' ).append( '<span class="optional">(optional)</span>' );
}
}
}
});
})(jQuery);
</script>
<?php
}
add_action( 'woocommerce_after_order_notes', 'action_woocommerce_after_order_notes', 10, 1 );
// Display on the order edit page (backend)
function action_woocommerce_admin_order_data_after_shipping_address( $order ) {
if ( $value = $order->get_meta( '_billing_vat' ) ) {
echo '<p><strong>' . __( 'Billing VAT', 'woocommerce' ) . ':</strong> ' . $value . '</p>';
}
}
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'action_woocommerce_admin_order_data_after_shipping_address', 10, 1 );
Should i need to defined the countries in another array? Thx!
I think u overcomplicate it. Try with the following jquery script.
$(document ).on( 'updated_checkout', function() {
add_remove_checkout_fields_conditionaly();
});
function add_remove_checkout_fields_conditionaly() {
var country = $('#billing_country').val();
var compare = ["US", "GB"];
if($.inArray(country,compare)) {
$('#billing_vat_field').addClass('validate-required');
$('#billing_vat_field label span').hide();
$('#billing_vat_field label').append('<abbr class="required" title="">*</abbr>');
} else {
$('#billing_vat_field').removeClass('validate-required');
$('#billing_vat_field label span').show();
$('#billing_vat_field label abbr').hide();
}
}
I'm creating a plugin that will calculate custom shipping variants with API. I have a jQuery script that calculates postal and fias codes based on an entered address.
$("#billing_address_1").suggestions({
serviceUrl: "https://suggestions.dadata.ru/suggestions/api/4_1/rs",
token: php_vars.dadata_suggest_token,
type: "ADDRESS",
count: 5,
onSelect: function (suggestion) {
$("#billing_city").val(suggestion.data.city);
$("#billing_state").val(suggestion.data.region);
$("#billing_postcode").val(suggestion.data.postal_code);
if (suggestion.data.settlement_fias_id)
$("#billing_fias_code").val(suggestion.data.settlement_fias_id);
else if (suggestion.data.city_fias_id)
$("#billing_fias_code").val(suggestion.data.city_fias_id);
else
$("#billing_fias_code").val('');
}
});
To store the fias code, I created custom field.
add_filter( 'woocommerce_checkout_fields' , array( $this, 'custom_checkout_fields' ));
add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'shipping_apartment_update_order_meta') );
function custom_checkout_fields( $fields ) {
$fields['shipping']['shipping_fias_code'] = array(
'type' => 'text',
'label' => __('FIAS', 'woocommerce'),
'placeholder' => _x('Код', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
$fields['billing']['billing_fias_code'] = array(
'type' => 'text',
'label' => __('FIAS', 'woocommerce'),
'placeholder' => _x('Код', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
return $fields;
}
function shipping_apartment_update_order_meta( $order_id ) {
if ( ! empty( $_POST['shipping_fias_code'] ) ) {
update_post_meta( $order_id, 'shipping_fias_code', sanitize_text_field( $_POST['shipping_fias_code'] ) );
}
if ( ! empty( $_POST['billing_fias_code'] ) ) {
update_post_meta( $order_id, 'billing_fias_code', sanitize_text_field( $_POST['billing_fias_code'] ) );
}
}
The calculate_shipping() method in WC_Shipping_Method in woocommerce calculates shipping options using the $ package variable, which has an 'destination' array field with information about the shipping address. The postal code is passed into this array by default. But I also need to pass my custom field inside $package.
As I understand it, the field that I created will save the information added there via the jQuery script, only after the form is posted. But other fields included in $package['destination'] are saved immediately after adding information to them.
How do I add data from a custom field in the checkout form to the $package['destination'] variable?
I can't test (or modify) your jQuery code, so you will have to handle it yourself, maybe making some changes to it. I have revisited completely all your code (except your jQuery code) and everything works as you expect.
So $package['destination'] will have an additional entry for 'fias_code'.
The commented code:
// Add billing and shipping fields
add_filter( 'woocommerce_billing_fields' , 'custom_billing_fields' );
add_filter( 'woocommerce_shipping_fields' , 'custom_shipping_fields' );
function custom_shipping_fields( $fields ) {
$fields['shipping_fias_code'] = array(
'type' => 'text',
'label' => __('FIAS', 'woocommerce'),
'placeholder' => _x('Код', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
return $fields;
}
function custom_billing_fields( $fields ) {
$fields['billing_fias_code'] = array(
'type' => 'text',
'label' => __('FIAS', 'woocommerce'),
'placeholder' => _x('Код', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
return $fields;
}
// Ajax sender
add_action( 'wp_footer', 'checkout_send_fias_code_via_ajax_js' );
function checkout_send_fias_code_via_ajax_js() {
if ( is_checkout() && ! is_wc_endpoint_url() ) :
?><script type="text/javascript">
jQuery( function($){
if (typeof wc_checkout_params === 'undefined')
return false;
// Function that send the Ajax request
function sendAjaxRequest( value, fieldset = 'billing' ) {
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'fias_code',
'fias_code': value,
'fieldset' : fieldset
},
success: function (result) {
$(document.body).trigger('update_checkout'); // Update checkout processes
console.log( result ); // For testing (output data sent)
}
});
}
// Billing fias code change & input events
$(document.body).on( 'change input', 'input[name=billing_fias_code]', function() {
sendAjaxRequest( $(this).val() );
});
// Shipping fias code change & input events
$(document.body).on( 'change input', 'input[name=shipping_fias_code]', function() {
sendAjaxRequest( $(this).val(), 'shipping' );
});
});
</script>
<?php
endif;
}
// The Wordpress Ajax PHP receiver (set data to a WC_Session variable)
add_action( 'wp_ajax_fias_code', 'set_fias_code_to_wc_session' );
add_action( 'wp_ajax_nopriv_fias_code', 'set_fias_code_to_wc_session' );
function set_fias_code_to_wc_session() {
$field_key = 'fias_code';
if ( isset($_POST[$field_key]) && isset($_POST['fieldset']) ){
// Get data from custom session variable
$values = (array) WC()->session->get($field_key);
// Initializing when empty
if( ! empty($values) ) {
$values = array(
'billing' => WC()->customer->get_meta('billing_'.$field_key),
'shipping' => WC()->customer->get_meta('shipping_'.$field_key)
);
}
// Sanitizing data sent
$fieldset = esc_attr($_POST['fieldset']);
$fias_code = sanitize_text_field($_POST[$field_key]);
// Set / udpate custom WC_Session variable
$values[$fieldset] = $fias_code;
WC()->session->set($field_key, wc_clean($values));
// Send back to javascript the data received as an array (json encoded)
echo json_encode(array($fieldset.'_'.$field_key => $fias_code));
wp_die(); // always use die() or wp_die() at the end to avoird errors
}
}
// Update checkout fields 'fias_code' values from custom WC_session variable
add_filter('woocommerce_checkout_get_value', 'update_fias_code_checkout_fields_values', 10, 2 );
function update_fias_code_checkout_fields_values( $value, $input ) {
$field_key = 'fias_code';
// Get data from custom session variable
$values = (array) WC()->session->get($field_key);
if ( ! empty($values) ) {
if ( 'billing_'.$field_key === $input ) {
$value = $values['billing'];
}
if ( 'shipping_'.$field_key === $input ) {
$value = $values['shipping'];
}
}
return $value;
}
// Add 'fias_code' data to destination shipping packages
add_filter( 'woocommerce_cart_shipping_packages', 'add_fias_code_to_destination_shipping_package' );
function add_fias_code_to_destination_shipping_package( $packages ) {
$customer = WC()->customer; // The WC_Customer Object
// Get 'fias_code' data from customer meta data
$main_key = 'fias_code';
$meta_value = $customer->get_meta('shipping_'.$main_key);
$meta_value = empty($meta_value) ? $customer->get_meta('billing_'.$main_key) : $meta_value;
// Get data from custom session variable
$values = (array) WC()->session->get($main_key);
if ( ! empty($values) ) {
$session_value = $values['shipping'];
if ( $session_value === $meta_value ) {
$session_value = $values['billing'];
if ( $session_value !== $meta_value ) {
$meta_value = $values['billing'];
}
} else {
$meta_value = $session_value;
}
}
// Loop through shipping packages
foreach ( $packages as $key => $package ) {
// Set to destination package the "fias_code"
$packages[$key]['destination'][$main_key] = $meta_value;
}
return $packages;
}
// Remove custom WC_Session variable once order has been created (before thankyou)
add_action( 'woocommerce_checkout_order_created', 'remove_fias_code_custom_wc_session_variable' );
function remove_fias_code_custom_wc_session_variable() {
// Remove the custom WC_Session variable
WC()->session->__unset('fias_code');
}
This code goes in functions.php file of the active child theme (or active theme). Tested and works.
Important notes:
Displaying and save fias_code checkout fields:
I am using woocommerce_billing_fields and woocommerce_shipping_fields filter hooks instead of woocommerce_checkout_fields as that way the data is saved itself as order meta data and user meta data. So your last function is not required anymore.
The order meta keys start with an underscore like all billing and shipping order metadata.
The fields will be displayed on My account edit addresses… So if you want to avoid that you will need to add a condition to both related hooks.
Regarding my jQuery code:
You should better copy it to an external file and register/enqueue it in a clean WordPress way, restricting output to checkout page only. Then you will remove the related action hook and the hooked function…
Regarding action and filter hooks: You will have to change all add_action() and add_filter() for your plugin like:
add_filter( 'woocommerce_billing_fields' , array($this, 'custom_billing_fields') );
add_filter( 'woocommerce_shipping_fields' , array($this, 'custom_shipping_fields') );
add_action( 'wp_footer', array($this, 'checkout_send_fias_code_via_ajax_js') );
add_action( 'wp_ajax_fias_code', array($this, 'set_fias_code_to_wc_session') );
add_action( 'wp_ajax_nopriv_fias_code', array($this, 'set_fias_code_to_wc_session') );
add_filter('woocommerce_checkout_get_value', array($this, 'update_fias_code_checkout_fields_values'), 10, 2 );
add_filter( 'woocommerce_cart_shipping_packages', array($this, 'add_fias_code_to_destination_shipping_package') );
add_action( 'woocommerce_checkout_order_created', array($this, 'remove_fias_code_custom_wc_session_variable') );
I am creating a WooCommerce plugin and I want to display dynamically Sub Areas according to chosen customer city in checkout page.
Here is my code attempt:
add_filter( 'woocommerce_checkout_fields', 'dvs_city_list' );
function dvs_city_list( $fields ) {
$fields["billing"]["billing_city"]["type"] = 'select';
$fields["billing"]["billing_city"]["input_class"] = array(
'state_select' => 'state_select'
);
$fields["billing"]["billing_city"]["options"] = array(
'Lahore' => 'Lahore',
'Karachi' => 'Karachi'
),
return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'dvs_area_list' );
function dvs_area_list( $fields ) {
$fields['billing']['billing_area']['label'] = 'Area';
$fields['billing']['billing_area']['required'] = 'True';
$fields["billing"]["billing_area"]["type"] = 'select';
$fields["billing"]["billing_area"]["class"][0] = 'form-row-last';
$fields['billing']['billing_area']['priority'] = 50;
$fields["billing"]["billing_area"]["input_class"] = array(
'state_select' => 'state_select'
);
$city = $_REQUEST['billing_city'];
if ($city == 'Lahore') {
$fields["billing"]["billing_area"]["options"] = array(
'Naval Town' => 'Naval Town',
'Bahria Town' => 'Bahria Town',
'Faisal Town' => 'Faisal Town'
);
}
else ($city == 'Karachi') {
$fields["billing"]["billing_area"]["options"] = array(
'Walton Road' => 'Walton Road',
'Zest Road' => 'Zest Road'
);
}
return $fields;
}
Here is the screenshot
But I am getting this error
Notice:
Undefined index: billing_city in …wp-content/plugins/custom-plugin/index.php on line 35
How to fixed this error? What I am doing wrong?
To synch a custom checkout select field from another select field, it requires to use jQuery.
Also you can merge both functions as they use the same hook.
Below in the first function, we keep your cities / areas settings that we can call everywhere. The last function enable dynamic options changes on the "Billing areas" dropdown depending on the chosen city:
function cities_areas_settings() {
$text_domain = 'woocommerce';
return array(
__('Lahore', $text_domain) => array(
__('Naval Town', $text_domain),
__('Bahria Town', $text_domain),
__('Faisal Town', $text_domain),
),
__('Karachi', $text_domain) => array(
__('Walton Road', $text_domain),
__('Zest Road', $text_domain),
)
);
}
add_filter( 'woocommerce_checkout_fields', 'custom_checkout_fields' );
function custom_checkout_fields( $fields ) {
// Initializing
$text_domain = 'woocommerce';
$option_cities = array();
$lahore_areas = array( '' => __('Choose your area', $text_domain) );
// Load settings and prepare options arrays
foreach( cities_areas_settings() as $city => $areas ) {
$option_cities[$city] = $city;
if( $city === 'Lahore' ) {
foreach( $areas as $area ) {
$lahore_areas[$area] = $area;
}
}
}
// 1. Billing City field
$fields['billing']['billing_city']['type'] = 'select';
$fields['billing']['billing_city']['class'] = array('form-row-first');
$fields['billing']['billing_city']['input_class'] = array('state_select');
$fields['billing']['billing_city']['options'] = $option_cities;
// 2. Billing Area Field
$fields['billing']['billing_area'] = array(
'type' => 'select',
'label' => __('Area', $text_domain),
'class' => array('form-row-last'),
'input_class' => array('state_select'),
'options' => $lahore_areas,
'required' => true,
'default' => '',
'priority' => 50,
);
return $fields;
}
add_action('wp_footer', 'custom_checkout_js_script');
function custom_checkout_js_script() {
if( is_checkout() && ! is_wc_endpoint_url() ) :
// Initializing
$text_domain = 'woocommerce';
$karachi_areas = array( '' => __('Choose your area', $text_domain) );
$settings = cities_areas_settings(); // Load settings
// Prepare 'Karachi' options dropdown
foreach( cities_areas_settings()['Karachi'] as $area ) {
$karachi_areas[$area] = $area;
}
?>
<script language="javascript">
jQuery( function($){
var a = 'select[name="billing_city"]',
b = 'select[name="billing_area"]',
o = <?php echo json_encode($karachi_areas); ?>,
s = $(b).html();
// Utility function to fill dynamically the select field options
function dynamicSelectOptions( opt ){
var options = '';
$.each( opt, function( key, value ){
options += '<option value="'+key+'">'+value+'</option>';
});
$(b).html(options);
}
// On Start (once DOM is loaded)
if ( $(a).val() === 'Karachi' ) {
dynamicSelectOptions( o );
}
console.log($(a).val());
// On billing city change live event
$('form.woocommerce-checkout').on('change', a, function() {
console.log($(this).val());
if ( $(this).val() === 'Karachi' ) {
dynamicSelectOptions( o );
} else {
$(b).html(s);
}
});
});
</script>
<?php
endif;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related: Dynamic synched custom checkout select fields in WooCommerce
I'm trying to ad a custom fee to the order total upon checkout.
I've added a checkbox within woocommerce
add_action( 'woocommerce_after_checkout_billing_form', 'add_box_option_to_checkout' );
function add_box_option_to_checkout( $checkout ) {
echo '<div id="message_fields">';
woocommerce_form_field( 'add_gift_box', array(
'type' => 'checkbox',
'class' => array('add_gift_box form-row-wide'),
'label' => __('Ilość pudełek ozdobnych - 25 PLN/szt'),
'placeholder' => __(''),
), $checkout->get_value( 'add_gift_box' ));
}
Included a custom js file which schould handle the event
jQuery( document ).ready(function( $ ) {
$('#add_gift_box').click(function(){
var data = {
action: 'woocommerce_add_gift_box',
state: '200',
};
jQuery.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: data,
success: function (code) {
console.log(code);
jQuery('body').trigger('update_checkout');
},
dataType: 'html'
});
});
});
And a php fee handling function
function woo_add_cart_fee( $data ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) || ! $_POST ) return;
$extracost = 0;
if (isset($_POST['state'])) {
$extracost = intval($_POST['state']);
}
WC()->cart->add_fee( 'Ozdobne pudełka:', $extracost );
}
add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' );
add_action('wp_ajax_woocommerce_add_gift_box', 'woo_add_cart_fee', 10);
add_action('wp_ajax_nopriv_woocommerce_add_gift_box', 'woo_add_cart_fee', 10);
For some reasons the value of $_POST['state'] isn't added, the function works when I give a hard coded value, I've tried many option but cant get this to work.
I've seen similar posts but none of them had the answer.
The post data is sent by the AJAX functions in 'post_data', serialized. So to get the value of your checkbox, you only need to parse_str() this!
parse_str( $_POST['post_data'], $post_data );
then you can get your 'add_gift_box' option from $post_data['add_gift_box']. Note that upon order completion, this 'post_data' element is not available anymore and everything is in $_POST.
Complete example, based on your code:
1) adding the checkbox to the checkout
add_action( 'woocommerce_after_checkout_billing_form', 'add_box_option_to_checkout' );
function add_box_option_to_checkout( $checkout ) {
echo '<div id="message_fields">';
woocommerce_form_field( 'add_gift_box', array(
'type' => 'checkbox',
'class' => array('add_gift_box form-row-wide'),
'label' => __('Ilość pudełek ozdobnych - 25 PLN/szt'),
'placeholder' => __(''),
), $checkout->get_value( 'add_gift_box' ));
echo '</div>';
}
2) script to update cart when checkbox clicked (no need for extra AJAX requests!)
add_action( 'wp_footer', 'woocommerce_add_gift_box' );
function woocommerce_add_gift_box() {
if (is_checkout()) {
?>
<script type="text/javascript">
jQuery( document ).ready(function( $ ) {
$('#add_gift_box').click(function(){
jQuery('body').trigger('update_checkout');
});
});
</script>
<?php
}
}
3) action to add the fee
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['add_gift_box'])) {
$extracost = 25; // not sure why you used intval($_POST['state']) ?
WC()->cart->add_fee( 'Ozdobne pudełka:', $extracost );
}
}
This is awesome!! Thanks a lot. I've changed it a little bit to add a percentage instead. I know this is not a better answer but I have no reputation to push your answer up. For whoever was stuck like me..
add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' ); function woo_add_cart_fee( $cart ){
global $woocommerce;
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['add_gift_box'])) {
$percentage = 0.01;
$surcharge = ( $woocommerce->cart->cart_contents_total + $woocommerce->cart->shipping_total ) * $percentage;
$woocommerce->cart->add_fee( 'Surcharge', $surcharge, true, '' );
}
}