WooCommerce: Run some code only once on order status change - php

I would like to trigger an action hook (or something similar) on specific order status change. The code should be run only once from order status "processing" to "completed".
Here is m code attempt:
function payment_complete( $order_id, $old_status, $new_status ){
if( $new_status == "completed" && $old_status == "processing") {
// $this->generate_order_file($order_id);
echo '<script>alert("Working now, but not once:()")</script>';
}
}
add_action( 'woocommerce_order_status_changed', 'payment_complete', 99, 3 );
But it seems that my code runs multiple times. I am stuck for instance. Any help will be appreciated.

You can use the following to make the order status change from "processing" to "complete" to be triggered only once for your code:
add_action( 'woocommerce_order_status_processing_to_completed', 'order_processing_to_completed', 100, 2 );
function order_processing_to_completed( $order_id, $order ) {
// Avoid hook to be triggered multiple times at once
if ( did_action( 'woocommerce_order_status_processing_to_completed' ) > 1 ) {
return;
}
// Check that this action hook has not been triggered before
if ( ! $order->get_meta( '_processing_to_completed' ) ) {
// Grab the action in WordPress error logs (for testing)
error_log('"processing_to_completed" Run once only.');
// Add a custom meta data to flag the action as triggered
$order->update_meta_data( '_processing_to_completed', 'yes' );
$order->save(); // Save
// Here add your code to be run once
}
}
Note: You should not use payment_complete function name as it could be used by another plugin.
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
The code inside the if statement will get triggered only once.

Related

Does the "woocommerce_thankyou" action hook fire on failed orders?

My affiliate script tracks a conversion after an order is placed. it runs inside the woocommerce_thankyou action hook:
function affiliate_tracking_code( $order_id ) {
// get the order info for the script
?>
<script>
// affiliate script here
</script>
<?php
}
add_action( 'woocommerce_thankyou', 'affiliate_tracking_code', 10, 1 );
I do not want this script to fire if the order has failed or is pending. Only if it is successful. I can not find in the documentation whether or not the woocommerce_thankyou action hook fires for anything but successful orders.
If it does then what is the best way to make sure that my script only tracks conversions for successful orders and not failed ones.?
One way I have tested is to wrap my script in an if and check if ( $order->get_status() == 'processing' ) : // run the script however I am not sure if there are hidden loopholes.
Yes, it will fire or failed orders as well.
add_action('woocommerce_before_thankyou', 'woocommerce_before_thankyou_failed_order')
function woocommerce_before_thankyou_failed_order( $order_id ) {
$order = wc_get_order( $order_id );
if ( !$order->has_status( 'failed' ) ) {
// if order not failed
}
}
See the hook under wp-content/plugins/woocommerce/templates/checkout/thankyou.php

Woocommerce - woocommerce_order_status_pending hook not calling

I am having trouble with checking order status of woocommerce order.
I have a plugin that I am creating and I need to know, when the order become "pending" and then "completed". But all hooks are working only if I set the order status manually in wordpress admin.
function order_status_changed_clbk( $order_id ){
...some code...
}
add_action( 'woocommerce_order_status_pending', 'order_status_changed_clbk' );
UPDATE
I've found out that there is a little problem. If the user cancels the payment for example at PayPal, he maybe get's redirected to the checkout again. Now let's expect that he repeats the checkout again. In this case the hook get's called a second time which could be problematic. So I've implemented myself a payment_counter:
add_action( 'woocommerce_checkout_order_processed', 'order_status_changed_clbk' );
function order_status_changed_clbk( $order_id ) {
$payment_counter = (int) get_post_meta( $order_id, 'payment_counter', true );
if ( empty( $payment_counter ) ) {
update_post_meta( $order_id, 'payment_counter', 1 );
error_log( 'Function works!' ); //Get's called only once
} else {
update_post_meta( $order_id, 'payment_counter', ++ $payment_counter ); //Cool thing for statistics maybe, but not really needed
}
}
Maybe this hook works for you:
function order_status_changed_clbk( $order_id ){
error_log( 'Function works!' );
}
add_action( 'woocommerce_checkout_order_processed', 'order_status_changed_clbk' );
I'm using it within my plugin. If the order is processed, it's also "pending" so maybe this is the solution your'e looking for.
Try it out and check your debug.log for Function works!.

WooCommerce Auto-Delete Failed Orders

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…

Woocommerce disable automatic order status change pending->processing

I want to dissable this option:
Whenever someone makes and order on my site and the payment is successfull the order status automaticaly changes from pending to processing.
However I don`t want this feature to have enabled. Rather I want to do it manually when i proces the orders.
I found this function in the woocommerce which is making this feature possible. I don`t want to directly change it there but rather with some kind of php snippet which overrides this function.
Here is the function which i need to change : http://woocommerce.wp-a2z.org/oik_api/wc_orderpayment_complete/
PS: I just having a hard time to do it correctly.
Update
May be this payment_complete() is not involved in the process you are looking for. Alternatively, what you could try is the woocommerce_thankyou action hook instead:
add_action( 'woocommerce_thankyou', 'thankyou_order_status', 10, 1 );
function thankyou_order_status( $order_id ){
if( ! $order_id ) return;
$order = new WC_Order( $order_id ); // Get an instance of the WC_Order object
if ( $order->has_status( 'processing' ) )
$order-> update_status( 'pending' )
}
You can use the same alternative hook: woocommerce_thankyou_{$order->get_payment_method()} (replacing $order->get_payment_method() by the payment method ID slug)
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested on Woocommerce 3+ and works.
Using a custom function hooked in woocommerce_valid_order_statuses_for_payment_complete filter hook, where you will return the desired orders statuses that can be taken by the related function payment_complete() which is responsible of auto change the order status.
By default the array of order statuses in the filter is:
array( 'on-hold', 'pending', 'failed', 'cancelled' ).
And we can remove 'on-hold' order status this way:
add_filter( 'woocommerce_payment_complete_order_status', 'disable_auto_order_status', 10, 2 );
function disable_auto_order_status( $order_statuses, $order ) {
$return array( 'pending', 'failed', 'cancelled' );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested on Woocommerce 3+ and works.
Add the following code to your functions.php file.function
ja_order_status( $order_status, $order_id ) {
$order = new WC_Order( $order_id );
if ( 'processing' == $order_status ) {
return 'pending';
}
return $order_status;
}
add_filter( 'woocommerce_payment_complete_order_status', 'ja_order_status', 10, 2 );
Tested on WooCommerce with Storefront paid via Stripe test mode.

Conditionally hiding et showing payment gateways

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.

Categories