Change woocommerce status based on different shipping and payment methods - php

I'm having some issues when a customer select a certain delivery option and a certain payment. At the moment we are using bacs and local payment gateways methods to collect the money, and we have local pick up and express delivery options. also, we have custom statuses for woocommerce for each delivery option. The problem occurs when someone asks for express delivery, and he wants to pay with bacs. we tried to assign custom status for each combination but the code is not working, due its selecting the same custom status to every option in the code.
add_action( 'woocommerce_thankyou', 'express_shipping_update_order_status', 10, 1 );
function express_shipping_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search_rm = 'Despacho Express Todo Santiago (Excluye Padre Hurtado. Recibe al siguiente día hábil)';
$search_estoril = 'Retiro en Tienda Estoril';
$search_vina = 'Retiro en Tienda Reñaca';
$order = wc_get_order( $order_id );
$payment_method=$order->get_payment_method();
foreach($order->get_shipping_methods() as $shipping_item ){
if( strpos( $shipping_item->get_method_title(), $search_rm ) !== false && $payment_method == "bacs"){
$order->update_status('check-payment');
break;
} else {
$order->update_status('express-rm');
break;
}
if( strpos( $shipping_item->get_method_title(), $search_estoril ) !== false && $payment_method == "bacs"){
$order->update_status('check-payment');
break;
} else {
$order->update_status('retiro-rm');
break;
}
if( strpos( $shipping_item->get_method_title(), $search_vina ) !== false && $payment_method == "bacs"){
$order->update_status('check-payment');
break;
} else {
$order->update_status('retiro-vina');
break;
}
}
}
this is the result:
is there any way to fix this? thanks!

Because you use custom shipping methods and custom order statuses, it is difficult to give an appropriate answer.
However, if you run the following code and apply the debug information in the if condition, you should get the desired result.
A hint. Build your code step by step and test it in the meantime, see debugging in WooCommerce versus multiple if and else conditions without you really being able to determine where things are going wrong
Explanation via comment tags added in the code:
function action_woocommerce_thankyou( $order_id ) {
// Get $order object
$order = wc_get_order( $order_id );
// Get payment method
$payment_method = $order->get_payment_method();
// Get shipping method
$shipping_method = $order->get_shipping_method();
// DEBUGGING PURPOSES. Delete after testing
echo 'DEBUG: Shipping method = ' . $shipping_method;
// Compare payment method
if ( $payment_method == 'bacs' ) {
// Compare shipping method
if ( $shipping_method == 'my_shipping_method_copy_pasted_from_the_debug_information' ) {
$order->update_status( 'my-custom-order-status' );
} else {
// etc..
}
}
}
add_action( 'woocommerce_thankyou', 'action_woocommerce_thankyou', 10, 1 );

Related

Set order status to "Processing" after payment for a single virtual product in WooCommerce

In WooCommerce, any virtual product order status is automatically marked as "Completed" after payment.
I need the status to be set to "Processing" after payment. This behavior should apply only to a single virtual product ID that I specify.
The closest solution that I found, for I'm looking for, is this:
Auto change order status to completed for specific products in WooCommerce
The only issue is that it sets the status to "Completed" instead of "Processing", and I need it the other way around.
function virtual_product_woocommerce_order_status( $order_id ) {
if( ! $order_id ) return;
$order = wc_get_order( $order_id );
$order_items = $order->get_items();
$is_virtual_found = false;
$order_product_ids = array();
$specific_product_id = 1342;
foreach ( $order_items as $item ) {
// Get product id
$product = wc_get_product( $item['product_id'] );
// Add the product IDS to the list
$order_product_ids[] = $item['product_id'];
// Is virtual
$is_virtual = $product->is_virtual();
// Is_downloadable
$is_downloadable = $product->is_downloadable();
// Backorders allowed
$backorders_allowed = $product->backorders_allowed();
if( $is_virtual && $is_downloadable && $backorders_allowed ) {
$is_virtual_found = true;
break;
}
}
// true
if( $is_virtaul_found && in_array($specific_product_id, $order_product_ids) ) {
$order->update_status( 'processing' );
}
}
add_action('woocommerce_thankyou', 'virtual_product_woocommerce_order_status', 10, 1 );

Disable new account email notification in WooCommerce based on current product type in cart

For a client that uses learndash, i stumbled upon a quite unhandy feature. This elearning integration requires that clients are forced to create an account in the checkout and receive an email about it. For a sold course this is good, however, this client wants to sell other things as well.
So as this plugin does not function without the forced account creation, i wanted to remove the new account email to customers when the purchased product types are not 'course'.
So far i have:
function disable_account_creation_email( $email_class ) {
$order = wc_get_order();
$product_type = '';
if (!empty($order)){
foreach ($order->get_items() as $item_key => $item){
$product = $item->get_product();
$product_type .= $product->get_type();
}
if (stripos($product_type,'course') ===false){
remove_action( 'woocommerce_created_customer_notification', array( $email_class, 'customer_new_account' ), 10, 3 );
}
}
}
add_action( 'woocommerce_email', 'disable_account_creation_email' );
However, it doesnt work.
Any suggestions?
You have multiple mistakes in your current code
You can use the woocommerce_email_enabled_customer_new_account filter hook.
Checks for checkout/cart page
This answer checks whether a product is of type simple - Replace with your own product type
Tested in WooCommerce 5.0.0 and it works, explanation via commenttags added to the code
Use this to check when 1 of the products in cart is of a certain type
function filter_woocommerce_email_enabled_customer_new_account( $enabled, $user, $email ) {
// Only run in the Cart or Checkout pages
if ( is_cart() || is_checkout() ) {
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
// Get product
$product = $cart_item['data'];
// Get product type
$product_type = $product->get_type();
// Compare
if ( $product_type == 'simple' ) {
// Enabled = false, break loop
$enabled = false;
break;
}
}
}
return $enabled;
}
add_filter( 'woocommerce_email_enabled_customer_new_account', 'filter_woocommerce_email_enabled_customer_new_account' , 10, 3 );
Or use the following if ALL products in cart should be of a certain type
function filter_woocommerce_email_enabled_customer_new_account( $enabled, $user, $email ) {
// Only run in the Cart or Checkout pages
if ( is_cart() || is_checkout() ) {
// Set flag
$flag = true;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
// Get product
$product = $cart_item['data'];
// Get product type
$product_type = $product->get_type();
// Compare
if ( $product_type != 'simple' ) {
$flag = false;
}
}
// If flag is true
if ( $flag ) {
// Enabled = false
$enabled = false;
}
}
return $enabled;
}
add_filter( 'woocommerce_email_enabled_customer_new_account', 'filter_woocommerce_email_enabled_customer_new_account' , 10, 3 );

Reduce stock only for specific order statuses and payment method in Woocommerce

I'm working on a bit of custom Woocommerce functionality for a client. They use the BACS payment gateway to handle manual payments.
However, the gateway currently reduces the stock too early for our needs, i.e., when the order is "On Hold". I would like to ONLY reduce the stock when the order is marked "Processing" or "Complete" (avoiding duplicate reductions).
I have manged to prevent the stock from reducing itself while "on hold" with the following snippet:
function wcs_do_not_reduce_onhold_stock( $reduce_stock, $order ) {
if ( $order->has_status( 'on-hold' ) ) {
$reduce_stock = false;
}
return $reduce_stock;
}
add_filter( 'woocommerce_can_reduce_order_stock', 'wcs_do_not_reduce_onhold_stock', 10, 2 );
I'm not too sure how to proceed though. While the above code works, adding the following condition does not:
else if ( $order->has_status( 'processing' ) || $order->has_status( 'completed' ) ) {
$reduce_stock = true;
}
In short, I'd ideally like the stock to change depending on the following stock statuses:
On Hold - Does nothing
Completed or Processing - Reduce Stock (Only once)
Cancelled - Increase Stock (Only if initially reduced)
Any help is much appreciated!
Using a custom function hooked in woocommerce_order_status_changed you will be able to target 'processing' and 'completed' order statuses change reducing order items stock.
I have added in your function a condition to target only "BACS" payment gateway on orders.
add_filter( 'woocommerce_can_reduce_order_stock', 'wcs_do_not_reduce_onhold_stock', 10, 2 );
function wcs_do_not_reduce_onhold_stock( $reduce_stock, $order ) {
if ( $order->has_status( 'on-hold' ) && $order->get_payment_method() == 'bacs' ) {
$reduce_stock = false;
}
return $reduce_stock;
}
add_action( 'woocommerce_order_status_changed', 'order_stock_reduction_based_on_status', 20, 4 );
function order_stock_reduction_based_on_status( $order_id, $old_status, $new_status, $order ){
// Only for 'processing' and 'completed' order statuses change
if ( $new_status == 'processing' || $new_status == 'completed' ){
$stock_reduced = get_post_meta( $order_id, '_order_stock_reduced', true );
if( empty($stock_reduced) && $order->get_payment_method() == 'bacs' ){
wc_reduce_stock_levels($order_id);
}
}
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works
The accepted answer did not work for me. The first part did but not the second, this is how I modified it:
// This is the same as the accepted answer
add_filter( 'woocommerce_can_reduce_order_stock', 'wcs_do_not_reduce_onhold_stock', 10, 2 );
function wcs_do_not_reduce_onhold_stock( $reduce_stock, $order ) {
if ( $order->has_status( 'on-hold' ) && $order->get_payment_method() == 'bacs' ) {
$reduce_stock = false;
}
return $reduce_stock;
}
// This is what I changed
add_action( 'woocommerce_order_status_processing', 'reduce_stock_on_bacs_order_status_change', 10, 1 );
add_action( 'woocommerce_order_status_completed', 'reduce_stock_on_bacs_order_status_change', 10, 1 );
function reduce_stock_on_bacs_order_status_change( $order_id ) {
// Get the order object
$order = wc_get_order( $order_id );
// Check if the order was paid using BACS
if ( 'bacs' == $order->get_payment_method() ) {
// Check if the stock reduction has already been done for this order
$stock_reduction_done = get_post_meta( $order_id, '_stock_reduction_done', true );
if ( ! $stock_reduction_done ) {
// Iterate over the order items
foreach( $order->get_items() as $item_id => $item ) {
// Reduce stock for each item
$product = $item->get_product();
$qty = $item->get_quantity();
$product->reduce_stock( $qty );
}
// Mark the stock reduction as done for this order
update_post_meta( $order_id, '_stock_reduction_done', true );
}
}
}
This will not reduce the stock for BACS payments until the order is marked as processing or completed.

WooCommerce email based on shipping zone

I need send email instructions when customer select shipping zone id = 0 (The rest of the world).
I found code below, but its based on payment method:
add_action( 'woocommerce_email_before_order_table', 'add_order_email_instructions', 10, 2 );
function add_order_email_instructions( $order, $sent_to_admin ) {
if ( ! $sent_to_admin ) {
if ( 'cod' == $order->payment_method ) {
// cash on delivery method
echo '<p><strong>Instructions:</strong> Full payment is due immediately upon delivery: <em>cash only, no exceptions</em>.</p>';
} else {
// other methods (ie credit card)
echo '<p><strong>Instructions:</strong> Please look for "Madrigal Electromotive GmbH" on your next credit card statement.</p>';
}
}
}
How can I change it to specific shipping zone please?
How can I set specific email instructions based on shipping zone in WooCommerce?
Any help on this will be appreciated.
Get the shipping zone for an order is not so easy. It can be done with the following code example:
add_action( 'woocommerce_email_before_order_table', 'custom_text_in_email_shipping_zone_based', 10, 4 );
function custom_text_in_email_shipping_zone_based( $order, $sent_to_admin, $plain_text, $email ) {
if ( ! $sent_to_admin ) {
// Get the shipping method related data (we need the Instance ID)
$shipping_item = $order->get_items('shipping');
$item = reset($shipping_item);
$shipping_method_id = $item->get_method_id();
$method_arr = explode( ':', $shipping_method_id );
// Get the Zone ID and related data
$shipping_zone_object = WC_Shipping_Zones::get_zone_by( 'instance_id', $method_arr[1] );
$zone_id = $shipping_zone_object->get_id(); // Zone ID
$zone_name = $shipping_zone_object->get_zone_name(); // Zone name
// Get the zone locations codes and types (if needed)
foreach( $shipping_zone_object->get_zone_locations() as $zone_location ){
$zone_location_code = $zone_location->code;
$zone_location_type = $zone_location->type;
}
if ( '0' == $zone_id ) {
// Rest of the world
echo '<p><strong>Instructions:</strong> for Rest of the world.</p>';
}
elseif ( 'Mexico' == $zone_name ) {
// Mexico zone name
echo '<p><strong>Instructions:</strong> for Mexico.</p>';
}
else {
// All other zones
echo '<p><strong>Instructions:</strong> Other zones.</p>';
}
}
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works.
According to the WC_Abstract_Order codex page https://docs.woocommerce.com/wc-apidocs/class-WC_Abstract_Order.html#_has_shipping_method you can check the shipping method using has_shipping_method().
So, your if statement should be something like
if ( $order->has_shipping_method('name_of_my_shipping_method') ) {
I couldn't find an equivalent for shipping zones though.
Hope that helps
add_action( 'woocommerce_email_before_order_table', 'custom_text_in_email_shipping_zone_based', 10, 4 );
function custom_text_in_email_shipping_zone_based( $order, $sent_to_admin, $plain_text, $email ) {
if ( ! $sent_to_admin ) {
// Get the shipping method related data (we need the Instance ID)
$shipping_item = $order->get_items('shipping');
$item = reset($shipping_item);
$shipping_method_id = $item->get_method_id();
$method_arr = explode( ':', $shipping_method_id );
// Get the Zone ID and related data
$shipping_zone_object = WC_Shipping_Zones::get_zone_by( 'instance_id', $method_arr[1] );
$zone_id = $shipping_zone_object->get_id(); // Zone ID
$zone_name = $shipping_zone_object->get_zone_name(); // Zone name
// Get the zone locations codes and types (if needed)
foreach( $shipping_zone_object->get_zone_locations() as $zone_location ){
$zone_location_code = $zone_location->code;
$zone_location_type = $zone_location->type;
}
if ( '0' == $zone_id ) {
// Rest of the world
echo '<p><strong>Instructions:</strong> for Rest of the world.</p>';
}
elseif ( 'Mexico' == $zone_name ) {
// Mexico zone name
echo '<p><strong>Instructions:</strong> for Mexico.</p>';
}
else {
// All other zones
echo '<p><strong>Instructions:</strong> Other zones.</p>';
}
}
}
The code showing above doesn't work even though the author has stated tried and tested, can anybody update the code.
No matter what zone you select you always end up with All other zones.

Disable all payments gateway if there's specifics products in the Cart

I would like to disable all payments gateways under special situation: I've 2 special products that I don't want to be combined at checkout with any other product.
Lets say that my "special" products IDs are 496 and 484. All other are "normal" products.
if one of these "special" products is in the cart, I want to disable "paypal" for example.
if a customer has in his cart, at once, a "special" product and a "normal" product, I want to disable all the payments gateway, so he can't checkout.
This is my code:
//disable add to cart if
add_filter( 'woocommerce_available_payment_gateways', 'filter_gateways', 1);
function filter_gateways( $gateways )
{
global $woocommerce;
foreach ($woocommerce->cart->cart_contents as $key => $values ) {
// store product IDs in array
$nonPPproducts = array(496,484);
if (in_array( $values['product_id'], $nonPPproducts ) ) {
unset($gateways['cod'], $gateways['bacs'], $gateways['cheque'], $gateways['stripe']);
} elseif ( in_array( $values['product_id'], $nonPPproducts ) && in_array( $values['product_id'] ) ) {
unset($gateways['under-review'], $gateways['cod'], $gateways['bacs'], $gateways['cheque'], $gateways['stripe']);
}
}
return $gateways;
}
But I can't figure out why the only first if statement works… In other words whatever the situation, all payment gateways are disabled except under-review payment gateway.
What I am doing wrong?
How can I achieve this?
Thanks
Updated for WooCommerce 3+
First I think that in_array( $values['product_id'] ) in your code is not working as a correct condition and so your else statement is never "true". Then as a customer can have many items in his cart, depending on customer successive choices, with your code there will be many redundant gateway unsets…
Here it is your code revisited (you will need to put the desire unset gateways in each statement):
add_filter( 'woocommerce_available_payment_gateways', 'filter_gateways', 1);
function filter_gateways( $gateways ){
// Not in backend (admin)
if( is_admin() )
return $gateways;
// Storing special product IDs in an array
$non_pp_products = array( 496, 484 );
// Needed variables
$is_non_prod = false;
$is_prod = false;
$count = 0;
foreach ( WC()->cart->get_cart() as $cart_item ) {
// count number of items if needed (optional)
$count++;
$product = $cart_item['data'];
if( ! empty($product) ){
$product_id = method_exists( $product, 'get_id' ) ? $product->get_id() : $product->id;
if ( in_array( $product_id, $non_pp_products ) && ! $is_non_prod )
$is_non_prod = true;
if ( !in_array( $product_id, $non_pp_products ) && !$is_prod )
$is_prod = true;
}
}
if ( $is_non_prod && ! $is_prod ) // only special products
{
// unset only paypal;
unset( $gateways['paypal'] );
}
elseif ( $is_non_prod && $is_prod ) // special and normal products mixed
{
// unset ALL GATEWAYS
unset( $gateways['cod'],
$gateways['bacs'],
$gateways['cheque'],
$gateways['paypal'],
$gateways['stripe'],
$gateways['under-review'] );
}
elseif ( ! $is_non_prod && $is_prod ) // only normal products (optional)
{
// (unset something if needed)
}
return $gateways;
}
Naturally this code goes on functions.php file of your active child theme or theme.

Categories