One of the Payment Gateways I use changes the Order status from "Processing" to "Failed" whenever the transaction fails or user cancels the transaction. When the customer later tries to pay for a failed order the payment gateway throws an error "Duplicate Order ID." So to avoid this issue I want to auto-delete failed orders as and when they occur.
There is a similar question on this site but the solution given there doesn't seem to work.
The code mentioned there look like this.
<?php
function update_order_status( $order_id ) {
$order = new WC_Order( $order_id );
$order_status = $order->get_status();
if ('cancelled' == $order_status || 'failed' == $order_status || 'pending' == $order_status ) {
wp_delete_post($order_id,true);
}
}
You could use this custom function hooked in woocommerce_order_status_changed action hook, that is triggered when order status change.
You will need to set in the function the related payment gateway (the Gateway ID)…
This function will detect order status changes (for this defined payment gateway) when status change to "cancelled" and from "processing" to "failed" statuses.
So for this particular payment gateway and this particular order status changes, all data related to the current order will be completely erased from database.
In Woocommerce all orders submitted to payment gateways start with a "pending" status, so we will not use it.
The code:
add_action( 'woocommerce_order_status_changed', 'auto_destroy_failed_orders', 10, 4 );
function auto_destroy_failed_orders( $order_id, $old_status, $new_status, $order ){
// HERE set your payment Gateway ID (look in WC settings > checkout to get the Gateway ID)
$gateway_id = 'paypal';
if ( $order->get_payment_method() != $gateway_id ) return; // Only for this payment gateway
if ( ( $old_status == 'processing' && $new_status == 'failed' ) || $new_status == 'cancelled' ) {
wp_delete_post( $order_id, true );
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works.
Inconvenient: When order get destroyed on order status change in edit-order backend, you will be redirected to posts list page, instead to be redirected to the order edit page as it doesn't exist anymore… I have tried to make a delay using wp_schedule_single_event(), but I cant get it working with it…
Related
I am trying to send an email on success order so I was using until now the woocommerce_thankyou hook which seems to work perfect. But I found out that it is triggerred even when a customer tries to pay to external payment gateway (credit card payment), even though the payment is not accepted by the bank.
Which hook can I use in order to cover all these cases?
Bank Transfer, COD, Credit cart (only in succesfull payment)?
On successful paid orders for all payment gateways others than Bank wire, cheque or Cash on delivery, you can use dedicated woocommerce_payment_complete hook located in WC_Order payment_complete() method instead of more generic hook woocommerce_thankyou, like:
add_action( 'woocommerce_payment_complete', 'action_payment_complete', 10, 2 );
function action_payment_complete( $order_id, $order ) {
// Here add your code
}
Note that you can use defined $order_id and $order function arguments. Also this hook is only triggered once, avoiding repetitions.
For Bank wire (bacs), Cheque (cheque) or Cash on delivery (cod) payment methods, As the shop manager confirm manually that order is paid by changing order status, you can use the dedicated hook woocommerce_order_status_changed as follows.
add_action( 'woocommerce_order_status_changed', 'bacs_cheque_cod_payment_complete', 10, 4 );
function bacs_cheque_cod_payment_complete( $order_id, $old_status, $new_status, $order ) {
// 1. For Bank wire and cheque payments
if( in_array( $order->get_payment_method(), array('bacs', 'cheque')
&& in_array( $new_status, array('processing', 'completed')
&& ! $order->get_date_paid('edit') ) {
// Do something
}
// 2. For Cash on delivery payments
if( 'cod' === $order->get_payment_method() && 'completed' === $new_status ) {
// Do something
}
}
Note that you can use defined $order_id and $order function arguments. Also this hook will be triggered once, on order status change, avoiding repetitions.
Related: After a successful payment, What hook is triggered in Woocommerce
Is it possible to set all new orders to on hold without them going to processing first?
I need to capture the funds, but the order needs to go as on-hold rather than processing so that I can make sure the payment is received before triggering the email to our supplier.
I have found some code that allows me to change all new orders to on-hold, however when payment is authorized the order is set to processing.
pending payment --> processing (triggers email) --> on-hold
Screenshot of Order Notes that show this
This is the code that I found:
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_onhold_order' );
function custom_woocommerce_auto_onhold_order( $order_id ) {
global $woocommerce;
if ( !$order_id )
return;
$order = new WC_Order( $order_id );
$order->update_status( 'on-hold' ); //All new orders go to "on-hold"
}
I've been looking for the action which triggers the order status to be set to processing on payment authorization however I cannot find it.
All help is greatly appreciated!
Edit: I've managed to stumble upon a snippet of code that appears to work.
add_filter( 'woocommerce_payment_complete_order_status', 'custom_update_order_status', 10, 2 );
function custom_update_order_status( $order_status, $order_id ) {
return 'on-hold';
}
All orders start with a "pending" status in Woocommerce. That status is set before they go through the payment gateway...
There is no email notifications for "Pending" orders staus
You can try the following, that will set the status "on-hold" on order creation (The update status action comes once the order has been created ans saved in database):
add_action( 'woocommerce_checkout_create_order', 'force_new_order_status', 20, 1 );
function force_new_order_status( $order ) {
if( ! $order->has_status('on-hold') )
$order->set_status( 'on-hold', 'Forced status by a custom script' );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
When a woocommerce order is created the status of the order is "processing". I need to change the default order-status to "pending".
How can I achieve this?
The default order status is set by the payment method or the payment gateway.
You could try to use this custom hooked function, but it will not work (as this hook is fired before payment methods and payment gateways):
add_action( 'woocommerce_checkout_order_processed', 'changing_order_status_before_payment', 10, 3 );
function changing_order_status_before_payment( $order_id, $posted_data, $order ){
$order->update_status( 'pending' );
}
Apparently each payment method (and payment gateways) are setting the order status (depending on the transaction response for payment gateways)…
For Cash on delivery payment method, this can be tweaked using a dedicated filter hook, see:
Change Cash on delivery default order status to "On Hold" instead of "Processing" in Woocommerce
Now instead you can update the order status using woocommerce_thankyou hook:
add_action( 'woocommerce_thankyou', 'woocommerce_thankyou_change_order_status', 10, 1 );
function woocommerce_thankyou_change_order_status( $order_id ){
if( ! $order_id ) return;
$order = wc_get_order( $order_id );
if( $order->get_status() == 'processing' )
$order->update_status( 'pending' );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works
Note: The hook woocommerce_thankyou is fired each time the order received page is loaded and need to be used with care for that reason...
Now the function above will update the order status only the first time. If customer reload the page, the condition in the IF statement will not match anymore and nothing else will happen.
Related thread: WooCommerce: Auto complete paid Orders (depending on Payment methods)
Nowadays, if the payment gateway that you use properly sets the order status using WC_Order->payment_complete(), you can use the woocommerce_payment_complete_order_status filter.
This is better than using woocommerce_thankyou hook since we are setting the order status immediately, rather than applying it after it has been already set.
function h9dx3_override_order_status($status, $order_id, $order) {
if ($status === 'processing') {
$status = 'pending';
}
return $status;
}
add_filter('woocommerce_payment_complete_order_status', 'h9dx3_override_order_status', 10, 3);
Again, this will only work if the payment gateway uses the proper payment_complete wrapper method rather than setting status directly with set_status. You can just search the gateway code for 'payment_complete(' and 'set_status(' to see what it does.
If you develop a plugin for everyone, you will be better off with using woocommerce_thankyou, or you could use a combined approach and use woocommerce_thankyou as the fallback if the order status was not updated.
The hook woocommerce_thankyou suffers from the problem that you can pay for an order and then close the browser or go somewhere else and therefore never click "Return to Merchant" (which is displayed after paying via paypal) to get to the thankyou page. And then the code will never be executed.
// Rename order status 'Processing' to 'Order Completed' in admin main view - different hook, different value than the other places
add_filter( 'wc_order_statuses', 'wc_renaming_order_status' );
function wc_renaming_order_status( $order_statuses ) {
foreach ( $order_statuses as $key => $status ) {
if ( 'wc-processing' === $key )
$order_statuses['wc-processing'] = _x( 'Order Completed', 'Order status', 'woocommerce' );
}
return $order_statuses;
}
In Woocommerce, I would like to hide "paypal" gateway on the "checkout" page before the order is created for the first time and just show "cash on delivery" gateway (labeled as Reserve).
On the other hand, on checkout/order-pay page when the order status is "pending", hide the 'Reserve' gateway and show "paypal". (this happens when we change the status of the order to "pending" manually and send the invoice to the customer with a payment link).
I thought it should be done by checking order status and using the woocommerce_available_payment_gateways filter hook. But I have problems with getting current order status.
Also I'm not sure what's the status of a newly created order which the user is on the checkout page and still the order is not shown in the admin backend.
This is my incomplete code:
function myFunction( $available_gateways ) {
// How to check if the order's status is not pending payment?
// How to pass the id of the current order to wc_get_order()?
$order = wc_get_order($order_id);
if ( isset($available_gateways['cod']) && /* pending order status?? */ ) {
// hide "cod" gateway
} else {
// hide "paypal" gateway
}
return $available_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'myFunction' );
I also tried WC()->query_vars['order'] instead of wc_get_order(); to get the current order and check its status but it didn't work too.
I saw woocommerce_order_items_table action hook but couldn't get the order either.
How could I retrieve the Id and the status of the order on the checkout/order-pay page?
Update 2021
If I have correctly understood, You want to set/unset your available payment gateways, depending on the live generated order which status has to pending to have the "paypal" gateway. Ian all other cases the available gateway is only "reserve" (renamed "cod" payment gateway).
This code retrieve the live order ID using the get_query_var(), this way:
add_filter( 'woocommerce_available_payment_gateways', 'custom_available_payment_gateways' );
function custom_available_payment_gateways( $available_gateways ) {
// Not in backend (admin)
if( is_admin() )
return $available_gateways;
if ( is_wc_endpoint_url( 'order-pay' ) ) {
$order = wc_get_order( absint( get_query_var('order-pay') ) );
if ( is_a( $order, 'WC_Order' ) && $order->has_status('pending') ) {
unset( $available_gateways['cod'] );
} else {
unset( $available_gateways['paypal'] );
}
} else {
unset( $gateways['paypal'] );
}
return $available_gateways;
}
Code goes in functions.php file of your active child theme (or theme) or also in any plugin file.
The code is tested and works.
I have integrated a payment gateway to accept online payments for my store running on woocommerce. Everything works fine but I noticed that woocommerce is changing the order status to wc-processing for all the online paid orders by default.
As per my store's functionality I want all the online paid orders to be in wc-on-hold status initially.
Is there any way to stop woocommerce changing the order status to wc-processing programatically?
Here it is a code snippet based on this thread. We use here woocommerce_thankyou (that is fired just after payment has been done) to hook our function, converting 'processing' orders status to 'on-hold':
add_action( 'woocommerce_thankyou', 'custom_woocommerce_paid_order_status', 10, 1 );
function custom_woocommerce_paid_order_status( $order_id ) {
if ( ! $order_id ) {
return;
}
global $woocommerce;
$order = new WC_Order( $order_id );
// 'processing' orders status are converted to 'on-hold'.
if ( is_object($order) && $order->has_status( 'processing' ) {
$order->update_status( 'on-hold' );
}
return;
}
You can also target in your conditions the payment gateways for example here we bypass 3 payment gateways and target a specific payment gateway using "your_payment_gateway" slug:
add_action( 'woocommerce_thankyou', 'custom_woocommerce_paid_order_status', 10, 1 );
function custom_woocommerce_paid_order_status( $order_id ) {
if ( ! $order_id ) {
return;
}
global $woocommerce;
$order = new WC_Order( $order_id );
// Bypass orders with Bank wire, Cash on delivery and Cheque payment methods.
if ( ( get_post_meta($order->id, '_payment_method', true) == 'bacs' ) || ( get_post_meta($order->id, '_payment_method', true) == 'cod' ) || ( get_post_meta($order->id, '_payment_method', true) == 'cheque' ) ) {
return;
}
// Target your "your_payment_gateway_slug" with this conditional
if ( is_object($order) && get_post_meta($order->id, '_payment_method', true) == 'your_payment_gateway_slug' && $order->has_status( 'processing' ) ) {
$order->update_status( 'on-hold' );
}
return;
}
This code snippets goes on function.php file of your active child theme or theme.
You can easily do anything you want, and the correct hook for paid orders is woocommerce_thankyou
References:
WooCommerce Class WC_Abstract_Order
WooCommerce: Auto complete paid Orders (depending on Payment methods)
Renaming WooCommerce Order Status
yes there is a way, but you need to modify the payment plugin or add your own code, you can read this to understand how payments work.
Now, woocommerce use $order->payment_complete() method to handle the completed order, so you need to hook your own function to modify the status, here is the description of that method
Use this filter: woocommerce_payment_complete_order_status