I added custom email field on WooCommerce Checkout by following code
woocommerce_form_field('giftcard-friend-email', array(
'type' => 'email',
'class' => array( 'form-row-wide' ),
'required' => true,
'label' => __('To: Friend Email') ,
'placeholder' => __('Friend Email') ,
),
$checkout->get_value('giftcard-friend-email'));
It's working, required validation also working but it doesn't give error when input is an invalid email address.
I wonder if there's any built-in WooCommerce method to achieve this same as Billing Email?
Thanks!
You should always provide in your question the full function code and all related code too.
I have changed the giftcard-friend-email to _giftcard_friend_email a more normal slug as it will be saved as order meta data, so underscores are better option.
The following code will:
Display a custom email field in checkout page (so remove your code).
Will validate this email field checking that is not empty and a valid email.
Save this custom email value as order meta data when order is placed
The code:
// Display the custom checkout field
add_action('woocommerce_before_order_notes', 'add_custom_checkout_field', 20, 1 );
function add_custom_checkout_field( $checkout ) {
echo '<div id="friend_email_checkout_field">';
woocommerce_form_field( '_giftcard_friend_email', array(
'type' => 'email',
'label' => __('To: Friend Email') ,
'placeholder' => __('Friend Email') ,
'class' => array( 'form-row-wide' ),
'required' => true,
), $checkout->get_value('_friend_email') );
echo '</div>';
}
// Field custom email Validation
add_action( 'woocommerce_checkout_process', 'friend_email_checkout_field_validation' );
function friend_email_checkout_field_validation() {
if( isset($_POST['_giftcard_friend_email']) && empty($_POST['_giftcard_friend_email']) )
wc_add_notice( __( 'Please fill in the "Friend Email" field.', 'woocommerce' ), 'error' );
elseif( !preg_match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$^", $_POST['_giftcard_friend_email'] ) ){
wc_add_notice( __( 'Please enter a valid "Friend Email".', 'woocommerce' ), 'error' );
}
}
// Save the checkout field value to order meta data
add_action('woocommerce_checkout_create_order', 'save_friend_email_checkout_field_value', 20, 2 );
function save_friend_email_checkout_field_value( $order, $data ) {
if ( isset( $_POST['_giftcard_friend_email'] ) ) {
$order->update_meta_data( '_giftcard_friend_email', sanitize_email( $_POST['_giftcard_friend_email'] ) );
}
}
Code goes in function.php file of the active child theme (or active theme). Tested and works.
Related
I am seeing a strange issue on my test website. The website can be viewed here and it's a test site so there's no payment (an no payment details entry is required).
https://puffpastrydelights.com/order-online/
So what I am trying to do is ensure the user has provided a delivery date or time if they choose delivery, or ensure they have provided a pickup date and time if they choose pickup.
So for replication, if you order a food item, in the shopping cart select pickup and then in the checkout page fill all the details, your checkout will process and everything is fine.
Now try the same again but this time select delivery in the cart page before you head to checkout, you will see it will show a validation error stating to provide delivery date and time even though you have. It's this I am unsure on and can't see in my code what's causing this issue:
// Hide Local Pickup shipping method
add_filter( 'woocommerce_checkout_fields', 'hide_local_pickup_method');
function hide_local_pickup_method( $fields_pickup) {
// change below for the method
$shipping_method_pickup ='local_pickup:2';
// change below for the list of fields. Add (or delete) the field name you want (or don’t want) to use
$hide_fields_pickup = array( 'billing_company', 'billing_state', 'billing_company');
$shipping_fields_pickup = array( 'shipping_first_name', 'shipping_last_name', 'shipping_company', 'shipping_address_1', 'shipping_address_2', 'shipping_city', 'shipping_postcode');
$hide_pickup_date_time = array( 'pickup_date', 'pickup_time');
$hide_delivery_date_time = array( 'delivery_date', 'delivery_time');
$chosen_methods_pickup = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_pickup = $chosen_methods_pickup[0];
foreach($hide_fields_pickup as $field_pickup ) {
if ($chosen_shipping_pickup == $shipping_method_pickup) {
$fields_pickup['billing'][$field_pickup]['required'] = false;
$fields_pickup['billing'][$field_pickup]['class'][] = 'hide_pickup';
}
$fields_pickup['billing'][$field_pickup]['class'][] = 'billing-dynamic_pickup';
}
foreach($shipping_fields_pickup as $shipping_field ) {
if ($chosen_shipping_pickup == $shipping_method_pickup) {
$fields_pickup['shipping'][$shipping_field]['required'] = false;
}
}
foreach($hide_pickup_date_time as $pickup_date_time ) {
if ($chosen_shipping_pickup != $shipping_method_pickup) {
$fields_pickup['order'][$pickup_date_time]['required'] = false;
}
}
foreach($hide_delivery_date_time as $delivery_date_time ) {
if ($chosen_shipping_pickup != $shipping_method_pickup) {
$fields_pickup['order'][$delivery_date_time]['required'] = false;
}
}
return $fields_pickup;
}
/**
* Process the checkout
*/
add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process');
function my_custom_checkout_field_process() {
$shipping_method_pickup ='local_pickup:2';
$chosen_methods_pickup = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_pickup = $chosen_methods_pickup[0];
// Check if set, if its not set add an error.
if ($chosen_shipping_pickup == $shipping_method_pickup) {
if ( ! $_POST['pickup_date'] ){
wc_add_notice( __( 'Please provide a Pickup Date.' ), 'error' );
}
if ( ! $_POST['pickup_time'] ){
wc_add_notice( __( 'Please provide a Pickup Time.' ), 'error' );
}
} else {
if ( ! $_POST['delivery_date'] ){
wc_add_notice( __( 'Please provide a Delivery Date.' ), 'error' );
}
if ( ! $_POST['delivery_time'] ){
wc_add_notice( __( 'Please provide a Delivery Time.' ), 'error' );
}
}
}
These fields are custom fields and are set below like so:
add_action('woocommerce_before_order_notes', 'custom_checkout_field');
function custom_checkout_field($checkout)
{
echo '<div id="custom_checkout_field"><h3>' . __('Pickup/Delivery') . '</h3>';
woocommerce_form_field(
'delivery_date',
array(
'type' => 'date',
'required' => 'true',
'class' => array(
'delivery-date-class form-row-wide'
),
'label' => __('Delivery Date'),
),
$checkout->get_value('delivery_date')
);
woocommerce_form_field(
'delivery_time',
array(
'type' => 'time',
'required' => 'true',
'class' => array(
'delivery-time-class form-row-wide'
),
'label' => __('Delivery Time'),
),
$checkout->get_value('delivery_time')
);
woocommerce_form_field(
'pickup_date',
array(
'type' => 'date',
'required' => 'true',
'class' => array(
'pickup-date-class form-row-wide'
),
'label' => __('Pickup Date'),
),
$checkout->get_value('pickup_date')
);
woocommerce_form_field(
'pickup_time',
array(
'type' => 'time',
'required' => 'true',
'class' => array(
'pickup-time-class form-row-wide'
),
'label' => __('Pickup Time'),
),
$checkout->get_value('pickup_time')
);
echo '</div>';
}
Your code is correct, no errors.
The problem is that you have two HTML elements with the same id (and name) on the checkout page.
The fields are:
delivery_date there is a date input field and a text input field with this id (and name)
delivery_time there is a time input field and a text input field with this id (and name)
pickup_date there is a date input field and a text input field with this id (and name)
pickup_time there is a time input field and a text input field with this id (and name)
As you can see from this screenshot (the text input fields are hidden):
Try removing the text input fields or, if needed, change the id attribute value to be unique on the page.
In the code you published there is no reference to the text input fields, most likely they are in your functions.php.
In the Checkout form I created a select field. My question is how in Wordpress or Woocmmerce this camp can be left as required.
<p class="form-row form-row-wide validate-required validate-region" id="shipping_region_field" data-priority="6">
<select name="shipping_region" id="shipping_region" class="state_select select2-selection--single" autocomplete="address-level1" data-placeholder="" tabindex="-1" aria-hidden="true">
<option>Opción 01</option>
<option>Opción 02</option>
</select>
</p>
1) For normal or custom billing and shipping fields you can use woocommerce_billing_fields or woocommerce_shipping_fields action hooks on checkout page as follow.
It will make a custom checkout field required without any need to add a validation script and to save it in the order. The field will also appear in My account edit adresses field section.
Some argument explanations:
The class 'update_totals_on_change' allow to trigger "update checkout" on change.
The 'required' attribute make the field required or not
The 'priority' attribute allow you to change the location of the field.
The code:
add_filter( 'woocommerce_shipping_fields', 'display_shipping_region_checkout_field', 20, 1 );
function display_shipping_region_checkout_field( $fields ) {
$fields['shipping_region'] = array(
'type' => 'select',
'label' => __("Region", "woocommerce") ,
'class' => array('form-row-wide', 'update_totals_on_change'),
'required' => true,
'options' => array(
'' => __("Choose a region please"),
'option-1' => __("Option 01"),
'option-2' => __("Option 02"),
'option-3' => __("Option 03"),
),
'priority' => 100,
'clear' => true,
);
return $fields;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
2) In specific cases you need to use the woocommerce_default_address_fields filter. This filter is applied to all billing and shipping default fields *(see the documentation). It's used only some default checkout fields.
3) You can also use woocommerce_checkout_fieldsthat has $fields as function argument (see documentation).
4) For other custom checkout fields you can use one of the following hooks with the woocommerce_form_field() function:
woocommerce_before_checkout_billing_form has $checkout as function argument
woocommerce_before_checkout_billing_form has $checkout as function argument
woocommerce_before_checkout_shipping_form has $checkout as function argument
woocommerce_before_checkout_shipping_form has $checkout as function argument
woocommerce_before_order_notes has $checkout as function argument
woocommerce_after_order_notes has $checkout as function argument
The code that display the field, validate the field and save the field in the order:
// Display field
add_action( 'woocommerce_after_checkout_shipping_form', 'display_shipping_region_after_checkout_shipping_form', 10, 1 );
function display_shipping_region_after_checkout_shipping_form ( $checkout ) {
woocommerce_form_field( 'shipping_region', array(
'type' => 'select',
'label' => __("Region", "woocommerce") ,
'class' => array('form-row-wide','update_totals_on_change'),
'required' => true,
'options' => array(
'' => __("Choose a region please"),
'option-1' => __("Option 01"),
'option-2' => __("Option 02"),
'option-3' => __("Option 03"),
),
'priority' => 100,
'clear' => true,
), $checkout->get_value( 'shipping_region' ) );
}
// Field Validation
add_action('woocommerce_checkout_process', 'shipping_region_custom_checkout_field_validation');
function shipping_region_custom_checkout_field_validation() {
if ( isset($_POST['shipping_region']) && empty($_POST['shipping_region']) )
wc_add_notice( __( 'Please select something into Region field.' ), 'error' );
}
// Save Field value
add_action( 'woocommerce_checkout_create_order', 'action_checkout_create_order_callback', 10, 2 );
function action_checkout_create_order_callback( $order, $data ) {
if ( isset($_POST['shipping_region']) && empty($_POST['shipping_region']) ) {
$order->update_meta_data( '_shipping_region', esc_attr($_POST['shipping_region']) );
if( $order->get_user_id() > 0 )
update_user_met( $order->get_user_id(), 'shipping_region', esc_attr($_POST['shipping_region']) );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
WooCommerce Documentation: Customizing checkout fields using actions and filters
you can add an attribute to the select tag
<select required name="shipping_region" id="shipping_region" class="state_select select2-selection--single" autocomplete="address-level1" data-placeholder="" tabindex="-1" aria-hidden="true">
<option>Opción 01</option>
<option>Opción 02</option>
</select>
**If You Have Created Your Custom Field at the checkout page**
add_action( 'woocommerce_after_checkout_validation', 'shipping_time_optionss', 9999, 2);
function shipping_time_optionss( $fields, $errors ){
// if any validation errors
if ( empty( $_POST['woo_shipping_time'] ) ) {
$errors->add( 'woocommerce_password_error', __( 'Please Select Shipping Time Option.' ) );
}
}
**
If You Have Created Your Custom Field at the checkout page
**
add_action( 'woocommerce_after_checkout_validation', 'shipping_time_optionss', 9999, 2);
function shipping_time_optionss( $fields, $errors ){
// if any validation errors
if ( empty( $_POST['woo_shipping_time'] ) ) {
$errors->add( 'woocommerce_password_error', __( 'Please Select Shipping Time Option.' ) );
}
}
I have added two custom input fields in the shipping method section by changing the template /genesis-sample/woocommerce/checkout/review-order.php
I have also managed to get them conditionally required. Only when the specific radio button is checked, the input fields appear and become required. I am using jQuery code to make the fields appear and disappear. All of this is working fine. The code is here:
if ( isset($_POST['shipping_method_0_legacy_local_pickup']) && $_POST['shipping_method_0_legacy_local_pickup'] == 1 ) {
$cheq = 'true';
}
else {
$cheq = 'false';
}
woocommerce_form_field( 'fieldtester1' , array(
'type' => 'text',
'class' => array('wccs-field-class wccs-form-row-wide blahblah1'),
'label' => 'Enter Your Carrier Name',
'required' => $cheq,
'placeholder' => 'Carrier Name',
), $checkout->get_value( 'fieldtester1' ));
woocommerce_form_field( 'fieldtester2' , array(
'type' => 'text',
'class' => array('wccs-field-class wccs-form-row-wide blahblah2'),
'label' => 'Enter Your Carrier Account #',
'required' => $cheq,
'placeholder' => 'Carrier Number',
), $checkout->get_value( 'fieldtester2' ));
But here's the problem, even with the required attribute set as true for the two fields, the validation doesn't happen if the field is empty when the Place Order button is pressed. Ideally, the order should not go through and an error message should be generated. I have already added following code in functions.php to force validation of the input field but it doesn't do a thing. The code is here:
add_action('woocommerce_checkout_process', 'carrier_checkout_process');
function carrier_checkout_process() {
if ( isset($_POST['shipping_method_0_legacy_local_pickup']) && $_POST['shipping_method_0_legacy_local_pickup'] == 1 ) {
if( empty( $_POST['fieldtester1'] ) ) {
wc_add_notice( ( "Please don't forget to enter your shipping carrier details." ), "error" );
}
}
}
So, here's what I am looking for:
can anyone help me in forcing the validation for two input fields I
have added?
After the validation, I would also like to add these two fields in the new order email, and order details in the Wordpress dashboard.
If you are wondering why I am not using a woocommerce checkout field hook to add the input fields in the first place...I am not adding the fields to the checkout form so the hooks aren't of any help. The fields are added in the shipping method section. Another reason was, I wanted to update the required attribute every time a user would switch the shipping method. Using Jquery to change the required attribute wasn't working for whatever reason, believe me I tried for two days. Anyways, that part is already working. The only issues are getting the fields to validate and add them to the order emails and order details. I have looked around everywhere and the closest help I got was this post where LoicTheAztec gave a detailed solution
This can be done without jQuery using a special hook that will display your two "Carrier" custom fields below legacy_local_pickup shipping method when it's selected. If customer change to a different shipping method, those fields will be removed.
So you should need to remove your customized template and all related code before.
Now the validation works perfectly and when "Carrier" custom fields are filled, they are saved in order meta data.
// Add custom fields to a specific selected 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 checkout page
$customer_carrier_method = 'legacy_local_pickup';
if( $method->id != $customer_carrier_method ) return; // Only display for "local_pickup"
$chosen_method_id = WC()->session->chosen_shipping_methods[ $index ];
// If the chosen shipping method is 'legacy_local_pickup' we display
if($chosen_method_id == $customer_carrier_method ):
echo '<div class="custom-carrier">';
woocommerce_form_field( 'carrier_name' , array(
'type' => 'text',
'class' => array('form-row-wide carrier-name'),
'label' => 'Carrier Information:',
'required' => true,
'placeholder' => 'Carrier Name',
), WC()->checkout->get_value( 'carrier_name' ));
woocommerce_form_field( 'carrier_number' , array(
'type' => 'text',
'class' => array('form-row-wide carrier-number'),
'required' => true,
'placeholder' => 'Carrier Number',
), WC()->checkout->get_value( 'carrier_number' ));
echo '</div>';
endif;
}
// Check custom fields validation
add_action('woocommerce_checkout_process', 'carrier_checkout_process');
function carrier_checkout_process() {
if( isset( $_POST['carrier_name'] ) && empty( $_POST['carrier_name'] ) )
wc_add_notice( ( "Please don't forget to enter the shipping carrier name." ), "error" );
if( isset( $_POST['carrier_number'] ) && empty( $_POST['carrier_number'] ) )
wc_add_notice( ( "Please don't forget to enter the shipping carrier account number." ), "error" );
}
// Save custom fields to order meta data
add_action( 'woocommerce_checkout_update_order_meta', 'carrier_update_order_meta', 30, 1 );
function carrier_update_order_meta( $order_id ) {
if( isset( $_POST['carrier_name'] ))
update_post_meta( $order_id, '_carrier_name', sanitize_text_field( $_POST['carrier_name'] ) );
if( isset( $_POST['carrier_number'] ))
update_post_meta( $order_id, '_carrier_number', sanitize_text_field( $_POST['carrier_number'] ) );
}
This code goes on functions.php file of your active child theme (or theme). Tested and works.
Not displayed:
Displayed when "Local Pickup" is selected:
I have a checkbox which if checked opens a drop down field (requirements). If you try and send the Woocommerce order it conditionally checks if the field has content, if it doesn't it returns an error.
This all works accept when the requirements field does have information input it still treats it as not having content and returns the error.
As this is the basic way that forms should work I don't understand why I'm getting such a result. Here's the code:
/**
* Add a message field to the WC checkout
*/
add_action( 'woocommerce_before_checkout_form', 'custom_checkout_field' );
function custom_checkout_field( $checkout ) {
echo '<div id="message"><h3>' . __( '<i class="fas fa-envelope"></i> Message' ) . '</h3><p style="margin: 0 0 8px;">Would you like to leave a message?</p>';
woocommerce_form_field( 'checkbox', array(
'type' => 'checkbox',
'class' => array( 'msg-checkbox' ),
'label' => __( 'Yes' ),
), $checkout->get_value( 'checkbox' ) );
woocommerce_form_field( 'requirements', array(
'type' => 'text',
'class' => array('msg'),
'label' => __('Please input your message.'),
'placeholder' => __(''),
), $checkout->get_value( 'requirements' ));
echo '</div>';
}
/**
* Process checkout with additional custom field
*/
add_action('woocommerce_checkout_process', 'custom_checkout_field_process');
function custom_checkout_field_process() {
// Check if set, if not add an error.
if(isset($_POST['checkbox']) && ( empty($_POST['requirements'])));
wc_add_notice( __( 'Please let us know what you would like to do' ), 'error' );
}
I hope that makes sense. basically it works to a point but the validation for the input field doesn't work when it has content.
Thanks.
Update: Code improvements and added a show/hide functionality with jQuery
The only problem comes from the hook that you are using for your custom checkout fields. It output those fields outside the checkout form, so the values are never submitted and then always empty.
Instead you will use woocommerce_checkout_before_customer_details action hook for your first hooked function that will output those fields inside the checkout form this time:
// Output custom checkout fields
add_action( 'woocommerce_checkout_before_customer_details', 'custom_checkout_field_before_billing' );
function custom_checkout_field_before_billing() {
$domain = 'woocommerce';
// Hide the message text field on load
?>
<style>p#requirements_field{display:none;}</style>
<div id="message">
<h3><i class="fa fa-envelope"></i><?php _e( 'Message', 'woocommerce' ); ?></h3>
<?php
woocommerce_form_field( 'checkbox_msg', array(
'type' => 'checkbox',
'class' => array( 'msg-checkbox' ),
'label' => __( 'Would you like to leave a message?', 'woocommerce' ),
), WC()->checkout->get_value( 'cb_msg' ));
woocommerce_form_field( 'requirements', array(
'type' => 'text',
'class' => array('msg t_msg'),
'label' => __('Please input your message.'),
'placeholder' => __(''),
), WC()->checkout->get_value( 'requirements' ));
echo '</div>';
// jQuery: show hide the text requirement field
?><script>
jQuery(document).ready(function($) {
var a = '#requirements_field';
$('input#checkbox_msg').change( function(){
if( $(this).is(':checked') )
$(a).show();
else
$(a).hide();
});
});
</script><?php
}
// Process checkout with additional custom field
add_action('woocommerce_after_checkout_validation', 'custom_checkout_field_validation_process', 20, 2 );
function custom_checkout_field_validation_process( $data, $errors ) {
// Check if set, if not add an error.
if( isset($_POST['checkbox_msg']) && empty($_POST['requirements']) )
$errors->add( 'requirements', __( "Please let us know what you would like to do filling up the message field.", "woocommerce" ) );
}
Code goes in function.php file of your active child theme (or active theme).
Tested and works.
I'm trying to re-order 2 custom checkout fields added with the help of woocommerce_checkout_init filter, just that when I apply woocommerce_checkout_fields filter to re-order fields, it doesn't recognize them and they are null.
I think it's because the filter woocommerce_checkout_init goes after woocommerce_checkout_fields.
How Can I solve this?
Here is my code:
add_action( 'woocommerce_checkout_init', 'wc_add_confirm_email_checkout', 10, 2 );
function wc_add_confirm_email_checkout( $checkout ) {
$checkout->checkout_fields['billing']['billing_email2'] = array(
'type' => 'text',
'label' => __( 'Confirm Email Address', 'woocommerce' ),
'required' => true,
'placeholder' => _x( 'Confirm Email Address', 'placeholder', 'woocommerce' )
);
}
add_action( 'woocommerce_checkout_init', 'wc_add_confirm_password_checkout', 10, 2 );
function wc_add_confirm_password_checkout( $checkout ) {
//var_dump($checkout->checkout_fields);
if ( get_option( 'woocommerce_registration_generate_password' ) == 'no' ) {
$checkout->checkout_fields['account']['account_password2'] = array(
'type' => 'password',
'label' => __( 'Confirm password', 'woocommerce' ),
'required' => true,
'placeholder' => _x( 'Confirm Password', 'placeholder', 'woocommerce' )
);
}
}
add_filter('woocommerce_checkout_fields','reorder_woo_fields');
function reorder_woo_fields($fields) {
$fields2['billing']['billing_first_name'] = $fields['billing']['billing_first_name'];
$fields2['billing']['billing_last_name'] = $fields['billing']['billing_last_name'];
$fields2['billing']['billingooglg_email'] = $fields['billing']['billing_email'];
$fields2['billing']['billing_email2'] = $fields['billing']['billing_email2'];
$fields2['billing']['account_password'] = $fields['account']['account_password'];
$fields2['billing']['account_password2'] = $fields['account']['account_password2'];
$fields2['billing']['billing_address_1'] = $fields['billing']['billing_address_1'];
$fields2['billing']['billing_postcode'] = $fields['billing']['billing_postcode'];
var_dump($fields2);
//return $fields2;
}
For WooCommerce 3+ (update):
Since WooCommerce 3.0 checkout fields have changed a little bit so is not possible to reorder fields as before.
There is a new 'priority' argument that handle fields order, for checkout fields and my account fields as well.
Here is fully functional example for WooCommerce 3+:
// REORDERING CHECKOUT BILLING FIELDS (WOOCOMMERCE 3+)
add_filter( "woocommerce_checkout_fields", "reordering_checkout_fields", 15, 1 );
function reordering_checkout_fields( $fields ) {
## ---- 1. REORDERING BILLING FIELDS ---- ##
// Set the order of the fields
$billing_order = array(
'billing_first_name',
'billing_last_name',
'billing_email',
'billing_phone',
'billing_company',
'billing_address_1',
'billing_address_2',
'billing_postcode',
'billing_city',
'billing_state',
'billing_country'
);
$count = 0;
$priority = 10;
// Updating the 'priority' argument
foreach($billing_order as $field_name){
$count++;
$fields['billing'][$field_name]['priority'] = $count * $priority;
}
## ---- 2. CHANGING SOME CLASSES FOR BILLING FIELDS ---- ##
$fields['billing']['billing_email']['class'] = array('form-row-first');
$fields['billing']['billing_phone']['class'] = array('form-row-last');
$fields['billing']['billing_postcode']['class'] = array('form-row-first');
$fields['billing']['billing_city']['class'] = array('form-row-last');
## ---- RETURN THE BILLING FIELDS CUSTOMIZED ---- ##
return $fields;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Before WooCommerce 3
I am not completely sure, but you there are some things that you can't do like merging billing fields with account fields. If you want to do that is going to be much more complicated than what you are trying to do here. In that case you will need to rewrite/create some checkout templates…
Another thing is that billing_email and billing_phone are sharing the same line together with 'class' => 'form-row-first' and 'class' => 'form-row-last'. When not this class is define 'class' => 'form-row-wide'… So you are going to need overriding these 'class' too.
After that you dont need to use 'woocommerce_checkout_init' hook…
You can still use 'woocommerce_checkout_fields'.
Also you can merge all of them in one function this way:
/*
* Creating, overriding and reordering custom fields.
*/
add_filter( "woocommerce_checkout_fields", "custom_override_checkout_fields", 11, 1 );
function custom_override_checkout_fields( $fields ) {
// Creating 'billing_email2' field
$fields['billing']['billing_email2'] = array(
'type' => 'text',
'label' => __( 'Confirm Email Address', 'woocommerce' ),
'placeholder' => _x( 'Confirm Email Address', 'placeholder', 'woocommerce' ),
'required' => true,
'class' => array('form-row-last'),
'clear' => true
);
// =======> I don't really know if you need this one <========
// it already exist (see in first reference link at bottom).
// Creating 'account_password2' field
if ( get_option( 'woocommerce_registration_generate_password' ) == 'no' ) {
$fields['account']['account_password2'] = array(
'type' => 'password',
'label' => __( 'Confirm password', 'woocommerce' ),
'placeholder' => _x( 'Confirm Password', 'placeholder', 'woocommerce' ),
'required' => true,
'class' => array('form-row-wide') //,
// 'clear' => true
);
}
// Overriding existing billing_phone field 'class' property
$fields['billing']['billing_phone']['class'] = array('form-row-wide');
// Reordering billing fields
$order = array(
"billing_first_name",
"billing_last_name",
"billing_email",
"billing_email2",
"billing_phone",
"billing_company",
"billing_address_1",
"billing_address_2",
"billing_postcode",
"billing_country"
);
foreach($order as $field)
{
$ordered_fields[$field] = $fields["billing"][$field];
}
$fields["billing"] = $ordered_fields;
return $fields;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
As I have said before, I think that you can't merge billing fields with account fields.
As 'account_password2' already exist if you refer to official documentation (see below in first reference link), you may not need to create it. You will have to test this and to fine tune it. But this is the way to do it.
References:
Official: Customizing checkout fields using actions and filters
How to reorder billing fields in WooCommerce Checkout template?
Notice!
Woocommerce has made some changes and for arranging address fields you should use 'woocommerce_default_address_fields' filter.
add_filter( 'woocommerce_default_address_fields', 'custom_override_default_locale_fields' );
function custom_override_default_locale_fields( $fields ) {
$fields['state']['priority'] = 5;
$fields['address_1']['priority'] = 6;
$fields['address_2']['priority'] = 7;
return $fields;
}
Reorder Woocommerce checkout fields
To Add to the accepted answer...
If you need to move form fields from one field group to another, for example moving a shipping field into billing or an account field into billing, you can do the following.
function move_password_field($checkout_fields){
// Move Account Password into Billing
$checkout_fields['billing']['account_password'] = $checkout_fields['account']['account_password'];
// Remove Password from Billing
unset($checkout_fields['account']['account_password']);
return $checkout_fields;
}
add_filter('woocommerce_checkout_fields', 'move_password_field', 999);