I am using Change Woocommerce Order Status based on Shipping Method code and it works beautifully for re-assigning my custom order status "awaiting-pickup" in WooCommerce based on shipping method string.
Here is my code:
add_action( 'woocommerce_thankyou', 'shipping_method_update_order_status', 10, 1 );
function shipping_method_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search = 'local_pickup'; // The needle to search in the shipping method ID
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// When "pickup" method is used, we change the order to "awaiting-pickup" status
if( strpos( $shipping_item->get_method_title(), $search ) !== false ){
$order->update_status('awaiting-pickup');
$order->save();
break;
}
}
}
I need help extending this to apply a few different rules based on other shipping methods like for 'free_shipping' and 'flat_rate' that I would like to reassign as 'awaiting-delivery' too.
$search = 'flat_rate' OR 'free_shipping';
$order->update_status('awaiting-delivery');
The shipping instances are structured like so:
'local_pickup:2'
'local_pickup:5'
'local_pickup:7'
'local_pickup:10'
'flat_rate:3'
'flat_rate:6'
'flat_rate:9'
'free_shipping:11'
'free_shipping:12'
'free_shipping:13'
Every time I create a new shipping zone extra shipping instances that are attached to that zone will have new numbers attached the method type. Ultimately I need something that use the following logic:
IF 'local_pickup' IN string
THEN $order->update_status('awaiting-pickup');
ELSEIF 'flat_rate' OR 'free_shipping' IN string
THEN $order->update_status('awaiting-delivery');
END
Update 2
As you are using the real shipping method Id in here, you don't need to search for a string. Then it will be more simple to make it work for multiple shipping methods Ids as follows:
add_action( 'woocommerce_thankyou', 'shipping_method_update_order_status', 10, 1 );
function shipping_method_update_order_status( $order_id ) {
if ( ! $order_id ) return;
// Here define your shipping methods Ids
$shipping_methods_ids_1 = array('local_pickup');
$shipping_methods_ids_2 = array('flat_rate', 'free_shipping');
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// For testing to check the shipping method slug (uncomment the line below):
// echo '<pre>'. print_r( $shipping_item->get_method_id(), true ) . '</pre>';
// When "Local pickup" method is used, we change the order to "awaiting-pickup" status
if( in_array( $shipping_item->get_method_id(), $shipping_methods_ids_1 ) && ! $order->has_status('awaiting-pickup') ){
$order->update_status('awaiting-pickup'); // Already use internally save() method
break; // stop the loop
}
// When 'Flat rate' or 'Free shipping' methods are used, we change the order to "awaiting-delivery" status
elseif( in_array( $shipping_item->get_method_id(), $shipping_methods_ids_2 ) && ! $order->has_status('awaiting-delivery') ){
$order->update_status('awaiting-delivery'); // Already use internally save() method
break; // stop the loop
}
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Assuming new orders are always given the status of PROCESSING, then this is how I would do it:
add_action('woocommerce_order_status_changed', 'jds_auto_change_status_by_shipping_method');
function jds_auto_change_status_by_shipping_method($order_id) {
// If the status of an order is changed to PROCESSING and the shipping method contains specific text then change the status.
if ( ! $order_id ) {
return;
}
global $product;
$order = wc_get_order( $order_id );
if ($order->data['status'] == 'processing') { // if order status is processing
$shipping_method = $order->get_shipping_method();
if ( strpos($shipping_method, 'local_pickup') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-pickup'); // change status
} else if ( strpos($shipping_method, 'flat_rate') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-delivery'); // change status
} else if ( strpos($shipping_method, 'free_shipping') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-delivery'); // change status
}
}
}
I will note that the $shipping_method returns the Human Readable text that the customer sees on the website when they checkout so you may need to adjust local_pickup to match the exact text the customer sees (like 'Local Pickup').
Related
I've used a Business Bloomer snippet and tweaked it a bit to unset some shipping methods depending on a shipping class of items in the cart.
It works fine with the 4 shipping methods I have tested with but in order to fully work on my website, I have to list all shipping methods values manually and I have A LOT ( 86 to unset in if and 60 in else.)
Therefore, I would like to edit the snipped so I could unset all shipping methods that contains the same term all at once rather than look for each value individually, but don't really know how to.
I have prints for sale and so I’ve set some shipping methods for orders including prints and some for orders whitout. Therefore all my shipping methods for prints have a value ending with “_print” when the others have a value ending with “_classique”, as you can see in the code bellow.
My goal is to disable all shipping methods ending with “_classique” when a product with the “print” shipping class in the cart and vice versa.
So far my code looks like this :
add_filter( 'woocommerce_package_rates', 'businessbloomer_hide_regular_shipping_method', 10, 2 );
function businessbloomer_hide_regular_shipping_method( $rates, $package ) {
$shipping_class_target = 35; // shipping class ID
$in_cart = false;
foreach( WC()->cart->get_cart_contents() as $key => $values ) {
if( $values[ 'data' ]->get_shipping_class_id() == $shipping_class_target ) {
$in_cart = true;
break;
}
}
if( $in_cart ) { // shipping method with value
unset( $rates['wbs:2:d6f790a0_colissimo_sans_signature_classique'] );
unset( $rates['wbs:2:d748dcd4_lettre_suivie_classique'] );
unset( $rates['wbs:2:f1058bc8_colissimo_avec_signature_classique'] );
}
else{
unset( $rates['wbs:2:1cdf4913_colissimo_sans_signature_print'] );
unset( $rates['wbs:2:fghla482_lettre_suivie_print'] );
unset( $rates['wbs:2:g27a1f56_colissimo_avec_signature_print'] );
}
return $rates;
}
Any help on how to achieve this would be very appreciated.
Thank you to anyone who'll take time to read this !
Updated
Using PHP strpos() will allow you to check if a word is contained in a string. Then you will need to make some changes in your code to make it work:
add_filter( 'woocommerce_package_rates', 'show_hide_shipping_methods', 10, 2 );
function show_hide_shipping_methods( $rates, $package ) {
$shipping_class_id = 35; // Targeted shipping class ID
$found = false;
// Loop through cart items for the current package
foreach ( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class_id() == $shipping_class_id ) {
$found = true;
break;
}
}
// Loop through shipping rates
foreach ( $rates as $rate_key => $rate ) {
if ( $found && strpos($rate_key, '_classique') !== false ) {
unset($rates[$rate_key]);
}
elseif (! $found && strpos($rate_key, '_print') !== false ) {
unset($rates[$rate_key]);
}
}
return $rates;
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Clearing shipping caches:
You will need to empty your cart, to clear cached shipping data
Or In shipping settings, you can disable / save any shipping method, then enable back / save.
Related:
How can I check if a word is contained in another string using PHP?
Hide shipping methods for specific shipping class in WooCommerce
Filter Shipping method based on shipping class in Woocommerce 3
I am running a function whereby I check if a user has a refunded item/s on their previous order and if they do then apply a credit as a negative cart fee at checkout. The function is working for users who have placed an order before but is causing a critical error on the site for new users.
Fatal error: Uncaught Error: Call to a member function get_refunds() on bool in /wp-content/themes/my-theme/refunds.php:20 Stack trace:
#0 /wp-includes/class-wp-hook.php(287): add_last_order_refund_total_at_checkout(Object(WC_Cart))
#1 /wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters('', Array)
#2 /wp-includes/plugin.php(478): WP_Hook->do_action(Array)
#3 /wp-content/plugins/woocommerce/includes/class-wc-cart.php(1714): do_action('woocommerce_car...', Object(WC_Cart))
#4 /wp-content/plugins/woocommerce/includes/class-wc-cart-totals.php(270): WC_Cart->calculate_fees()
#5 /wp-content/plugins/woocommerce/includes/class-wc-cart-totals.php(829): WC_Cart_Totals->get_fees_from_cart()
#6 /wp-content/plugins/woocommerce/includes/class-wc- in /wp-content/themes/my-theme/refunds.php on line 20
Here is the code to check for any refunds on the previous order:
//Check total of refunded items from last order - add as a fee at checkout
function add_last_order_refund_total_at_checkout($cart_object){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$user_id = get_current_user_id(); // The current user ID
$customer = new WC_Customer( $user_id );
$last_order = $customer->get_last_order();
$order_id = $last_order;
// Get the WC_Order Object instance (from the order ID)
$order = wc_get_order( $order_id );
// Get the Order refunds (array of refunds)
$order_refunds = $order->get_refunds();
$total_to_refund = $order->get_total_refunded()*(-1);//WIP need to check which items have tax
if (!empty($order_refunds)) WC()->cart->add_fee( 'Refund', $total_to_refund );
}
add_action( 'woocommerce_cart_calculate_fees', 'add_last_order_refund_total_at_checkout', 10, 1 );
I believe I need to first check to see if a user has any previous orders otherwise the get_refund() is causing an error as their aren't any orders to check? How would I safely do this?
If you look to WC_Customer get_last_order() method documentation or source code, you will see:
/*
* #return WC_Order|false
*/
which means that get_last_order() method can return alternatively:
the WC_Order object
or false boolean value.
So you can just use in your code:
$last_order = $customer->get_last_order();
if ( ! $last_order ) {
return; // Exit (Not an order)
}
To avoid this error.
Now you can use is_a() php conditional functions to check that a variable is a from a specific Object class and not something else like:
$last_order = $customer->get_last_order();
if ( ! is_a( $last_order, 'WC_Order' ) ) {
return; // Exit (Not an order)
}
// Your other code…
Or you can use method_exists() php conditional functions for WC_Order get_refunds() method, to check that the a variable is a from a specific Object class and not something else:
$last_order = $customer->get_last_order();
if ( ! method_exists( $last_order, 'get_refunds' ) ) {
return; // Exit (Not an order)
}
// Your other code…
The three cases work nicely, avoiding an error
Give it a try this way, multiple controls have been added, see comments
// Check total of refunded items from last order - add as a fee at checkout
function add_last_order_refund_total_at_checkout( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// The current user ID
$user_id = get_current_user_id();
// User id exists
if ( $user_id > 0 ) {
$customer = new WC_Customer( $user_id );
// Get last order
$last_order = $customer->get_last_order();
// True
if ( $last_order ) {
// Get the order id
$order_id = $last_order->get_id();
// Order ID exists
if ( is_numeric( $order_id ) ) {
// Get the WC_Order Object instance (from the order ID)
$order = wc_get_order( $order_id );
// Get the Order refunds (array of refunds)
$order_refunds = $order->get_refunds();
// NOT empty
if ( ! empty( $order_refunds) ) {
// WIP need to check which items have tax
$total_to_refund = $order->get_total_refunded();
// Add fee
$cart->add_fee( 'Refund', $total_to_refund );
}
}
}
}
}
add_action( 'woocommerce_cart_calculate_fees', 'add_last_order_refund_total_at_checkout', 10, 1 );
On Woocommerce, I have enabled 2 shipping methods: Free shipping or Flat rate. I have enabled 2 payment methods: Bank transfer (bacs) and PayPal (paypal).
What I want to achieve:
If a customer selects PayPal as payment type he should be forced to select "Flat rate" as shipping method. "Free shipping" should be either hidden or greyed out or something like that.
If bank transfer is chosen then both shipping methods should be available.
Any help is appreciated.
If anyone is interested, I found a solution:
function alter_payment_gateways( $list ){
// Retrieve chosen shipping options from all possible packages
$chosen_rates = ( isset( WC()->session ) ) ? WC()->session->get( 'chosen_shipping_methods' ) : array();
if( in_array( 'free_shipping:1', $chosen_rates ) ) {
$array_diff = array('WC_Gateway_Paypal');
$list = array_diff( $list, $array_diff );
}
return $list;
}
add_action('woocommerce_payment_gateways', 'alter_payment_gateways');
This code will deactivate PayPal if a customer selects free shipping.
Update 2: The following code will disable "free_shipping" shipping method (method ID) when "paypal" is the chosen payment method:
add_filter( 'woocommerce_package_rates', 'shipping_methods_based_on_chosen_payment', 100, 2 );
function shipping_methods_based_on_chosen_payment( $rates, $package ) {
// Checking if "paypal" is the chosen payment method
if ( WC()->session->get( 'chosen_payment_method' ) === 'paypal' ) {
// Loop through shipping methods rates
foreach( $rates as $rate_key => $rate ){
if ( 'free_shipping' === $rate->method_id ) {
unset($rates[$rate_key]); // Remove 'Free shipping'shipping method
}
}
}
return $rates;
}
// 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('chosen_payment_method' ) ) $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();
}
// Jquery script for checkout page
add_action('wp_footer', 'refresh_checkout_on_payment_method_change' );
function refresh_checkout_on_payment_method_change() {
// Only checkout page
if( is_checkout() && ! is_wc_endpoint_url() ):
?>
<script type="text/javascript">
jQuery(function($){
// On shipping method change
$('form.checkout').on( 'change', 'input[name^="payment_method"]', function(){
$('body').trigger('update_checkout'); // Trigger Ajax checkout refresh
});
})
</script>
<?php
endif;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
To get the related shipping methods rate IDs, something like flat_rate:12, inspect with your browser code inspector each related radio button attribute name like:
Note: Since WooCommerce new versions changes, sorry, the code doesn't work anymore.
Essentially I'm trying to make the flat rate method Id flat_rate:7 disabled when there is cart items that have the shipping class "Roller" (ID 92).
This is the code I tried:
add_filter('woocommerce_package_rates', 'wf_hide_shipping_method_based_on_shipping_class', 10, 2);
function wf_hide_shipping_method_based_on_shipping_class($available_shipping_methods, $package)
{
$hide_when_shipping_class_exist = array(
92 => array(
'flat_rate:7'
)
);
$shipping_class_in_cart = array();
foreach(WC()->cart->cart_contents as $key => $values) {
$shipping_class_in_cart[] = $values['data']->get_shipping_class_id();
}
foreach($hide_when_shipping_class_exist as $class_id => $methods) {
if(in_array($class_id, $shipping_class_in_cart)){
foreach($methods as & $current_method) {
unset($available_shipping_methods[$current_method]);
}
}
}
return $available_shipping_methods;
}
Shipping class ID 92 is the shipping class and I want to hide flat_rate:7 for it.
My Site is this: http://www.minimoto.me/
WordPress: 4.8.4
WooCommerce: 3.1.1
Any help will be greatly appreciated.
Update 2019: You should try instead this shorter, compact and effective way:
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping method to hide
$method_key_id = 'flat_rate:7';
// Checking in cart items
foreach( $package['contents'] as $item ){
// If we find the shipping class
if( $item['data']->get_shipping_class_id() == $class ){
unset($rates[$method_key_id]); // Remove the targeted method
break; // Stop the loop
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works.
Sometimes, you should may be need to refresh shipping methods going to shipping areas, then disable / save and re-enable / save your "flat rates" shipping methods.
Related: Hide shipping methods for specific shipping classes in WooCommerce
To find the shipping methods IDs and the shipping classes IDs see below…
Update for many different shipping methods (related to your comments):
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping methods you want to hide
$method_key_ids = array('flat_rate:7', 'local_pickup:3');
// Checking in cart items
foreach( $package['contents'] as $item ) {
// If we find the shipping class
if( $item['data']->get_shipping_class_id() == $class ){
foreach( $method_key_ids as $method_key_id ){
unset($rates[$method_key_id]); // Remove the targeted methods
}
break; // Stop the loop
}
}
return $rates;
}
Tested and works…
Finding the shipping class ID.
In the database under wp_terms table:
Search for a term name or a term slug and you will get the term ID (the shipping class ID).
On Woocommerce shipping settings editing a "Flat rate", with your browser html inspector tool, inspect a shipping Class rate field like:
In the imput name attribute you have woocommerce_flat_rate_class_cost_64. So 64 is the ID for the shipping class.
Get the shipping method rate ID:
To get the related shipping methods rate IDs, something like flat_rate:12, inspect with your browser code inspector each related radio button attribute value like:
By tweaking LoicTheAztec's code (cheers), I was able to unset a shipping method for each package based on the shipping class of its contents, rather than the cart as a whole. Perhaps it will help someone else too:
// UNSET A SHIPPING METHOD FOR PACKAGE BASED ON THE SHIPPING CLASS(es) OF ITS CONTENTS
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
foreach( $package['contents'] as $package_item ){ // Look at the shipping class of each item in package
$product_id = $package_item['product_id']; // Grab product_id
$_product = wc_get_product( $product_id ); // Get product info using that id
if( $_product->get_shipping_class_id() != 371 ){ // If we DON'T find this shipping class ID
unset($rates['wbs:9:dae98e94_free_ups_ground']); // Then remove this shipping method
break; // Stop the loop, since we've already removed the shipping method from this package
}
}
return $rates;
}
This code allows me to unset my 'Free UPS Ground' shipping if the package contains anything but 'Standard' items (shipping_class_id 371 in my case).
The scenario from the original post (disable method x if shipping class y) would work like this:
// UNSET A SHIPPING METHOD FOR PACKAGE BASED ON THE SHIPPING CLASS(es) OF ITS CONTENTS
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
foreach( $package['contents'] as $package_item ){ // Look at the shipping class of each item in package
$product_id = $package_item['product_id']; // Grab product_id
$_product = wc_get_product( $product_id ); // Get product info using that id
if( $_product->get_shipping_class_id() == 92 ){ // If we DO find this shipping class ID
unset($rates['flat_rate:7']); // Then remove this shipping method
break; // Stop the loop, since we've already removed the shipping method from this package
}
}
return $rates;
}
The idea here is that when an order comes in with an "express delivery" as Shipping Method, the order status is updated to On-Hold.
As there I have some different "express delivery" Shipping Method rates I thought that by using stristr() to see if the word 'express' appears anywhere in the formatted shipping method title. But I seem to be missing something as I don't get anything.
How can I check if the Order shipping method is an "express delivery" to be able to update the order status?
Here is the code that I have:
add_action( 'woocommerce_thankyou', 'express_orders_4865', 10, 1 );
function express_orders_4865( $order_id ) {
global $woocommerce;
$order = new WC_Order( $order_id );
$shipping_method = $order->get_shipping_method();
if (stristr($shipping_method, 'express') === TRUE) {
$order->update_status('on-hold');
} else {
return;
}
}
EDIT-----------------------------------------------------------
For anyone using Woocommerce Table Rate Shipping the get_method_id returns the table rate id so i used get_method_title instead as below, if there is a better way please comment...
add_action( 'woocommerce_thankyou', 'express_shipping_update_order_status', 10, 1 );
function express_shipping_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search = 'Express'; // The needle to search in the shipping method ID
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// When "express delivery" method is used, we change the order to "on-hold" status
if( strpos( $shipping_item->get_method_title(), $search ) !== false ){
$order->update_status('on-hold');
break;
}
}
}
I prefer to use the faster and less memory intensive function strpos() instead as the shipping method ID is alway in lowercase (like a kind of slug).
So is better the get the WC_Order_Item_Shipping object data for this case, using the available methods.
So the code should be:
add_action( 'woocommerce_thankyou', 'express_shipping_update_order_status', 10, 1 );
function express_shipping_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search = 'express'; // The needle to search in the shipping method ID
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// When "express delivery" method is used, we change the order to "on-hold" status
if( strpos( $shipping_item->get_method_title(), $search ) !== false && ! $order->has_status('on-hold')){
$order->update_status('on-hold');
break;
}
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works…
So the code above didn't work for me, i had someone in a FB group help me debug it and this was the final one that worked for me
add_action( 'woocommerce_thankyou', 'express_shipping_update_order_status', 10, 1 );
function express_shipping_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search = 'express'; // The needle to search in the shipping method ID
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// When "express delivery" method is used, we change the order to "on-hold" status
if( strpos( $shipping_item->get_method_title(). $search ) !== false ){
$order->update_status('on-hold');
$order->save();
break;
}
}
}
My solution assumes that the normal status for a new order is PROCESSING.
So when an order is changed to PROCESSING, check the shipping method and if it matches, then change it to ON-HOLD.
add_action('woocommerce_order_status_changed', 'jds_auto_change_status_by_shipping_method');
function jds_auto_change_status_by_shipping_method($order_id) {
// If the status of an order is changed to PROCESSING and the shipping method contains specific text then change the status.
if ( ! $order_id ) {
return;
}
global $product;
$order = wc_get_order( $order_id );
if ($order->data['status'] == 'processing') { // if order status is processing
$shipping_method = $order->get_shipping_method();
if ( strpos($shipping_method, 'express') !== false ) { // if shipping method CONTAINS this text
$order->update_status('on-hold'); // change status to this
}
}
}
Note that $shipping_method returns the human readbale version of the shipping method that the customer sees, so you need to match exactly how the word 'express' appears to customer... is it 'express' or 'Express' or 'EXPRESS'