WooCommerce’s default order flow requires manual intervention to mark an order as Completed, even when it contains only digital products that don’t require picking, packing, or shipping. A customer buys a plugin, a PDF, or a software licence, the payment clears, the download link is emailed automatically, and then the order sits in Processing indefinitely until someone logs into the admin and updates its status. This is unnecessary friction for both the store owner and the customer, and it clutters the orders list with processing orders that are effectively already fulfilled.
This snippet hooks into the payment completion event and automatically transitions orders to Completed when every item in the order is either virtual or downloadable.
The Code
Add this to your functions.php or a site-specific plugin. It hooks into woocommerce_payment_complete, which fires after a payment gateway confirms a successful transaction.
The Logic
The snippet loops through every line item in the order and checks whether the associated product is virtual or downloadable. If any single item is neither, meaning it’s a physical product that needs to be shipped, the $auto_complete flag is set to false and the loop breaks early. Only when every item passes the check does the order status update to Completed.
This conservative approach ensures mixed orders, those containing both physical and digital items, are left in Processing for manual fulfilment, while pure digital orders are handled automatically.
The Status Note
update_status() accepts an optional second argument for an order note. The snippet includes a note explaining why the status changed, which is visible in the order timeline in the admin. This is helpful when reviewing orders later, the note makes it immediately clear the auto-completion was intentional and rule-based rather than a manual action.
Why woocommerce_payment_complete
This hook fires after payment is confirmed, which is the right moment to trigger auto-completion, not before. Using an earlier hook like woocommerce_checkout_order_created would mark orders complete before payment is confirmed, which is incorrect. The woocommerce_payment_complete hook guarantees the payment gateway has already acknowledged the transaction.
Payment Gateway Compatibility
Most standard payment gateways, Stripe, PayPal, Square, trigger woocommerce_payment_complete on successful payment. Bank transfer and cheque payment gateways don’t trigger it until payment is manually confirmed in the admin, which is correct behaviour, those orders should remain in Processing until the store owner confirms receipt of funds. The snippet works correctly with both flows.
Customer Experience
From the customer’s perspective, an order marked Completed immediately after payment provides a cleaner experience, their account shows the order as fully resolved, and any completion-triggered emails (like a “your order is complete” email if you’ve configured one) fire promptly rather than being delayed until manual processing.
add_action( 'woocommerce_payment_complete', function( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) return;
$auto_complete = true;
foreach ( $order->get_items() as $item ) {
$product = $item->get_product();
if ( ! $product ) continue;
if ( ! $product->is_virtual() && ! $product->is_downloadable() ) {
$auto_complete = false;
break;
}
}
if ( $auto_complete ) {
$order->update_status( 'completed', __( 'Order auto-completed: all items are virtual or downloadable.', 'nahnu-snippets' ) );
}
} );
