How to change Shipping Zones depending on the payment option in Woocommerce? - php

Target: I want to add different shipping cost for the different payment gateways depending on the pin code user have.
Tried Using Shipping Zones:
I have 3 different shipping zones added in my woo-commerce dashboard. All the zones are created using the pin codes of the city and flat rate. However, if the pin code exists in multiple zones, then the top level zone gets the first priority and all other zones get ignored at the checkout page.
Now I want to change that zone depending on the Payment Gateway user chooses. For example user, 'A' has the pin code '123456' and this pin code exists in two Shipping Zones: 'SH1', 'SH2'. So, if the user chose 'Cash On Delivery' option, then I want to disable shipping zone 'SH1' for him while activating 'SH2'.
Here is the code I tried, but not works.
add_filter( 'woocommerce_available_payment_gateways', 'change_user_zone', 20, 1);
function change_user_zone( $gateways ){
$targeted_zones_names = array('COD FCity'); // Targeted zone names
$current_payment_gateway = WC()->session->get('chosen_payment_method'); // Selected payment gateway
// Current zone name
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); // The chosen shipping mehod
$chosen_method = explode(':', reset($chosen_methods) );
$shipping_zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $chosen_method[1] );
$current_zone_name = $shipping_zone->get_zone_name();
if($current_payment_gateway === "cod" && $current_zone_name === "COD FCity"){
$WC_Shipping_Zone = new WC_Shipping_Zone();
$var = $WC_Shipping_Zone->set_zone_locations( "India - Maharashtra - Mumbai" );
}
return $gateways;
}
Please help!
Note: I'm looking for a coding solution. If you want to suggest any plugin for this, then use the comment section, please.

Ok, I don't find a way to change the shipping zone. So I added the extra fee in the cart depending on the Payment Gateway and Shipping Zone.
add_filter( 'woocommerce_before_calculate_totals', 'update_the_cart_with_extra_fee');
function update_the_cart_with_extra_fee(){
global $woocommerce;
$targeted_zones_names = array('SH1'); // Targeted zone names
$current_payment_gateway = WC()->session->get('chosen_payment_method'); // Selected payment gateway
// Current zone name
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); // The chosen shipping mehod
$chosen_method = explode(':', reset($chosen_methods) );
$shipping_zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $chosen_method[1] );
$current_zone_name = $shipping_zone->get_zone_name();
if($current_payment_gateway === "cod" && $current_zone_name === "SH1"){
$extra_shipping_cost = 0;
//Loop through the cart to find out the extra costs
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
//Get the product info
$_product = $values['data'];
//Get the custom field value - make sure it's i.e. 10 not $10.00
$custom_shipping_cost = 60;
//Adding together the extra costs
$extra_shipping_cost = $extra_shipping_cost + $custom_shipping_cost;
$woocommerce->cart->add_fee( __('Cash on Delivery', 'woocommerce'), $extra_shipping_cost ); // Place this outside of foreach loop if you want to add fee for every product in the cart.
}
}
}
I hope this will help someone.

Related

How do I hide shipping method if order amount is under minimum? Woocommerce

I need to hide or only let the customers choose shipping option delivery if the order amount is above 15. If the order amount is under 15 they should not order for delivery only for local pickup.
I tried some code i found here, but it doesnt work. it only works on cart page, if you change the delivery option on checkout page you can still order with delivery shipment method.
This is the code I found.
add_action( 'woocommerce_check_cart_items', 'wc_minimum_required_order_amount' );
function wc_minimum_required_order_amount() {
// HERE Your settings
$minimum_amount = 15; // The minimum cart total amount
$shipping_method_id = 'local_pickup'; // The targeted shipping method Id (exception)
// Get some variables
$total = (float) WC()->cart->total; // Total cart amount
$chosen_methods = (array) WC()->session->get( 'chosen_shipping_methods' ); // Chosen shipping method rate Ids (array)
// Only when a shipping method has been chosen
if ( ! empty($chosen_methods) ) {
$chosen_method = explode(':', reset($chosen_methods)); // Get the chosen shipping method Id (array)
$chosen_method_id = reset($chosen_method); // Get the chosen shipping method Id
}
// If "Local pickup" shipping method is chosen, exit (no minimun is required)
if ( isset($chosen_method_id) && $chosen_method_id === $shipping_method_id ) {
return; // exit
}
// Add an error notice is cart total is less than the minimum required
if ( $cart_total < $minimum_amount ) {
wc_add_notice( sprintf(
__("The minimum required order amount is %s (your current order amount is %s).", "woocommerce"), // Text message
wc_price( $minimum_amount ),
wc_price( $cart_total )
), 'error' );
}
}

Only display wc_add_notice IF customer_postcode is not empty

I have gleaned code on stack overflow that allows me to display notices with minimum cart totals based on Zip code. I include the code below.
My PROBLEM is that the message automatically defaults to Zone 1.
Which means that if a new visitor (who actually lives in Zone 2) comes to the site, places items in cart, visits cart -> the error message for Zone 1 is already showing up. The new visitor has not input their postcode yet. The cart shipping calculator is where the new visitor will enter their postcode (zip code).
I don't know what will be a workaround for this problem, but I was wondering if one can add an IF statement before the Cart Notice code, that basically checks: if customer postcode is not set yet, then display message: "Please enter your postcode".
Then, if customer_postcode is set, then display the minimum cart value messages based on zones as in the existing code already.
I hope I am making sense - and hope someone can help me.
//MINIMUM CART AMOUNT FOR CHECKOUT
add_action( 'woocommerce_check_cart_items', 'required_min_cart_subtotal_amount' );
function required_min_cart_subtotal_amount() {
$zone = WC_Shipping_Zones::get_zone_by ( 'zone_name', "1" . 'zone_name', "2" . 'zone_name' , "3" );
// Only run it in Cart or Checkout pages
if( is_cart() || ( is_checkout() && ! is_wc_endpoint_url() ) ) {
// Get cart shipping packages
$shipping_packages = WC()->cart->get_shipping_packages();
//Get the WC_Shipping_Zone instance object for the first package
$shipping_zone = wc_get_shipping_zone( reset( $shipping_packages ) );
$zone_id = $shipping_zone->get_id(); //Get the zone ID
$zone_name = $shipping_zone->get_zone_name(); // Get the zone name
// Total (before taxes and shipping charges)
$total = WC()->cart->subtotal;
// HERE set minimum cart total amount (for Zone 1,2,3 and for other zones)
// $min_total = $zone_id == 200.0 : 99.99 ? "1";
$min_total = 180;
if ($zone_id == "2") $min_total = 100;
if ($zone_id == "3") $min_total = 220;
// Add an error notice if cart total is less than the minimum required
if( $total <= $min_total ) {
// Display an error message
wc_add_notice( sprintf(
__("A minimum order of - %s is required. Please enter your Postal Code in the Shipping section below. Minimum orders differ from area to area."),
wc_price( $min_total)
), 'error' );
remove_action( 'woocommerce_proceed_to_checkout','woocommerce_button_proceed_to_checkout', 20);
}
add_action( 'woocommerce_proceed_to_checkout', 'disable_checkout_button', 1);
}
}

Set all shipping methods cost to zero for a Free shipping coupon in Woocommerce

I have 3 shipping methods in my cart that should become zero prices as soon as your customer enters Free Shipping coupon.
I know how to add a filter in functions.php to detect the coupon but is someone know a snippet to set shipping methods visibles in cart (radio button) to ZERO for this order?
My deliveries methods are companies like UPS, FedEx...
I activated the free shipping option in order it can be managed with coupon.
The list of choice of deliveries methods for the customers is calculated according to my products shipping class and order total weight.
Shipping class are not calculated but set by product and i use TABLE RATE PLUGIN to calculate the weight.
First free shipping method has to be enabled with option "A valid free shipping coupon"…
Then you need to set the desired coupon codes with option "Allow free shipping" enabled.
The following code will set all shipping methods costs to zero when a valid coupon code (with option "Allow free shipping" enabled) will be applied.
Update: Hide "Free shipping" method and append shipping label titles with "(free)"
add_filter( 'woocommerce_package_rates', 'coupon_free_shipping_customization', 20, 2 );
function coupon_free_shipping_customization( $rates, $package ) {
$has_free_shipping = false;
$applied_coupons = WC()->cart->get_applied_coupons();
foreach( $applied_coupons as $coupon_code ){
$coupon = new WC_Coupon($coupon_code);
if($coupon->get_free_shipping()){
$has_free_shipping = true;
break;
}
}
foreach( $rates as $rate_key => $rate ){
if( $has_free_shipping ){
// For "free shipping" method (enabled), remove it
if( $rate->method_id == 'free_shipping'){
unset($rates[$rate_key]);
}
// For other shipping methods
else {
// Append rate label titles (free)
$rates[$rate_key]->label .= ' ' . __('(free)', 'woocommerce');
// Set rate cost
$rates[$rate_key]->cost = 0;
// Set taxes rate cost (if enabled)
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 )
$taxes[$key] = 0;
}
$rates[$rate_key]->taxes = $taxes;
}
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme).
Tested and works. It should works also for you.
Sometimes, you should may be need to refresh shipping methods:
1) Empty cart first.
2) Go to shipping Zones settings, then disable/save and re-enable/save the related shipping methods.
If you want to achieve the same but whenever there is a free shipping method available (not only applied coupon, but also cart total is above certain price), you can use this:
add_filter( 'woocommerce_package_rates', 'wc_apply_free_shipping_to_all_methods', 10, 2 );
function wc_apply_free_shipping_to_all_methods( $rates, $package ) {
if( isset( $rates['free_shipping:11'] ) ) {
unset( $rates['free_shipping:11'] );
foreach( $rates as $rate_key => $rate ) {
// Append rate label titles (free)
$rates[$rate_key]->label .= ' ' . __('(free)', 'woocommerce');
// Set rate cost
$rates[$rate_key]->cost = 0;
// Set taxes rate cost (if enabled)
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 )
$taxes[$key] = 0;
}
$rates[$rate_key]->taxes = $taxes;
}
}
return $rates;
}
Notice, that after free_shipping there is a :11. Prior to WooCommerce 2.6, the details of shipping method "Free Shipping" were stored under array element $rates['free_shipping']. Now, however, it is stored as $rates['free_shipping:shipping_zone_instance_id'] where shipping_zone_instance_id is the shipping zone instance id of the shipping method. You can check the instance id of the shipping method by inspecting the "Free Shipping" in admin panel, or opening it in new tab and looking at the url http://prntscr.com/jd2zjy.

Update WooCommerce "flat rate" shipping cost using jQuery

I am trying to update the shipping cost after the address is entered. When the client starts typing the address, and autocomplete function helps the client find the street name, building, floor, and side.
Once the address is entered JavaScript calculates the distance and the price.
Then the price is via AJAX send to shipment.php
It is my goal to have shipment update the shipping cost accordingly.
I have tried:
include '../../../../wp-load.php';
add_action( 'woocommerce_flat_rate_shipping_add_rate','add_another_custom_flat_rate', 10, 2 );
function add_another_custom_flat_rate( $method, $rate ) {
$new_rate = $rate;
$new_rate['cost'] = 50; // Update the cost to 50
$method->add_rate( $new_rate );
}
But without luck.
I have also following this guide and added another shipping method: Tuts
https://code.tutsplus.com/tutorials/create-a-custom-shipping-method-for-woocommerce--cms-26098
But once again, I cannot update the price once the address is entered.
I think that You don't need Query ajax to update the price directly for this calculated shipping cost and you don't need a custom shipping method as in your code or linked tutorial.
THERE IS THREE STEPS:
1) In your existing Ajax driven PHP function, you will set the calculated price in a custom WC_Session attribute where $shipping_cost variable will be the collected value of the calculated shipping cost passed via JS Ajax:
WC()->session->set( 'shipping_calculated_cost', $shipping_cost );
Then in jQuery Ajax "on success" event you will update checkout with the following:
$('body').trigger('update_checkout');
That will refresh checkout data…
2) In the custom function hooked in woocommerce_package_rates filer hook, you will get the session value of the calculated shipping cost…
IMPORTANT: In WooCommerce settings > shipping, you will set the "flat rate" cost to 1…
In The function code you will set the default price for this flat rate (that will be used when the shipping cost calculation doesn't exist yet):
add_filter('woocommerce_package_rates', 'update_shipping_costs_based_on_cart_session_custom_data', 10, 2);
function update_shipping_costs_based_on_cart_session_custom_data( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// SET HERE the default cost (when "calculated cost" is not yet defined)
$cost = '50';
// Get Shipping rate calculated cost (if it exists)
$calculated_cost = WC()->session->get( 'shipping_calculated_cost');
// Iterating though Shipping Methods
foreach ( $rates as $rate_key => $rate_values ) {
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
// For "Flat rate" Shipping" Method only
if ( 'flat_rate' === $method_id ) {
if( ! empty( $calculated_cost ) ) {
$cost = $calculated_cost;
}
// Set the rate cost
$rates[$rate_id]->cost = number_format($rates[$rate_id]->cost * $cost, 2);
// Taxes rate cost (if enabled)
foreach ($rates[$rate_id]->taxes as $key => $tax){
if( $rates[$rate_id]->taxes[$key] > 0 ){ // set the new tax cost
$taxes[$key] = number_format( $rates[$rate_id]->taxes[$key] * $cost, 2 );
$has_taxes = true;
} else {
$has_taxes = false;
}
}
if( $has_taxes )
$rates[$rate_id]->taxes = $taxes;
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme) or in any plugin file.
3) Refresh the shipping caches (needed sometimes):
First empty your cart.
This code is already saved on your function.php file.
Go in a shipping zone settings and disable one "flat rate" (for example) and "save". Then re-enable that "flat rate" and "save". You are done and you can test it.
This should work for you,
If you have many different "flat rates" by shipping zones, you will need to make some changes in my code, if the default cost is different for each…

Display shipping cost on product page - WooCommerce

I'm trying to make a custom page on Wordpress that display 2 subscription types in a form with 3 variations each (monthly, 6 month, 12 month). Each variation gets a radio button and I have a total price that is live updated when user clicks on the radio buttons. This part is working.
But now, I want to add 3 other radio buttons to choose the shipment method. (When user select one, it'll live update the total price too).
I've been searching a long time how to get shipping cost for a product but nothing has worked.
This question is too broad. So I can answer partially as you should need to make some work yourself, and ask later more specific questions…
Now the correct way to set shipping data on product page, is to use Ajax to update the data, as the action is made by the customer on client side (browser), avoiding 'post' and reload the page. But this should be your work...
1) Customer location (for shipping Zones):
You should need to get the customer location or shipping zone first.
Then you will need to update the customer country in WC()->session and in WC()->customer objects. This can be done with the following:
## Get the geolocated customer country code *(if enabled)*:
$country_code = WC()->customer->get_billing_country();
// or
// $country_code = WC()->customer->get_shipping_country();
## Set a new country code
$new_country_code = 'FR';
## 1. WC_session: set customer billing and shipping country
// Get the data
$customer_session = WC()->session->get( 'customer' );
// Change some data
$customer_session['country'] = $new_country_code; // Billing
$customer_session['shipping_country'] = $new_country_code; // Shipping
// Set the changed data
$customer_session = WC()->session->set( 'customer', $customer_session );
## 2. WC_Customer: set customer billing and shipping country
WC()->customer->set_billing_country( $new_country_code );
WC()->customer->set_shipping_country( $new_country_code );
2) The shipping methods (by Shipping Zone, with costs):
In Woocommerce the Shipping methods for a Shipping Zone are only available when customer add a product to cart…
Based on this answer code: Display shipping methods to frontend as in the admin panel? we can make a custom array of the necessary data to be used to get the shipping methods by shipping Zones, with costs and everything needed.
The code below is much more complete and include the shipping methods costs:
// Initializing variable
$zones = $data = $classes_keys = array();
// Rest of the World zone
$zone = new \WC_Shipping_Zone(0);
$zones[$zone->get_id()] = $zone->get_data();
$zones[$zone->get_id()]['formatted_zone_location'] = $zone->get_formatted_location();
$zones[$zone->get_id()]['shipping_methods'] = $zone->get_shipping_methods();
// Merging shipping zones
$shipping_zones = array_merge( $zones, WC_Shipping_Zones::get_zones() );
// Shipping Classes
$shipping = new \WC_Shipping();
$shipping_classes = $shipping->get_shipping_classes();
// The Shipping Classes for costs in "Flat rate" Shipping Method
foreach($shipping_classes as $shipping_class) {
//
$key_class_cost = 'class_cost_'.$shipping_class->term_id;
// The shipping classes
$classes_keys[$shipping_class->term_id] = array(
'term_id' => $shipping_class->term_id,
'name' => $shipping_class->name,
'slug' => $shipping_class->slug,
'count' => $shipping_class->count,
'key_cost' => $key_class_cost
);
}
// For 'No class" cost
$classes_keys[0] = array(
'term_id' => '',
'name' => 'No shipping class',
'slug' => 'no_class',
'count' => '',
'key_cost' => 'no_class_cost'
);
foreach ( $shipping_zones as $shipping_zone ) {
$zone_id = $shipping_zone['id'];
$zone_name = $zone_id == '0' ? __('Rest of the word', 'woocommerce') : $shipping_zone['zone_name'];
$zone_locations = $shipping_zone['zone_locations']; // array
$zone_location_name = $shipping_zone['formatted_zone_location'];
// Set the data in an array:
$data[$zone_id]= array(
'zone_id' => $zone_id,
'zone_name' => $zone_name,
'zone_location_name' => $zone_location_name,
'zone_locations' => $zone_locations,
'shipping_methods' => array()
);
foreach ( $shipping_zone['shipping_methods'] as $sm_obj ) {
$method_id = $sm_obj->id;
$instance_id = $sm_obj->get_instance_id();
$enabled = $sm_obj->is_enabled() ? true : 0;
// Settings specific to each shipping method
$instance_settings = $sm_obj->instance_settings;
if( $enabled ){
$data[$zone_id]['shipping_methods'][$instance_id] = array(
'$method_id' => $sm_obj->id,
'instance_id' => $instance_id,
'rate_id' => $sm_obj->get_rate_id(),
'default_name' => $sm_obj->get_method_title(),
'custom_name' => $sm_obj->get_title(),
);
if( $method_id == 'free_shipping' ){
$data[$zone_id]['shipping_methods'][$instance_id]['requires'] = $instance_settings['requires'];
$data[$zone_id]['shipping_methods'][$instance_id]['min_amount'] = $instance_settings['min_amount'];
}
if( $method_id == 'flat_rate' || $method_id == 'local_pickup' ){
$data[$zone_id]['shipping_methods'][$instance_id]['tax_status'] = $instance_settings['tax_status'];
$data[$zone_id]['shipping_methods'][$instance_id]['cost'] = $sm_obj->cost;
}
if( $method_id == 'flat_rate' ){
$data[$zone_id]['shipping_methods'][$instance_id]['class_costs'] = $instance_settings['class_costs'];
$data[$zone_id]['shipping_methods'][$instance_id]['calculation_type'] = $instance_settings['type'];
$classes_keys[0]['cost'] = $instance_settings['no_class_cost'];
foreach( $instance_settings as $key => $setting )
if ( strpos( $key, 'class_cost_') !== false ){
$class_id = str_replace('class_cost_', '', $key );
$classes_keys[$class_id]['cost'] = $setting;
}
$data[$zone_id]['shipping_methods'][$instance_id]['classes_&_costs'] = $classes_keys;
}
}
}
}
// Row output (for testing)
echo '<pre>'; print_r($data); echo '</pre>';
custom shipping methods
Now if you are using custom shipping methods (enabled sometimes by 3rd party shipping plugins) you will need to make some changes in the code…
Costs and taxes calculation
You should need to make the taxes calculations, as the costs are displayed just as they are set in shipping settings…
3) Product page
Customer location:
You will need first to have a location selector (to define the Shipping Zone) or to set the location based on Woocommerce geolocation.
Shipping Methods:
Once the Shipping Zone is defined, you can get the corresponding Shipping Methods and rates (costs), displaying on this product page your radio buttons for Shipping methods.
To get this, you should need to alter the single product pages:
Overriding the Woocommerce templates via your theme (and WC Subscriptions templates too)
Using the available filters and action hooks
Use Javascript, jQuery to alter prices and Ajax to update the customer data (WC_Session and WC_Customer).
You should need to get/set/update the "chosen_shipping_methods" with the following code (Ajax).
Get the Chosen Shipping method:
$chosen_shipping = WC()->session->get('chosen_shipping_methods')[0];
Set/Update the Chosen Shipping method (through Javascript/Ajax and admin-ajax.php):
// HERE the new method ID
$method_rate_id = array('free_shipping:10');
// Set/Update the Chosen Shipping method
WC()->session->set( 'chosen_shipping_methods', $method_rate_id );

Categories