Search code examples
phpwordpresswoocommercehook-woocommerceorders

Auto change order status to completed for specific products in WooCommerce


I have a virtual product and I want it to change status after payment is completed. The following code changes all purchases to "completed", but I want to change only one of my products, not all of them. My product is a variable product and has 4 items,

add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
    function custom_woocommerce_auto_complete_order( $order_id ) { 
        if ( ! $order_id ) {
            return;
        }

        $order = wc_get_order( $order_id );

        if( $order->has_status( 'processing' ) ) {
            $order->update_status( 'completed' );
        }
    }

I searched a lot but did not find an answer. please help me. Thank You


Solution

  • You can target specific product(s) Id(s) from order items, to make your code work only for them as follows:

    add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
    function custom_woocommerce_auto_complete_order( $order_id ) { 
        if ( ! $order_id ) {
            return;
        }
    
        $order = wc_get_order( $order_id );
        
        $product_ids   = array('23'); // Here set your targeted product(s) Id(s)
        $product_found = false;
        
        // Loop through order items
        foreach ( $order->get_items() as $item ) {
            if( array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
                $product_found = true;
                break;
            }
        }
    
        if( $order->has_status( 'processing' ) && $product_found ) {
            $order->update_status( 'completed' );
        }
    }
    

    If you want to target those products exclusively, then you will use the following instead:

    add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
    function custom_woocommerce_auto_complete_order( $order_id ) { 
        if ( ! $order_id ) {
            return;
        }
    
        $order = wc_get_order( $order_id );
        
        $product_ids   = array('23'); // Here set your targeted product(s) Id(s)
        $product_found = true;
        
        // Loop through order items
        foreach ( $order->get_items() as $item ) {
            if( ! array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
                $product_found = false;
                break;
            }
        }
    
        if( $order->has_status( 'processing' ) && $product_found ) {
            $order->update_status( 'completed' );
        }
    }
    

    Code goes in functions.php file of the active child theme (or active theme). It should works.


    Addition: Avoid multiple notifications

    You should better use woocommerce_payment_complete_order_status hook instead like in WooCommerce: Auto complete paid orders answer, to avoid multiple notifications.

    So the code is going to be:

    add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_complete_paid_order', 10, 3 );
    function wc_auto_complete_paid_order( $status, $order_id, $order ) {
        $product_ids   = array('23'); // Here set your targeted product(s) Id(s)
        $product_found = false;
        
        // Loop through order items
        foreach ( $order->get_items() as $item ) {
            if( array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
                $product_found = true;
                break;
            }
        }
    
        return $product_found ? 'completed' : $status;
    }
    

    Or targeting products exclusively:

    add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_complete_paid_order', 10, 3 );
    function wc_auto_complete_paid_order( $status, $order_id, $order ) {
        $product_ids   = array('23'); // Here set your targeted product(s) Id(s)
        $product_found = true;
        
        // Loop through order items
        foreach ( $order->get_items() as $item ) {
            if( ! array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
                $product_found = false;
                break;
            }
        }
    
        return $product_found ? 'completed' : $status;
    }
    

    It should work...