Cancel WooCommerce not-paid Orders automatically

One way to keep your online store clean and organized is by using the order status. Complete, Cancelled, Awaiting Payment are statuses which helps you to pay attention to the most important orders.

But when you have a lot of “Awaiting payment” orders and you don’t know until when you need to consider that order? Well, I’m writing today because of that. A client of mine gave me this problem: I want to cancel my pending orders (awaiting payment) after 4 days.

Before the solution we discuss about:

  • Should we need to consider all the payment methods?
  • The client should receive any type of email confirmation?
  • Which order status we need to consider?

But after that, I started to code. Follow me, step by step:

1. Create a query with WooCommerce orders.

$query = ( 
   array(
	'limit'   => 5,
	'orderby' => 'date',
	'order'   => 'DESC',
	'status'  => array( 'pending' )
   ) 
);

$orders = wc_get_orders( $query );

// our orders loop
foreach( $orders as $order ){		
		
}

For performance reasons, I’m getting only 5 orders each time I run my snippet. Pay attention to the “status” array, where you need to replace by the status you want to consider. We have a lot of status in WooCommerce, like “on-hold”, “completed”, “cancelled”, etc…

2. Check the number of days between order name and today

Inside the loop I wrote above, we will check the time difference and know the “age” of your order.

foreach( $orders as $order ){		
  
  // date of our order
  $date = new DateTime( $order->get_date_created() );
  // today's date
  $today = new DateTime();
  
  $interval = $date->diff($today);
  // days between order name and today	
  $datediff = $interval->format('%a');
   
  // 4: minimum of days the order can be considered
  if( $datediff >= 4 ){
    $order->update_status('cancelled', 'Cancelled for missing payment);
   }
		
	}

Right now, the code says: “If the order is pending (as we defined on the query) and it has more than 4 days, we will update the order status to “canceled“, adding a status note (Cancelled for missing payment).

3. Tell to WordPress when to run our function

Considering that you put our code into a function like that:


function autocancel_wc_orders(){
	$query = ( array(
		'limit'   => 5,
		'orderby' => 'date',
		'order'   => 'DESC',
		'status'  => array( 'pending' )
	) );
	$orders = wc_get_orders( $query );
	foreach( $orders as $order ){		

		$date     = new DateTime( $order->get_date_created() );
		$today    = new DateTime();
		$interval = $date->diff($today);
	
		$datediff = $interval->format('%a');

		if( $datediff >= 4 ){
			$order->update_status('cancelled', 'Cancelled for missing payment');
		}
		
	}

}

Now we just need to select a WordPress action to run this. Just as it is only 5 orders each time, I decided to use the simple admin_init action. We can use a lot of alternatives, like WPcron or something, but for this case, I think it was the best way: nice performance, admin security, and without dependencies need 🙂

add_action( 'admin_init', 'autocancel_wc_orders' );

That’s all. It was simple? If you want to discuss this or you have a better way to make this, please let me know in the comment area.

Cheers.

Comments

36 responses to “Cancel WooCommerce not-paid Orders automatically”

  1. brian avatar

    hey, very nice work.
    we need a directly cancelation of “pending orders” I have tried to set “if( $datediff >= 4 )” to “-1” => if( $datediff >= -1 ) and that is working, but is there any better solution?

    if the order is getting created, the order should get directly cancled if not payed.

    1. Samuel Silva avatar
      Samuel Silva

      Hi Brian!

      If you don’t want to use the number of days interval to cancel, you only need to remove that if clause, instead of using -1 comparing.

      – if( $datediff >= 4 ){

      The best,
      Samuel

  2. Axel avatar
    Axel

    Hi, this is exactly what I’ve been looking for some time now. Thank you!

    Just a question, does it compare hours or just date difference?

    I want to cancel on-hold orders after 24hrs.

    1. Samuel Silva avatar
      Samuel Silva

      Thank you for your words, Axel.
      Regarding your request, you only need to change the number from this line to 1.
      > if( $datediff >= 4 ){

      1. Axel avatar
        Axel

        Awesome. I already did, just wanted to make sure it actually activates after 24hrs and not when date changes.

        I also want to ask you a question and maybe make it a request/suggestion for future article:

        I’ve been searching for weeks on a way to make on-site notifications for woocommerce order status changes.

        By “on-site” what I mean is to have a bell icon (showing count of recent notifications inside a red circle) on the header (frontend, for all customers) and when clicked it displays all recent notifications, like for example: “Your order has been shipped ? Today at 2:00pm”

        I imagine that would be some kind of an inbox. I know this can be done, and I just started learning code so I’m kinda lost on how to achieve this.

        Hope you have some guidance as to how to do this or what should I learn to be able to build it.

        Anyways, that’s all ? thanks a lot for the automatic order cancellation snippet! I tested it and it’s working.

        Have a great day Samuel!

        1. Samuel Silva avatar
          Samuel Silva

          Hi Axel,

          Ok, I got you. We’re not considering 24hours on this snippet, but we’re counting and comparing day by day. To consider 24 hours, the date/time needs to compare the hour diff, instead of number of day diff.

          Regarding your suggestion, it seems great!
          The first step is searching and learning about the Ajax method on WordPress, this is the technology that provides you change the information on the HTML without a refresh.

          If you need custom code for starting or hiring me for the development, please email me: hello@samuelsilva.pt

          The best,
          Samuel

  3. Fernando Henriques avatar
    Fernando Henriques

    Olá Samuel.

    Muito bom artigo, obrigado, mesmo o que procurava. Este código deve ser adicionado à functions.php?

    1. Samuel Silva avatar
      Samuel Silva

      Olá Fernando.

      Obrigado. Sim, o snippet pode ser adicionado no functions.php, recomendo o child-theme.

  4. Ivor avatar

    Great work! Would be awesome if you can create a plugin from this where some of the variables could be set.

    I now do all this by hand, and always send them an email explaining why their order was cancelled (and that they are welcome to order again).

    If you need help (I’m pro PHP dev, but rookie in WordPress), let me know!

    1. Samuel Silva avatar
      Samuel Silva

      Hi Ivor! Actually, I was starting the plugin, but I didn’t finish it yet. If you’re interested into take a look on the code, just let me know 🙂

  5. Josh avatar
    Josh

    Hi. Thanks for sharing this.

    I tried to use the codes in my child theme’s functions.php but don’t seem to work. I want to make it cancel the order immediately after the customer fails to complete the payment. By default on my site, the order will be made and the status is pending payment. I would like this pending to change to cancelled immediately so that I don’t have to deal with it.

    I removed both
    if( $datediff >= 4 ){

    But I received this error when saving.
    syntax error, unexpected ‘orderby’ (T_STRING), expecting ‘)’

    Any advice? Thanks!

    1. Samuel Silva avatar
      Samuel Silva

      Hi Josh,

      Thank you for reaching out and paste your questions here in the comments. It can be a general issue and somebody else can have the same solution.

      Before anything, please check if you cleaned the “)” from the IF you removed.
      Regarding your error, I think it’s because of the parenthesis on the query, before the array(…). Please remove them – the result will be something like: $query = array(…).

      Hope this helps!

      1. Josh avatar
        Josh

        Hi Samuel! Thanks for getting back so soon.
        Still cant get it to work. If you would allow me to include the txt file here
        https://drive.google.com/file/d/1IHqOY–B58kqup0nM6UPu04Pv8czZcb6/view?usp=sharing

        1. Samuel Silva avatar
          Samuel Silva

          I catch it! Please remove the “{” after your comment “Immediate Cancellation”, that’s a sintax error.

  6. SF avatar
    SF

    Hello! Can I know what is the maximum number that I can put for
    ‘limit’ => 5,

    I am checking out test orders and realize the 6th order onwards will be back to ‘processing’ instead of ‘cancelled’

    1. Samuel Silva avatar
      Samuel Silva

      Hi! Technically, there isn’t a maximum number on the query.
      Maybe you have another service running that back the orders to “processing” status, this snippet compares the date and decide to cancel the order or not, according to the snippet info.

  7. César Macaia avatar
    César Macaia

    Olá Samuel, muito obrigado pelo tutorial, já estava com este pedido a quase 2 meses e realmente pelo painel do woocommerce não é possível resolver. Ví que está desenvolvendo um plugin, como está o processo? andando ou parado?

    1. Samuel Silva avatar
      Samuel Silva

      Cesar, if you need additional help with this, get in touch with me using the contact form.
      Cheers,
      Samuel

  8. César Macaia avatar
    César Macaia

    Hello Samuel I have tried the snippet and it worked in my small website. I have cancelled 2 orders. But I have another ecommerce, I tried the snippet but still showing 1373 orders as pending, looks like it changes just few orders.
    Older orders are not changing, What can I do? Is that limit you put as five (5) the problem?

    1. Samuel Silva avatar
      Samuel Silva

      Hi César! Each time you access the wp-admin, 5 orders will be processed, for performance reasons.
      You can also integrate this snippet in a WP Cron Job, and for example, we can process 5 orders for every 5 minutes.
      Hope this helps!

      1. César Macaia avatar
        César Macaia

        Thank you Samuel, I tried using WP Control plugin to schedule but it didn’t work. Now I put 100 as limit on function.php and updated but nothing happened.

  9. Sagar avatar
    Sagar

    Hi,
    I wanted to compare if the order is pending for more than 60 minutes. How can that be achieved?

    1. Samuel Silva avatar
      Samuel Silva

      Hi Safar! For this approach, I suggest you to run the snippet using a cronjob.

  10. Denis avatar
    Denis

    Hi… I have two questions.

    1) I pasted the code exactly as below in the function.php and nothing happened (note: I have several requests like “Pagamento Pending” in Portuguese, I don’t know if it changes something because of the woocomerce sling.

    2) Considering that this function works inside functions.php… I would like to create a unique php file to be executed by cronjob, as would be the formatting in the file and the leader for cronjob

    ———–
    function autocancel_wc_orders(){
    $query = ( array(
    ‘limit’ => 5,
    ‘orderby’ => ‘date’,
    ‘order’ => ‘DESC’,
    ‘status’ => array( ‘pending’ )
    ) );
    $orders = wc_get_orders( $query );
    foreach( $orders as $order ){

    $date = new DateTime( $order->get_date_created() );
    $today = new DateTime();
    $interval = $date->diff($today);

    $datediff = $interval->format(‘%a’);

    if( $datediff >= 7 ){
    $order->update_status(‘cancelled’, ‘Cancelled for missing payment’);
    }

    }

    }

    add_action( ‘admin_init’, ‘autocancel_wc_orders’ );

  11. Alan avatar

    Hello! great post!! I need to change the status from “on hold” to “cancelled” after xx days I tryied a similar code, it changes the status to “cancelled” but It doesn’t return the stock of the order. I need to get the stock back. Could you help me with that??

    1. Samuel Silva avatar
      Samuel Silva

      Hi! Thank you for your reading.

      Please take a look at this code snippet, hope this helps!
      https://stackoverflow.com/questions/45880888/woocommerce-auto-restore-stock-on-order-cancel

  12. Rush B avatar
    Rush B

    Hi Samuel,

    If I change this line to
    ‘status’ => array( ‘pending’,’on-hold’ )

    will it check both pending and on-hold orders?

    1. Samuel Silva avatar
      Samuel Silva

      Yes, exactly.

  13. André avatar

    Thanks, very helpful for me and i believe that many others

    1. Samuel Silva avatar
      Samuel Silva

      Thank you 🙂 You’re the best.

  14. Ignatius avatar
    Ignatius

    Hey,

    I want to get the date from the last time an order was updated so I have used:

    $date = new DateTime( $order->get_the_modified_date() );

    But it throw me an error, what am I doing wrong ?

    Best Regards.

    1. Samuel Silva avatar
      Samuel Silva

      Which error?

      1. Ignatius avatar
        Ignatius

        Hi,

        “Uncaught Error: Call to undefined method Automattic\WooCommerce\Admin\Overrides\Order::get_the_modified_date() in wp-content/themes/flatsome-child/functions.php:632”

        Thanks for your help

        1. Ignatius avatar
          Ignatius

          Fixed it myself, shoud have use “get_date_modified()” instead.

          Thanks for your work

  15. Evgeniy avatar
    Evgeniy

    Hi!
    Why do you not use parameter date_created in wc_get_orders ?
    like that:
    $query = [
    ‘limit’ => -1,
    ‘orderby’ => ‘date’,
    ‘order’ => ‘DESC’,
    ‘status’ => self::$vorkasse_statuses,
    ‘date_created’ => ‘update_status(‘wc-cancelled’, ‘Cancelled for missing payment’);
    }

  16. Jason Wang avatar

    Hello Samuel,
    I found your article very helpful! And I’m hoping you could help me out with this.
    I understand your code reads the dates that the orders are created on, if they are more than 4 days old they are auto-cancelled. Now for my website, I need to read the date that the order status changes from “Pending Quote” to “Pending payment”, which method can I use to retrieve the dates?
    Looking forward to your tips. Thank you in advance!

    Best regards

Leave a Reply

Your email address will not be published. Required fields are marked *