Commit 8a55371668 for woocommerce
commit 8a55371668c269f9f332632b4350c713440aea89
Author: Néstor Soriano <konamiman@konamiman.com>
Date: Tue Dec 9 15:25:53 2025 +0100
Fix: order being incorrectly changed to "Completed" (#62191)
diff --git a/plugins/woocommerce/changelog/pr-62191 b/plugins/woocommerce/changelog/pr-62191
new file mode 100644
index 0000000000..232b04bc74
--- /dev/null
+++ b/plugins/woocommerce/changelog/pr-62191
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix: order being incorrectly changed to Completed
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index 7312a7e9f4..742949f2bb 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -10,6 +10,7 @@
* @package WooCommerce\Classes
*/
+use Automattic\WooCommerce\Caches\OrderCache;
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Enums\ProductTaxStatus;
use Automattic\WooCommerce\Enums\ProductType;
@@ -303,6 +304,14 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
if ( $items_changed ) {
delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
+
+ // Invalidate the order cache to prevent stale item data.
+ // This fixes a race condition where get_items() may have been called
+ // before items were saved, caching empty items arrays.
+ // See https://github.com/woocommerce/woocommerce/issues/62173.
+ if ( OrderUtil::orders_cache_usage_is_enabled() ) {
+ wc_get_container()->get( OrderCache::class )->remove( $this->get_id() );
+ }
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index 6473f1735f..a951bac96e 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -3926,4 +3926,63 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$sync->process_batch( array( $order_id ) );
$this->assertEquals( false, (bool) $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$this->sut::get_orders_table_name()} WHERE id = %d", $order_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
+
+ /**
+ * @testdox needs_processing returns correct result when COGS filter triggers get_items() during order hydration.
+ *
+ * Reproduces GitHub issue #62173: woocommerce_new_order fires before items are in DB.
+ * If a hook loads the order and triggers get_items() (e.g., via COGS filter calling get_data()),
+ * empty items get cached. The fix invalidates the cache when items are saved.
+ */
+ public function test_needs_processing_with_cogs_filter_triggering_get_items() {
+ $this->toggle_cot_feature_and_usage( true );
+ $this->enable_cogs_feature();
+
+ $this->assertTrue(
+ OrderUtil::orders_cache_usage_is_enabled(),
+ 'Order cache must be enabled for this test.'
+ );
+
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_virtual( false );
+ $product->set_downloadable( false );
+ $product->save();
+
+ // COGS filter that calls get_data() (which calls get_items()), priming empty items.
+ $cogs_filter = function ( $cogs_value, $order ) {
+ $order->get_data();
+ return $cogs_value;
+ };
+ add_filter( 'woocommerce_load_order_cogs_value', $cogs_filter, 10, 2 );
+
+ // Load order during woocommerce_new_order (before items are in DB), caching it with empty items.
+ $new_order_hook = function ( $order_id ) {
+ wc_get_order( $order_id );
+ };
+ add_action( 'woocommerce_new_order', $new_order_hook, 10, 1 );
+
+ // Create order with item. woocommerce_new_order fires before save_items().
+ $order = new WC_Order();
+ $item = new WC_Order_Item_Product();
+ $item->set_props(
+ array(
+ 'product' => $product,
+ 'quantity' => 1,
+ 'subtotal' => 10,
+ 'total' => 10,
+ )
+ );
+ $order->add_item( $item );
+ $order->save();
+ $order_id = $order->get_id();
+
+ remove_filter( 'woocommerce_load_order_cogs_value', $cogs_filter, 10 );
+ remove_action( 'woocommerce_new_order', $new_order_hook, 10 );
+
+ // Without fix: returns cached order with empty items. With fix: fresh data from DB.
+ $fresh_order = wc_get_order( $order_id );
+
+ $this->assertCount( 1, $fresh_order->get_items(), 'Order should have items from DB.' );
+ $this->assertTrue( $fresh_order->needs_processing(), 'Order with physical product should need processing.' );
+ }
}