Good morning everybody.
I need to implement a method to subtract shipping costs from the total in case the shipping destination is included in a specific array of values.
This is not a case of free shipping, because this costs will be added later for other reasons.
I cannot base my decision on the user country, for two reasons:
user can be not registered
user country and shipping country can be different.
I see that WooCommerce reload the order totals when I change billing/shipping country. I believe I need to intercept this kind of change an trigger an action to insert a new cart fee (a negative one, of course).
Well, how can I do that?
This is a part of my code
function delayShippingCosts(){
global $woocommerce;
$EUcountries = ['IT','AT','BE','BG','CY','HR','DK','EE','FI','FR','DE','GR','IE','LV','LT','LU','MT','NE','PL','PT','CZ','RO','SK','SI','ES','SE','HU'];
return in_array( $woocommerce->customer->get_country() , $EUcountries);
}
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
global $woocommerce;
if(delayShippingCosts()){
$shippingCosts = WC()->cart->get_shipping_total() * -1;
if(current_user_can('administrator')) {
$woocommerce->cart->add_fee( 'Delayed shipping costs', $shippingCosts, true, 'standard' );
}
}
}
The problem is that now I'm looking to my customer data, and these are not dynamic (and void for unregisterd / unlogged users).
Any suggestions?
Thanks!!
EDIT
Almost OK
I managed to retrieve the shipping country from "woocommerce_checkout_update_order_review" hook, like that:
function action_woocommerce_checkout_update_order_review($posted_data) {
global $shipTo;
$data = array();
$vars = explode('&', $posted_data);
foreach ($vars as $k => $value){
$v = explode('=', urldecode($value));
$data[$v[0]] = $v[1];
}
WC()->cart->calculate_shipping();
$shipTo = $data['shipping_country'] ? $data['shipping_country'] : $data['billing_country'];
// REMOVE ALL NOTICES, IF PRESENTS...
wc_clear_notices();
}
add_action('woocommerce_checkout_update_order_review', 'action_woocommerce_checkout_update_order_review', 10, 1);
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
global $woocommerce;
... something ...
if(condition) {
wc_add_notice("info message", "error");
}
}
My problem is that the notice is not removed when "condition" in false.
I tried to call wc_remove_notices() both in woocommerce_cart_calculate_fees and woocommerce_checkout_update_order_review. No big difference! :(
Any hint?
The delayShippingCosts() function is not needed. You can get the list of European countries via the get_european_union_countries method of the WC_Countries class (unless you want to customize the list).
Also you are getting the country with the get_country() method of the WC_Customer class.
The WC_Customer::get_country function is deprecated since version 3.0.
You can get the country like this:
WC()->customer->get_shipping_country() the country of the shipping address
WC()->customer->get_billing_country() the country of the billing address
Finally, note that to apply the standard tax class for the fee you have to set the 4th parameter as an empty string instead of 'standard'. See here for more information.
TO GET THE COUNTRY FIELD FROM A USER NOT LOGGED IN WITH THE woocommerce_cart_calculate_fees HOOK
You can send an AJAX call to send the billing and shipping country value when the respective fields change in the checkout.
To make sure that the AJAX function is executed before the woocommerce_cart_calculate_fees hook it is necessary to remove the update_totals_on_change class from the billing and shipping country fields (to avoid the AJAX call being made to update the checkout) and update the checkout only after the call AJAX has been completed.
This method may take a few extra milliseconds/second to update the checkout because you have to wait for the AJAX call to create the option to complete.
See this answer for more details on how to submit an AJAX call in Wordpress.
Add the following code inside your active theme's functions.php:
// enqueue the script for the AJAX call
add_action('wp_enqueue_scripts', 'add_js_scripts');
function add_js_scripts(){
wp_enqueue_script( 'ajax-script', get_stylesheet_directory_uri().'/js/script.js', array('jquery'), '1.0', true );
wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
}
// update options with checkout country values
add_action( 'wp_ajax_nopriv_set_option_country', 'set_option_country' );
add_action( 'wp_ajax_set_option_country', 'set_option_country' );
function set_option_country() {
if ( isset( $_POST ) ) {
// get the countries valued in the checkout by the user (guest or logged in)
$countries = $_POST['countries'];
$billing_country = $countries['billing_country'];
$shipping_country = $countries['shipping_country'];
// update options
update_option( 'guest_billing_country', $billing_country );
update_option( 'guest_shipping_country', $shipping_country );
// returns the output as a response to the AJAX call
echo 'success';
}
// always die in functions echoing AJAX content
die();
}
Create a script.js file and add it inside your child theme (because I used get_stylesheet_directory_uri() instead of get_template_directory_uri()) in the directory: /child-theme/js/script.js:
jQuery(function($){
// disable AJAX update
$('#billing_country_field').removeClass('update_totals_on_change');
$('#shipping_country_field').removeClass('update_totals_on_change');
// when the country fields change
$('#billing_country, #shipping_country').change(function(){
var countries = {
'billing_country': $('#billing_country').val(),
'shipping_country': $('#shipping_country').val(),
};
$.ajax({
url: ajax_object.ajaxurl,
type : 'post',
data: {
'action': 'set_option_country',
'countries': countries
},
complete: function(){
// update checkout via AJAX
$(document.body).trigger('update_checkout');
},
success:function(data) {
console.log(data);
},
error: function(errorThrown){
console.log(errorThrown);
}
});
});
});
The code has been tested and works.
So, the correct scc_detract_shipping_costs function will be:
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
$countries = new WC_Countries();
// get the list of countries of the european union
$eu_countries = $countries->get_european_union_countries();
// get countries from checkout
$billing_country = get_option( 'guest_billing_country' );
$shipping_country = get_option( 'guest_shipping_country' );
// if the shipping country is part of the European Union
if ( in_array( $shipping_country, $eu_countries ) ) {
$shippingCosts = WC()->cart->get_shipping_total() * -1;
if ( current_user_can('administrator') ) {
WC()->cart->add_fee( 'Delayed shipping costs', $shippingCosts, true, '' );
}
}
}
The code has been tested and works. Add it to your active theme's functions.php.
TO GET THE COUNTRY FIELD FROM A USER NOT LOGGED IN WITH THE woocommerce_calculate_totals HOOK
add_action( 'woocommerce_calculate_totals', 'get_post_checkout_data' );
function get_post_checkout_data( $cart ) {
// get post data
if ( isset( $_POST['post_data'] ) ) {
parse_str( $_POST['post_data'], $post_data );
} else {
$post_data = $_POST;
}
if ( ! empty( $post_data ) ) {
$billing_country = $post_data['billing_country'];
$shipping_country = $post_data['shipping_country'];
// ...
}
}
Add the code in your active theme's functions.php.
I have a change_flat_rates_cost() function, but it works only when loading the page. but it is necessary for me that function worked at each change of the field "city", (in checkout fields).
I have tried to use ajax function and to call there add_filter() function, but it doesn't help.
I do it to change delivery cost without reset of the page depending on the chosen city
change_flat_rates_cost function (changes the shipping price)
function change_flat_rates_cost($rates, $package) {
$userid = get_current_user_id();
$meta_city = get_user_meta( $userid, 'billing_city', true );
if ( isset( $rates['shipping_method_1'] ) ) {
if ($meta_city == 'City1'){
$rates['shipping_method_1']->cost = 100;
}
if ($meta_city == 'City2'){
$rates['shipping_method_1']->cost = 200;
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'change_flat_rates_cost', 10, 2 );
ajax function:
add_action( 'wp_ajax_change_shipping', 'change_shipping' );
add_action( 'wp_ajax_nopriv_change_shipping', 'change_shipping' );
function change_shipping() {
global $woocommerce;
add_filter( 'woocommerce_package_rates', 'change_flat_rates_cost', 10, 2 );
die();
}
You can add filter as normal "add_filter". No need to put it there in ajax, because anyways there are conditions mentioned in your function.
So, do add_filter normally and instead of using add_filter in ajax function, you should use "apply_filters". This will call your custom shipping function.
I hope I've made it clear.
I'm trying to remove the autoloaded user info in the various checkout fields but cannot seem to find any way to access the fields value. I've tried the following which clears formatting, removes, the field, etc. but nothing I can find shows how to remove just the value. Does anyone know how to access this?
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );
function custom_override_checkout_fields( $fields ) {
$fields['billing']['billing_first_name']['placeholder'] = '';
$fields['billing']['billing_last_name']['value'] = '';
unset($fields['billing']['billing_company']);
return $fields;
}
Register filter:
add_filter( 'woocommerce_checkout_get_value' , 'clear_checkout_fields' );
Add this function. All fields will be empty.
function clear_checkout_fields($input)
{
return '';
}
Does anybody know how to disable the Postcode validation on the checkout page in WooCommerce?
I want to input 'text content' in "billing_postcode" field, but the shop says it's an invalid postcode.
Does anyone know how I can disable Postcode validation or allow text characters?
Just do this:
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields', 99 );
function custom_override_checkout_fields( $fields ) {
unset($fields['billing']['billing_postcode']['validate']);
unset($fields['shipping']['shipping_postcode']['validate']);
return $fields;
}
You can put this pretty much anywhere, but preferably in a custom plugin or in your theme's functions.php
You could try this:
add_filter( 'woocommerce_default_address_fields', 'custom_override_address_fields', 999, 1 );
function custom_override_address_fields( $address_fields ) {
// set as not required
$address_fields['postcode']['required'] = false;
// remove validation
unset( $address_fields['postcode']['validate'] );
return $address_fields;
}
I would like logged in users to be able to use 'cheque' payment option.
I found the following relevant thread: WooCommerce Show Payment Gateways for Logged In Customers Only However, placing the following code in functions.php no longer works for Woocommerce version 2.3.7:
add_filter( "woocommerce_available_payment_gateways", "rp_filter_gateways", 9999 );
function rp_filter_gateways($args) {
if(!is_user_logged_in() && isset($args['cheque'])) {
unset($args['cheque']);
}
return $args;
}
Please can someone provide me with an updated solution?
Many thanks
woocommerce_available_payment_gateways is still a valid hook. The following code has been tested with the latest version of WooCommerce:
function rp_filter_gateways( $args ) {
if( !is_user_logged_in() && isset($args['cheque']) ) {
unset( $args['cheque'] );
}
return $args;
}
add_action( 'woocommerce_available_payment_gateways', 'rp_filter_gateways' );