skip to Main Content

I’m trying to automatically cancel all processing WooCommerce orders that are not finalized within 30 days since order is created. The code below does not break the site but it doesn’t do anything. How can I fix it?

// Scheduled event to run daily
function cwpai_mark_cancelled_orders() {
    // Get all processing orders older than 30 days
    $args = array(
        'numberposts' => -1,
        'post_status' => 'wc-processing',
        'date_query'  => array(
            'column' => 'post_modified_gmt',
            'before' => '30 days ago',
        ),
    );
    $orders = get_posts($args);
    
    // Loop through each order and mark it as cancelled
    if ($orders) {
        foreach ($orders as $order) {
            wp_update_post(array(
                'ID'          => $order->ID,
                'post_status' => 'wc-cancelled',
            ));
        }
    }
}

// Schedule the event
function cwpai_schedule_cancelled_orders_event() {
    if (!wp_next_scheduled('cwpai_mark_cancelled_orders_event')) {
        wp_schedule_event(time(), 'daily', 'cwpai_mark_cancelled_orders_event');
    }
}
add_action('wp', 'cwpai_schedule_cancelled_orders_event');

// Hook into the scheduled event to run the function
function cwpai_run_cancelled_orders_event() {
    cwpai_mark_cancelled_orders();
}
add_action('cwpai_mark_cancelled_orders_event', 'cwpai_run_cancelled_orders_event');

2

Answers


  1. Firstly, include this 'post_type' => 'shop_order', within the arguments of the get_posts function. Additionally, take note that you’re utilizing 'column' => 'post_modified_gmt' within the date_query. If your WC-order has been updated, you might not observe any changes. You can modify this to 'column' => 'post_date_gmt'. However, this could prevent your orders from being saved, even if they are updated. Another approach is to consider the following code snippet to update the modified date in the admin panel:

    add_filter('manage_edit-shop_order_columns', function($columns) {
        $columns['post_modified'] = 'Modified';
        return $columns;
    });
    
    add_filter('manage_shop_order_posts_custom_column', function($column_name, $post_id) {
        if ($column_name == 'post_modified') {
            echo get_post_field('post_modified', $post_id);
        }
    }, 10, 2);
    
    add_action('admin_init', function () {
        add_meta_box(
            'cwpai_post_modified_metabox',
            'Modified',
            function ($post) {
                echo get_post_field('post_modified', $post->ID);
            },
            'shop_order',
            'side',
            'high'
        );
    });
    

    To verify, I recommend using this code: wp_clear_scheduled_hook( 'cwpai_mark_cancelled_orders_event' );. This enables you to bypass the one-day wait. However, remember to delete this code after finishing of tests. Furthermore, I advise utilizing the plugin WP Crontrol which grants visibility into all cron tasks.

    Finally, your primary code should resemble the following:

    // Scheduled event to run daily
    function cwpai_mark_cancelled_orders() {
        // Get all processing orders older than 30 days
        $args = array(
            'numberposts' => -1,
            'post_type' => 'shop_order',
            'post_status' => 'wc-processing',
            'date_query'  => array(
                'column' => 'post_modified_gmt',
                'before' => '30 days ago',
            ),
        );
        $orders = get_posts($args);
    
        // Loop through each order and mark it as cancelled
        if ($orders) {
            foreach ($orders as $order) {
                wp_update_post(wp_slash([
                    'ID'          => $order->ID,
                    'post_status' => 'wc-cancelled',
                ]));
            }
        }
    }
    
    // Schedule the event
    add_action('wp', 'cwpai_schedule_cancelled_orders_event');
    function cwpai_schedule_cancelled_orders_event() {
        if (!wp_next_scheduled('cwpai_mark_cancelled_orders_event')) {
            wp_schedule_event(time(), 'daily', 'cwpai_mark_cancelled_orders_event');
        }
    }
    
    // Hook into the scheduled event to run the function
    function cwpai_run_cancelled_orders_event() {
        cwpai_mark_cancelled_orders();
    }
    add_action('cwpai_mark_cancelled_orders_event', 'cwpai_run_cancelled_orders_event');
    
    Login or Signup to reply.
  2. To auto cancel old processing orders (> 30 days), you don’t need any scheduler, and WordPress scheduled actions (cron jobs) are known to be not reliable.

    That’s why WooCommerce, WooCommerce Subscription and all serious plugins uses Action Scheduler library, that is a scalable, traceable job queue for background processing.

    Your code is a bit outdated, as WooCommerce is progressively migrating to custom database tables, for better performances. So for orders it’s recommended to use WC_Order_query instead of WP_Query and CRUD available methods instead of WordPress functions.

    Try the following instead, that will auto cancel old processing orders (> 30 days) daily, and will send an email notification with the cancelled orders (if there were any to cancel):

    add_action( 'woocommerce_after_register_post_type', 'auto_cancel_old_processing_orders_daily' );
    function auto_cancel_old_processing_orders_daily() {
        // Run it once a day only
        if ( get_option('auto_cancel_processing_orders') != date('Y-m-d') ) {
            update_option('auto_cancel_processing_orders', date('Y-m-d') );
    
            // Get old processing orders (more than 30 days old)
            $processing_orders = wc_get_orders( array(
                'limit'         => -1, // 
                'status'        => 'processing', 
                'date_created'  => '<' . date("Y-m-d", strtotime("-30 days")),
            ) );
    
            $orders_count = count($processing_orders); // Count
            
            if( $orders_count > 0 ) {
                $processed_orders = ''; // Initializing
    
                // Loop through old processing orders
                foreach( $processing_orders as $order ) {
                    $order->update_status('wc-cancelled', 'Cancelled via automated daily process'); // Set as cancelled
    
                    $processed_orders .= sprintf('<li><a href="%s">Order #%s</a></li>', 
                        admin_url('post.php?post='.$order->get_id().'&action=edit'), $order->get_order_number());
                }
        
                // Send an email to the admin with all cancelled orders (linked)
                if( ! empty($processed_orders) ) {
                    $subject   = sprintf('Auto cancelled processing orders report (%d order(s) cancelled)', $orders_count); 
                    $recipient = sanitize_email( get_option( 'woocommerce_email_from_address' ) );
                    $content   = sprintf('<p>The following old processing orders have been auto cancelled (%d):<p>
                    <ul>%s<ul>', $orders_count, $processed_orders);
                    $headers   = "Content-Type: text/htmlrn";
                    WC()->mailer()->send( $recipient, $subject, $content, $headers );
                }
            }
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). Tested and works.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search