Search code examples
phpwordpresswoocommercehook-woocommerceorders

Add a free product programmatically on WooCommerce specific order status change


Im trying to add a line item when I update an order in the backend and it matches the status update of "enquiry" but no item is added.

I tried both get_status and has_status but I cant get it to work. Any ideas on why this isn't working?

Code:

add_action( 'woocommerce_order_status_changed', 'add_free_products', 20, 4 );
function add_free_products( $order_id, $order ){
 if ( ! $order_id )
        return;

    // Getting an instance of the order object
    $order = wc_get_order( $order_id );

    if( $order->get_status() == 'enquiry' ) {
       $product_id = '155185';
        $product = wc_get_product( $product_id );
        $order->add_product( $product);
        $order->save();
    }
}

Solution

  • There are some mistakes and missing things, Use the following instead:

    add_action( 'woocommerce_order_status_changed', 'add_free_product_on_order_enquiry_status', 20, 4 );
    function add_free_product_on_order_enquiry_status( $order_id, $old_status, $new_status, $order ){
        if ( "enquiry" === $new_status ) {
            $product_id = '155185';
            $product = wc_get_product( $product_id );
            $order->add_product( $product );
            $order->calculate_totals(); // calculate taxes, totals and save (method includes save() method)
            // $order->save();
        }
    }
    

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


    Addition 1

    You could also flag this action with custom meta data, to avoid adding multiple products if you change order status multiple times with the following:

    add_action( 'woocommerce_order_status_changed', 'add_free_product_on_order_enquiry_status', 20, 4 );
    function add_free_product_on_order_enquiry_status( $order_id, $old_status, $new_status, $order ){
        if ( "enquiry" === $new_status && ! $order->get_meta('_free_product_added') ) {
            $product_id = '155185';
            $product = wc_get_product( $product_id );
            $order->add_product( $product );
            $order->update_meta_data('_free_product_added', 'yes'); // Flag the order
            $order->calculate_totals(); // calculate taxes, totals and save (method includes save() method)
        }
    }
    

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


    Addition 2 - Check if the product has been already added to the order (avoiding adding the product multiple times):

    function order_has_specific_product( $product_id, $order ) {
        // Loop through order items to check if a product is on the current order
        foreach ( $order->get_items() as $item ) {
            if ( in_array( $product_id, array($item->get_product_id(), $item->get_variation_id()) ) ) {
                return true;
            }
        }
        return false;
    }
    
    add_action( 'woocommerce_order_status_changed', 'add_free_product_on_order_enquiry_status', 20, 4 );
    function add_free_product_on_order_enquiry_status( $order_id, $old_status, $new_status, $order ){
        $product_id = '155185'; // Free product to be added only once
    
        if ( "enquiry" === $new_status && ! order_has_specific_product( $product_id, $order ) ) {
            $product = wc_get_product( $product_id );
            $order->add_product( $product );
            $order->calculate_totals(); // calculate taxes, totals and save (method includes save() method)
        }
    }
    

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