Commit d8c320ad83e for woocommerce

commit d8c320ad83ee4edaa74cca8dfd55319dbb80ece9
Author: Till Krüss <tillkruss@users.noreply.github.com>
Date:   Wed May 6 03:14:22 2026 -0700

    Fix re-entrant order reads from cached refund objects (#64232)

    * Fix re-entrant order reads from cached refund objects

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Add changefile(s) from automation for the following project(s): woocommerce

    * flush orders cache on upgrade

    * fix `wc_get_orders()` parameters

    * switch to `10.8.0`

    * use deterministic ordering

    * rename cache key instead of using migration

    * formatting

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/64232-fix-order-refunds-cache b/plugins/woocommerce/changelog/64232-fix-order-refunds-cache
new file mode 100644
index 00000000000..6c17a568eb0
--- /dev/null
+++ b/plugins/woocommerce/changelog/64232-fix-order-refunds-cache
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent re-entrant order reads triggered by cached refund objects.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/class-wc-order.php b/plugins/woocommerce/includes/class-wc-order.php
index 68201403a46..a05929a72a2 100644
--- a/plugins/woocommerce/includes/class-wc-order.php
+++ b/plugins/woocommerce/includes/class-wc-order.php
@@ -2283,18 +2283,36 @@ class WC_Order extends WC_Abstract_Order {
 	 * @return WC_Order_Refund[]
 	 */
 	public function get_refunds() {
-		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $this->get_id();
-		$refunds   = wp_cache_get( $cache_key, $this->cache_group );
+		$cache_key  = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $this->get_id();
+		$refund_ids = wp_cache_get( $cache_key, $this->cache_group );

-		if ( false === $refunds ) {
-			$refunds = wc_get_orders(
+		if ( false === $refund_ids ) {
+			$refunds    = (array) wc_get_orders(
 				array(
 					'type'   => 'shop_order_refund',
 					'parent' => $this->get_id(),
 					'limit'  => -1,
 				)
 			);
-			wp_cache_set( $cache_key, $refunds, $this->cache_group );
+			$refund_ids = array();
+			foreach ( $refunds as $refund ) {
+				if ( $refund instanceof WC_Order_Refund ) {
+					$refund_ids[] = $refund->get_id();
+				}
+			}
+			wp_cache_set( $cache_key, $refund_ids, $this->cache_group );
+		} else {
+			$refunds = ! empty( $refund_ids )
+				? wc_get_orders(
+					array(
+						'type'          => 'shop_order_refund',
+						'post__in'      => $refund_ids,
+						'orderby'       => 'post__in',
+						'limit'         => -1,
+						'no_found_rows' => true,
+					)
+				)
+				: array();
 		}

 		$this->refunds = array();
diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
index ae571880fa2..97f4a30e574 100644
--- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
@@ -705,7 +705,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme

 		$cache_keys_mapping = array();
 		foreach ( $order_ids as $order_id ) {
-			$cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id;
+			$cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $order_id;
 		}

 		$non_cached_ids = array();
@@ -738,16 +738,15 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
 			)
 		);

-		$order_refunds = array();
+		$order_refund_ids = array_fill_keys( $non_cached_ids, array() );
 		foreach ( $refunds as $refund ) {
-			if ( $refund instanceof \WC_Order_Refund ) {
-				$order_refunds[ $refund->get_parent_id() ][] = $refund;
+			if ( $refund instanceof \WC_Order_Refund && isset( $order_refund_ids[ $refund->get_parent_id() ] ) ) {
+				$order_refund_ids[ $refund->get_parent_id() ][] = $refund->get_id();
 			}
 		}

 		foreach ( $non_cached_ids as $order_id ) {
-			$cached_refunds = isset( $order_refunds[ $order_id ] ) ? $order_refunds[ $order_id ] : array();
-			wp_cache_set( $cache_keys_mapping[ $order_id ], $cached_refunds, 'orders' );
+			wp_cache_set( $cache_keys_mapping[ $order_id ], $order_refund_ids[ $order_id ], 'orders' );
 		}
 	}

diff --git a/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
index 0653b33a511..1f59eec9cf5 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
@@ -53,7 +53,7 @@ class WC_Order_Refund_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT im
 		}

 		$parent_order_id  = $order->get_parent_id();
-		$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $parent_order_id;
+		$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $parent_order_id;
 		wp_delete_post( $id );
 		wp_cache_delete( $refund_cache_key, 'orders' );
 		$order->set_id( 0 );
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
index c44a3add351..85bed5fc1d3 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
@@ -78,7 +78,7 @@ class OrdersTableRefundDataStore extends OrdersTableDataStore {
 			return;
 		}

-		$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $refund->get_parent_id();
+		$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $refund->get_parent_id();
 		wp_cache_delete( $refund_cache_key, 'orders' );

 		$this->delete_order_data_from_custom_order_tables( $refund_id );
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
index cbbea52799a..fb874650bf0 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
@@ -69,16 +69,16 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 			)
 		)[0];

-		$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order->get_id();
-		$cached_refunds   = wp_cache_get( $refund_cache_key, 'orders' );
+		$refund_cache_key  = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $order->get_id();
+		$cached_refund_ids = wp_cache_get( $refund_cache_key, 'orders' );

-		$this->assertEquals( $cached_refunds[0]->get_id(), $fetched_order->get_refunds()[0]->get_id() );
+		$this->assertEquals( $cached_refund_ids[0], $fetched_order->get_refunds()[0]->get_id() );

 		$refund->delete( true );

 		// Cache should be cleared now.
-		$cached_refunds = wp_cache_get( $refund_cache_key, 'orders' );
-		$this->assertEquals( false, $cached_refunds );
+		$cached_refund_ids = wp_cache_get( $refund_cache_key, 'orders' );
+		$this->assertEquals( false, $cached_refund_ids );
 	}

 	/**