I want to add some custom billing states and then set a default state in the Admin panel. So far I have added the states as follows (code below; also not sure this is right):
add_filter( 'woocommerce_states', 'custom_woocommerce_states' );
function custom_woocommerce_states( $states ) {
$states['PE'] = array(
'PE1' => __('StateA', 'woocommerce')
'PE2' => __('StateB', 'woocommerce')
);
return $states;
}
How do I set the default value to be StateA in the Admin Panel?
First, there is a mistake in your current code.
In Admin WooCommerce order pages when adding (or editing) billing (or shipping) address(es), to set custom 'PE1' as default state when selected country is Peru (PE), use the following instead:
add_filter( 'woocommerce_states', 'custom_woocommerce_states_for_peru' );
function custom_woocommerce_states_for_peru( $states ) {
// For PERU
$states['PE'] = array(
'PE1' => __('StateA', 'woocommerce'),
'PE2' => __('StateB', 'woocommerce')
);
return $states;
}
// Admin orders: Set a default state for PERU country
add_action( 'admin_footer', 'custom_admin_shop_order_js' );
function custom_admin_shop_order_js() {
global $pagenow, $post_type;
if ( in_array( $pagenow, array('post-new.php', 'post.php') ) && 'shop_order' === $post_type ) :
?><script type='text/javascript'>
jQuery( function($) {
// Billing state
$(document.body).on( 'change', 'select#_billing_country,select#_shipping_country', function(){
var country = 'PE', // Set country
defaultState = 'PE1', // Set default state (for country)
parent = $(this).parent().parent(),
billingState = parent.find('select#_billing_state'),
shippingState = parent.find('select#_shipping_state');
if( country === $(this).val() ) {
if ( '' === billingState.val() ) {
billingState.val(defaultState).trigger("change");
} else if ( '' === shippingState.val() ) {
shippingState.val(defaultState).trigger("change");
}
}
});
});
</script><?php
endif;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
You can add some new custom states for a country using this code:
add_filter( 'woocommerce_states', 'custom_woocommerce_states' );
function custom_woocommerce_states( $states ) {
$states['XX'] = array( // XX is Country Code
'XX1' => 'State 1',
'XX2' => 'State 2'
);
return $states;
}
Then you can change the country and state default value using this code:
add_filter( 'default_checkout_billing_country', 'change_default_checkout_country' );
add_filter( 'default_checkout_billing_state', 'change_default_checkout_state' );
function change_default_checkout_country() {
return 'XX'; // country code
}
function change_default_checkout_state() {
return 'XX'; // state code
}
If you are trying to make it dynamic and choose your default state from your admin panel, you can add a section and an option to your Woocommerce settings and get it using get_option('custom_id'). You can get help for creating a custom setting for Woocommerce here.
Related
I currently use below code to set default country (based on User's current Geo-location).
function custom_update_allowed_countries( $countries ) {
// Only on frontend
if( is_admin() ) return $countries;
if( class_exists( 'WC_Geolocation' ) ) {
$location = WC_Geolocation::geolocate_ip();
if ( isset( $location['country'] ) ) {
$countryCode = $location['country'];
} else {
// If there is no country, then return allowed countries
return $countries;
}
} else {
// If you can't geolocate user country by IP, then return allowed countries
return $countries;
}
// If everything went ok then I filter user country in the allowed countries array
$user_country_code_array = array( $countryCode );
$intersect_countries = array_intersect_key( $countries, array_flip( $user_country_code_array ) );
return $intersect_countries;
}
add_filter( 'woocommerce_countries_allowed_countries', 'custom_update_allowed_countries', 30, 1 );
The above code affects not only the Billing Section but also the Shipping Section. In this case, a US user can only ship to US; a user from Canada can only ship to Canada and so on.
If there's a way that it will still default to the Goelocated country on the Billing, but User can be able to ship to Other countries?
I've checked every hook/filter and found nothing useful. So I came up with this solution. Add this code to your child theme functions.php file:
add_action( 'wp_enqueue_scripts', 'wp_enqueue_scripts_action' );
function wp_enqueue_scripts_action() {
wp_register_script( 'test-script', get_stylesheet_directory_uri() . '/test.js', [ 'jquery', ] );
wp_enqueue_script( 'test-script' );
wp_localize_script( 'test-script', 'test_script', [
'customer_country' => get_customer_geo_location_country()
]
);
}
/**
* Returns the customer country
*
* #return string|null
*/
function get_customer_geo_location_country(): ?string {
if ( class_exists( 'WC_Geolocation' ) ) {
$location = WC_Geolocation::geolocate_ip();
if ( isset( $location['country'] ) ) {
return $location['country'];
}
}
return null;
}
add_action( 'woocommerce_after_checkout_validation', 'woocommerce_after_checkout_validation_action', 10, 2 );
function woocommerce_after_checkout_validation_action( $fields, $errors ) {
$billing_country = $fields['billing_country'];
if ( ! empty( $billing_country ) && $billing_country !== get_customer_geo_location_country() ) {
$errors->add( 'validation', 'You are not allowed to select this billing country!' );
}
}
First we add a new script. If you already have a script, you can just copy the wp_localize_script function and change the handler and the object name.
With this function we can pass the current country of the customer to our JavaScript file. Inside this file we do this stuff:
(function ( $ ) {
$( document ).ready( function () {
if (test_script.customer_country) {
$( '#billing_country option' ).each( function () {
if ($( this ).val() !== test_script.customer_country) {
$( this ).remove();
}
} );
}
} );
})( jQuery );
This little function removes every country from our billing select that doesn't match the customers country. If you want you can remove all countries in an else statement to be sure the customer can't order in case there is no country available.
The customer should now only see his home country in the billing country dropdown.
To be sure he don't hacks us, we add a little validation to the checkout where we verify the selected country again.
If you test this on localhost, no country will be available so be sure it's on a live website in the web (or even staging).
This is a basic idea. You need to test it and maybe adjust it too your needs.
In woocommerce already the shipping country in restricted to IN
On checkout the default value of Country, state & city is set
I want to do that same in my-account/edit-address/ only in shipping address
tried using this code but its not working
add_filter( 'woocommerce_shipping_fields' , 'default_shipping_address' );
function default_shipping_address( $fields ) {
$fields['shipping_city']['default'] = 'Mumbai';
$fields['shipping_state']['default'] = 'Maharashtra';
return $fields;
}
this is not working let me know what's wrong or is there any other way to do this?
You are pretty near… As Shipping State value in WooCommerce is a state code, you need to replace 'Maharashtra' with 'MH' (For India) as follows:
add_filter( 'woocommerce_shipping_fields' , 'set_default_my_account_shipping_city_state_values' );
function set_default_my_account_shipping_city_state_values( $fields ) {
// Targeting My account section
if( is_account_page() ) {
$fields['shipping_city']['default'] = 'Mumbai';
$fields['shipping_state']['default'] = 'MH'; // <== HERE
}
return $fields;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Alternative: If it really doesn't work for you, you can try to use the following:
add_action( 'template_redirect' , 'set_default_shipping_city_and_state_values' );
function set_default_shipping_city_and_state_values( $fields ) {
if( is_wc_endpoint_url( 'edit-address' ) ) {
if ( WC()->customer->get_shipping_city() === '' ) {
WC()->customer->Set_shipping_city('Mumbai');
}
if ( WC()->customer->get_shipping_state() === '' ) {
WC()->customer->Set_shipping_state('MH');
}
}
}
You can use the address hook.
add_filter( 'woocommerce_default_address_fields' , 'default_shipping_address' );
function default_shipping_address( $fields ) {
$fields['shipping_city']['value'] = 'Mumbai';
$fields['shipping_state']['value'] = 'Maharashtra';
return $fields;
}
I've been looking for a solution to this for a while now... I am trying to show or hide specific shipping rates based on a radio button option added to the Woocommerce checkout page. But I don't know anything about Ajax & JQuery which I believe it requires.
Basically if a user selects radio option_1 it will ONLY show 'flat_rate:1' & 'flat_rate:2'. If the user selects radio option_2 it will ONLY show 'flat_rate:3' & 'flat_rate:4'
Click here for example of checkout screen
Here is the code for my radio buttons displayed on checkout page:
add_action( 'woocommerce_review_order_before_payment','tc_checkout_radio_choice' );
function tc_checkout_radio_choice() {
$businesstype = array(
'type' => 'radio',
'class' => array( 'business_residential' ),
'required' => true,
'options' => array(
'option_1' => 'Business',
'option_2' => 'Residential',
),
);
echo '<div id="checkout-radio">';
echo '<h3>Select Your Business Type</h3>';
woocommerce_form_field( 'radio_choice', $businesstype );
echo '</div>';
}
The closest example I've seen comes from an answer provided by 'LoicTheAztec' in this post here: Show/hide shipping methods based on a WooCommerce checkout field value
I'm completely lost with this and the more I try to solve it, the more I get confused.
Here is the correct way to show hide shipping methods based on multiple radio buttons choice on checkout page, that requires to use PHP, Ajax, jQuery and WC Session.
When "Business" radio button is selected (default choice), flat_rate:3 and flat_rate:4 shipping methods will be hidden, so customer will only be able to choose flat_rate:1 or flat_rate:2 shipping methods.
If "Residential" radio button is selected then flat_rate:1 and flat_rate:2 shipping methods will be hidden, so customer will only be able to choose flat_rate:3 or flat_rate:4 shipping methods.
The code:
// Display a Custom radio buttons fields for shipping options
add_action( 'woocommerce_review_order_before_payment','checkout_customer_type_radio_buttons' );
function checkout_customer_type_radio_buttons() {
$field_id = 'customer_type';
echo '<div id="customer-type-radio">';
echo '<h3>' . __("Select Your Business Type", "woocommerce") . '</h3>';
// Get the selected radio button value (if selected)
$field_value = WC()->session->get( $field_id );
// Get customer selected value on last past order
if( empty($field_value) )
$field_value = WC()->checkout->get_value( $field_id );
// The default value fallback
if( empty($field_value) )
$field_value = 'Business';
woocommerce_form_field( $field_id, array(
'type' => 'radio',
'class' => array( $field_id ),
'required' => true,
'options' => array(
'Business' => __('Business', 'woocommerce'),
'Residential' => __('Residential', 'woocommerce'),
),
), $field_value );
echo '</div>';
}
// Conditionally show/hide shipping methods
add_filter( 'woocommerce_package_rates', 'shipping_package_rates_filter_callback', 100, 2 );
function shipping_package_rates_filter_callback( $rates, $package ) {
$customer_type = WC()->session->get( 'customer_type' ); // Get the customere type
if ( $customer_type === 'Business' ) {
if( isset( $rates['flat_rate:3'] ) )
unset( $rates['flat_rate:3'] );
if( isset( $rates['flat_rate:4'] ) )
unset( $rates['flat_rate:4'] );
}
elseif ( $customer_type === 'Residential' ) {
if( isset( $rates['flat_rate:1'] ) )
unset( $rates['flat_rate:1'] );
if( isset( $rates['flat_rate:2'] ) )
unset( $rates['flat_rate:2'] );
}
return $rates;
}
// function that gets the Ajax data
add_action( 'wp_ajax_get_customer_type', 'wc_get_customer_type' );
add_action( 'wp_ajax_nopriv_get_customer_type', 'wc_get_customer_type' );
function wc_get_customer_type() {
$field_id = 'customer_type';
if ( isset($_POST[$field_id]) && ! empty($_POST[$field_id]) ){
WC()->session->set($field_id, $_POST[$field_id] );
}
echo json_encode( WC()->session->get( $field_id ) );
die(); // (required)
}
// The Jquery Ajax script
add_action( 'wp_footer', 'custom_checkout_ajax_jquery_script' );
function custom_checkout_ajax_jquery_script() {
$field_id = 'customer_type';
if( WC()->session->__isset($field_id) )
WC()->session->__unset($field_id);
// Only on checkout when billing company is not defined
if( is_checkout() && ! is_wc_endpoint_url() ):
?>
<script type="text/javascript">
jQuery( function($){
if (typeof wc_checkout_params === 'undefined')
return false;
var fieldId = 'p#customer_type_field input';
function userTypeTriggerAjax( customerType ){
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'get_customer_type',
'customer_type': customerType,
},
success: function (result) {
// Trigger refresh checkout
$('body').trigger('update_checkout');
console.log(result);
}
});
}
// On start
if( $(fieldId).val() != '' ) {
userTypeTriggerAjax( $(fieldId).val() );
}
// On change
$(fieldId).change( function () {
userTypeTriggerAjax( $(this).val() );
});
});
</script>
<?php
endif;
}
// Enabling, disabling and refreshing session shipping methods data
add_action( 'woocommerce_checkout_update_order_review', 'refresh_shipping_methods', 10, 1 );
function refresh_shipping_methods( $post_data ){
$bool = true;
if ( in_array( WC()->session->get('customer_type' ), ['Business', 'Residential'] ) )
$bool = false;
// Mandatory to make it work with shipping methods
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
}
WC()->cart->calculate_shipping();
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Code based on this related threads:
Remove shipping cost if custom checkbox is checked in WooCommerce Checkout
Show/hide shipping methods based on a WooCommerce checkout field value
If anyone wants to know how to make this a required option with an error notification before payment, this is what I did...
To de-select the radio buttons I removed the value from:
//if( empty($field_value) )
$field_value = '';
And added this code to the checkout process at the end of the code from Loic, which adds a notification message if a radio button has not been selected:
// Show notice if customer does not select a business type
add_action( 'woocommerce_checkout_process', 'business_type_error_message' );
function business_type_error_message() {
if ( ! (int) isset( $_POST['customer_type'] ) ) {
wc_add_notice( __( 'Please select an order type to calculate the correct shipping and proceed with your order.' ), 'error' );
}
}
Hope this helps others out there!
In my Wordpress Woocommerce site I need to skip the required validation for billing city field when the user selects a specific billing country.
I have referred answers in the following similar questions but both these don't seem to work on my checkout page. All I did was copy pasting those functions in the functions.php of the active theme and changed the fields and function names accordingly. But the changes or the codes don't appear in the source code of the checkout page and the required validation still works for the countries where the city is not required to be entered.
Referred (Neither of the following answers work for my checkout page)
Make checkout phone field optional for specific countries in WooCommerce
Make Woocommerce checkout phone field optional based on shipping country
My Code
// Making the billing city field not required (by default)
add_filter( 'woocommerce_billing_fields', 'filter_billing_city_field');
function filter_billing_city_field( $fields ) {
$fields['billing_city']['required'] = false;
return $fields;
}
// Real time country selection actions
add_action( 'woocommerce_after_order_notes', 'custom_checkout_scripts_and_fields', 8, 1 );
function custom_checkout_scripts_and_fields( $checkout ) {
$required = esc_attr__( 'required', 'woocommerce' );
// HERE set the countries codes (2 capital letters) in this array:
$countries = array( 'LK');
// Hidden field for the phone number validation
echo '<input type="hidden" name="billing_city_check" id="billing_city_check" value="0">';
$countries_str = "'".implode( "', '", $countries )."'"; // Formatting countries for jQuery
?>
<script type="text/javascript">
(function($){
var required = '<abbr class="required" title="<?php echo $required; ?>">*</abbr>',
countries = [<?php echo $countries_str; ?>],
location = $('#billing_country option:selected').val(),
cityCheck = 'input#billing_city_check';
function actionRequire( actionToDo='yes', selector='' ){
if ( actionToDo == 'yes' ) {
$(selector).addClass("validate-required");
$(selector+' label').append(required);
} else {
$(selector).removeClass("validate-required");
$(selector+' label > .required').remove();
}
$(selector).removeClass("woocommerce-validated");
$(selector).removeClass("woocommerce-invalid woocommerce-invalid-required-field");
}
// Default value when loading
actionRequire( 'no','#billing_city_check' );
if( $.inArray( location, countries ) >= 0 && $(cityCheck).val() == '0' ){
actionRequire( 'yes','#billing_city_check' );
$(cityCheck).val('1');
}
// Live value
$( 'form.checkout' ).on( 'change', '#billing_country', function(){
var location = $('#billing_country option:selected').val();
if ( $.inArray( location, countries ) >= 0 && $(cityCheck).val() == 0 ) {
actionRequire( 'yes','#billing_city_check' );
$(cityCheck).val('1');
} else if ( $(cityCheck).val() == 1 ) {
actionRequire( 'no','#billing_city_check' );
$(cityCheck).val('0');
}
});
})(jQuery);
</script>
<?php
}
// City validation, when it's visible
add_action('woocommerce_checkout_process', 'billing_city_field_process');
function billing_city_field_process() {
// Check if set, if its not set add an error.
if ( ! $_POST['billing_city'] && $_POST['billing_city_check'] == '1' )
wc_add_notice( __( 'Please enter city.' ), 'error' );
}
Add this code snippet in your activated theme function.php at the end.
add_filter('woocommerce_billing_fields', 'vg_customize_checkout_form');
function vg_customize_checkout_form($fields)
{
$fields['billing_city']['required'] = false;
return $fields;
}
#Source: https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-checkout.php#L845
add_action('woocommerce_after_checkout_validation', 'vg_custom_validation_billing_city', 10, 2);
function vg_custom_validation_billing_city($fields, $error)
{
if('IN' != $fields['billing_country'] && empty($fields['billing_city'])) {
$error->add('validation', 'City is required field.');
}
}
Let's see each hook functionality:
Filter Hook: woocommerce_billing_fields
Official Document:
https://docs.woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/#section-8
https://docs.woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/#section-2
Purpose: We can alter the predefined properties of the field by overriding the array element as we have overridden the required property of the billing_city field
Action Hook: woocommerce_after_checkout_validation
Official Document:
https://docs.woocommerce.com/wc-apidocs/source-class-WC_Checkout.html#830
Purpose: To add custom code after the cart has been successfully checkout. Here we can add our custom validation and based on validation success or failure we can through error.
Here we verified whether the billing country is not India then the city should be filled. If the user not filled the city for other than India then our custom error City is a required field. will be thrown.
Reference:
https://docs.woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/
https://docs.woocommerce.com/wc-apidocs/hook-docs.html
In WooCommerce, I have set different shipping methods, but one in particular must be exclusive for companies.
For this I am using the following code:
add_filter( 'woocommerce_package_rates', array( $this, 'package_rates' ), 10, 2 );
public function package_rates( $rates, $package ) {
$company_rate_id = 'flat_rate:7';
if(!empty(WC()->customer->get_billing_company())){
$company_rates = $rates[ $company_rate_id ];
$rates = array( $company_rate_id => $company_rates );
}else{
unset( $rates[ $company_rate_id ] );
}
return $rates;
}
The solution works, but only if the billing company already exist and is saved in the database. So if a customer updates this information on the checkout page, it doesn't work.
A possible solution would be to save this field live (billing_company).
I tried the following function:
add_filter( 'woocommerce_checkout_fields' , 'trigger_update_checkout_on_change' );
function trigger_update_checkout_on_change( $fields ) {
$fields['billing']['billing_company']['class'][] = 'update_totals_on_change';
return $fields;
}
This updates the shipping method, the problem is that again, the field is not saved in the database and the package_rates() function can not find it live.
This is a bit more complicated than that… jQuery and Ajax code are required to allow showing/hiding shipping methods based on a checkout field user input.
The following code will enable show/hide pre defined shipping methods based on checkout billing company field:
// Conditionally show/hide shipping methods
add_filter( 'woocommerce_package_rates', 'shipping_package_rates_filter_callback', 100, 2 );
function shipping_package_rates_filter_callback( $rates, $package ) {
// The defined rate id
$company_rate_id = 'flat_rate:7';
if( WC()->session->get('company' ) === '1' ) {
$rates = array( $company_rate_id => $rates[ $company_rate_id ] );
} else {
unset( $rates[ $company_rate_id ] );
}
return $rates;
}
// function that gets the Ajax data
add_action( 'wp_ajax_get_customer_company', 'wc_get_customer_company' );
add_action( 'wp_ajax_nopriv_get_customer_company', 'wc_get_customer_company' );
function wc_get_customer_company() {
if ( isset($_POST['company']) && ! empty($_POST['company']) ){
WC()->session->set('company', '1' );
} else {
WC()->session->set('company', '0' );
}
die(); // (required)
}
// The Jquery Ajax script
add_action( 'wp_footer', 'custom_checkout_script' );
function custom_checkout_script() {
if( WC()->session->__isset('company') )
WC()->session->__unset('company');
// Only on checkout when billing company is not defined
if( is_checkout() && ! is_wc_endpoint_url() ):
?>
<script type="text/javascript">
jQuery( function($){
if (typeof wc_checkout_params === 'undefined')
return false;
var fieldId = 'input#billing_company';
function companyTriggerAjax( company ){
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'get_customer_company',
'company': company,
},
success: function (result) {
// Trigger refresh checkout
$('body').trigger('update_checkout');
}
});
}
// On start
if( $(fieldId).val() != '' ) {
companyTriggerAjax( $(fieldId).val() );
}
// On change
$(fieldId).change( function () {
companyTriggerAjax( $(this).val() );
});
});
</script>
<?php
endif;
}
// Enabling, disabling and refreshing session shipping methods data
add_action( 'woocommerce_checkout_update_order_review', 'refresh_shipping_methods', 10, 1 );
function refresh_shipping_methods( $post_data ){
$bool = true;
if ( WC()->session->get('company' ) === '1' )
$bool = false;
// Mandatory to make it work with shipping methods
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
}
WC()->cart->calculate_shipping();
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Related: Remove shipping cost if custom checkbox is checked in WooCommerce Checkout