WooCommerce: Detecting Order Complete / On Order Completion

How to programmatically detect that a WooCommerce order has completed. In essence, “On order complete”. Easily detect all the status changes an order can go through with actions.

I’ve spent half a day trying to get this working properly. If you want to detect a WooCommerce order being completed in your plugin or theme so as to take an action, you can do so using the woocommerce_order_status_completed action. On order complete I want to change some metadata on some posts automatically, here’s how it’s done:

function mysite_woocommerce_order_status_completed( $order_id ) {
    error_log( "Order complete for order $order_id", 0 );
}
add_action( 'woocommerce_order_status_completed', 'mysite_woocommerce_order_status_completed', 10, 1 );

WooCommerce do make available a list of all WooCommerce actions, filters and hooks. However, it’s slightly obscure the way that this part is explained on that page; It’s shown as “woocommerce_order_status_.$new_status->slug“. So what it means is there’s an action for each of the order statuses:

woocommerce_order_status_pending
woocommerce_order_status_failed
woocommerce_order_status_on-hold
woocommerce_order_status_processing
woocommerce_order_status_completed
woocommerce_order_status_refunded
woocommerce_order_status_cancelled

Here they are again as actions:

function mysite_pending($order_id) {
    error_log("$order_id set to PENDING");
}
function mysite_failed($order_id) {
    error_log("$order_id set to FAILED");
}
function mysite_hold($order_id) {
    error_log("$order_id set to ON HOLD");
}
function mysite_processing($order_id) {
    error_log("$order_id set to PROCESSING");
}
function mysite_completed($order_id) {
    error_log("$order_id set to COMPLETED");
}
function mysite_refunded($order_id) {
    error_log("$order_id set to REFUNDED");
}
function mysite_cancelled($order_id) {
    error_log("$order_id set to CANCELLED");
}

add_action( 'woocommerce_order_status_pending', 'mysite_pending', 10, 1);
add_action( 'woocommerce_order_status_failed', 'mysite_failed', 10, 1);
add_action( 'woocommerce_order_status_on-hold', 'mysite_hold', 10, 1);
// Note that it's woocommerce_order_status_on-hold, and NOT on_hold.
add_action( 'woocommerce_order_status_processing', 'mysite_processing', 10, 1);
add_action( 'woocommerce_order_status_completed', 'mysite_completed', 10, 1);
add_action( 'woocommerce_order_status_refunded', 'mysite_refunded', 10, 1);
add_action( 'woocommerce_order_status_cancelled', 'mysite_cancelled', 10, 1);

Another useful action is woocommerce_payment_complete, called after the payment has been taken for an order. This is useful if you want to perform some automated task after payment has been taken:

function mysite_woocommerce_payment_complete( $order_id ) {
    error_log( "Payment has been received for order $order_id" );
}
add_action( 'woocommerce_payment_complete', 'mysite_woocommerce_payment_complete', 10, 1 );

I hope this saves someone some time!

About Matt Lowe

Matt Lowe is a WordPress web designer / developer based in Newbury, Berkshire. After 8 years of doing the nine-to-five for other companies and watching them make the same mistakes over and over he set out in business on his own, forming Squelch Design to help businesses get online and make money.

72 comments on “WooCommerce: Detecting Order Complete / On Order Completion

  1. Thanks
    Your effords really helped me (even for my own status “my-paid” – I simply use woocommerce_order_status_my-paid

    I had to use your way of handling after realizing that woocommerce_order_status_changed does not really work

  2. Your tutorial is great and really helpful. I am trying to create an online e-shop with live notification on order completion(user side). Do you have any recommendations on this?

    Thank you in advance

  3. Below is code display total sales of product full time, now I want to it reset total sales of product when order status is Processing or Complete ( in WooCommerce => Orders ). How come? It still does not work :'(

    add_action( ‘woocommerce_single_product_summary’, ‘order_is_status’, 10, 1);
    function order_is_status($order_id) {
    global $product;
    $order = new WC_Order($order_id);
    $units_sold = get_post_meta( $product->id, ‘total_sales’, true );
    if ( ‘completed’ == $order->status ) {
    $order -> update_post_meta( $post_id, ‘total_sales’, ‘0’ ); // reset total oder = 0
    }else {
    echo ” . sprintf( __( ‘Units sold: %s’, ‘woocommerce’ ), $units_sold ) . ”;
    }
    }

  4. Hello,
    I´m trying to restrict the content only for a completed paid order. How can I do that? I saw the example code below but I dont know where should I put it. Thank you in advance :D

    <?php
    /**
    * Only copy the opening php tag if needed
    * Removes membership access for processing orders so access is only granted at completion
    */
    function sv_remove_membership_processing_access() {
    if ( function_exists( 'wc_memberships' ) ) {
    remove_action( 'woocommerce_order_status_processing', array( wc_memberships(), 'grant_membership_access' ), 11 );
    }
    }
    add_action( 'init', 'sv_remove_membership_processing_access' );

    • The correct place to put your code would be in a separate plugin. The WordPress Codex has a great page on how to write a plugin which should point you in the right direction. Some people put the code in their child theme’s functions.php, which would work but it strongly advised against as it doesn’t create a separation between the display logic of your website, and the business logic of your website. And whatever you do, do NOT edit any theme that you haven’t created yourself or you’ll lose changes when you update.

  5. This is awesome. Great content. You rock, really. You do.

    Thank you very much. I’m trying to create a new e-mail when order gets paid, and saw only paid plugins to do that. These simple hooks allow me to have control of the flow, and do specific actions for specific stats

  6. Hi There

    I was looking at your code and think thats the last resort for me in the following scenario?

    Is it possible use main site’s db tables for all the Subsites in wordpress multi site network? I am setting a set of stores in WP in which all the sites will be using same data that is on the main site with an exception of ability to change pricing of each product on each sub site. Other than pricing, everything should be fetched from main site tables and everything should be sent to main site tables. By sending i mean, every order should go to main site.

    Is this even possible? Otherwise what i am thinking to do is to insert the order manually into main site’s tables once an order is completed on any of the subsites.

    Any help will be highly appreciated.

    • Hm. It’s almost certainly possible, but whether it’s a good idea is another question entirely. I think whatever solution you are able to come up with will be very brittle and likely to fail in unexpected ways further down the line when WooCommerce is updated. If a customer asked me for this I would spend all of my energy on trying to convince them NOT to do it, and ultimately turn the project down if they insist on going ahead. I wouldn’t want that kind of worry hanging over me.

      I don’t know enough about your project to be able to make a solid recommendation. I’d suggest you might need to think a little bit outside the box on this one though: do you HAVE to have things working this way? Is there an alternative solution that could perhaps provide the ILLUSION that this is what is happening, while removing the need to have orders go from one site to another?

      Final thought: the closest I’ve come to doing something like this is for a customer with a WooCommerce website with a live site and a staging site. When we deploy from the staging site to the live site all of their orders would be overwritten by the staging site’s orders (all of which are tests). Obviously that would be bad, so I engineered a solution that exported all orders from the live site before importing the staging site’s database, then re-imports all of the live orders back into the live site. The code took me a solid day to get working right and I daren’t breathe near it. What you’re asking to do is much, MUCH more tricky and much, MUCH more risky.

Leave a Reply

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