Commit d27f82b0383 for woocommerce

commit d27f82b03831f2698897408ccb381c7ceb0d4d82
Author: Alefe Souza <contact@alefesouza.com>
Date:   Tue Oct 13 13:13:38 2026 -0300

    Show the preloaded cart instead of a stale saved copy in Cart and Checkout blocks (#69667)

diff --git a/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart b/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart
new file mode 100644
index 00000000000..d4b51001f2f
--- /dev/null
+++ b/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix the Cart and Checkout blocks showing an outdated cart, such as the wrong shipping method, on sites that delay scripts.
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
index 331a6e04d41..f2b79cb9001 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
@@ -9,6 +9,7 @@ import {
 	select,
 } from '@wordpress/data';
 import { controls as dataControls } from '@wordpress/data-controls';
+import { getSetting } from '@woocommerce/settings';

 /**
  * Internal dependencies
@@ -63,9 +64,16 @@ window.addEventListener( 'load', () => {
 	// On login, if a customer had a cart session, the cached cart is equal to the default cart data, with no items.
 	// We need to check if the cached cart has items, otherwise we will wrongly skip the API request.
 	const hasItemsInCachedCart = ( cachedCart?.itemsCount ?? 0 ) > 0;
+	// A preloaded cart is fresh and costs no request, so let the resolver use it. The cached cart's hash does not
+	// cover the selected shipping rates, the customer address, or extension data, so it can be stale.
+	const isCartPreloaded = getSetting< string[] >(
+		'preloadedApiRequestPaths',
+		[]
+	).includes( '/wc/store/v1/cart' );

 	if (
 		( ! hasCartSession() || hasItemsInCachedCart ) &&
+		! isCartPreloaded &&
 		! isAddingToCart() &&
 		! isEditor() // Don't finish resolution in editor,but only for real carts
 	) {
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
index f1e13858fd4..662d74e3fda 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
@@ -2,6 +2,7 @@
  * External dependencies
  */
 import { dispatch as wpDispatch } from '@wordpress/data';
+import { getSetting } from '@woocommerce/settings';
 import type { Cart } from '@woocommerce/types';

 /**
@@ -19,7 +20,15 @@ jest.mock( '@woocommerce/utils', () => ( {
 	isSiteEditorPage: jest.fn().mockReturnValue( true ),
 } ) );
 jest.mock( '../persistence-layer' );
+jest.mock( '@woocommerce/settings', () => {
+	const actual = jest.requireActual( '@woocommerce/settings' );
+	return {
+		...actual,
+		getSetting: jest.fn( actual.getSetting ),
+	};
+} );

+const mockGetSetting = jest.mocked( getSetting );
 const mockHasCartSession = jest.mocked( hasCartSession );
 const mockIsAddingToCart = jest.mocked( isAddingToCart );
 const mockPersistenceLayerGet = jest.mocked( persistenceLayer.get );
@@ -82,6 +91,19 @@ describe( 'Window load event handler', () => {
 		expect( mockFinishResolution ).toHaveBeenCalledWith( 'getCartData' );
 	} );

+	it( 'should let the resolver read the cart when the page preloaded it, even if cached cart has items', () => {
+		mockHasCartSession.mockReturnValue( true );
+		mockIsAddingToCart.mockReturnValue( false );
+		mockPersistenceLayerGet.mockReturnValue( {
+			itemsCount: 2,
+		} as unknown as Cart );
+		mockGetSetting.mockReturnValueOnce( [ '/wc/store/v1/cart' ] );
+
+		loadHandler( new Event( 'load' ) );
+
+		expect( mockFinishResolution ).not.toHaveBeenCalled();
+	} );
+
 	it( 'should make API request when has cart session but cached cart is empty', () => {
 		mockHasCartSession.mockReturnValue( true );
 		mockIsAddingToCart.mockReturnValue( false );
diff --git a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
index 3c8725743ac..9b5ead9e8be 100644
--- a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
+++ b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
@@ -259,6 +259,7 @@ class AssetDataRegistry {
 		$core_data                                 = $this->get_core_data();
 		$core_data['experimentalWcRestApiV4']      = Features::is_enabled( 'rest-api-v4' );
 		$core_data['experimentalCartSaveForLater'] = FeaturesUtil::feature_is_enabled( 'cart_save_for_later' );
+		$core_data['preloadedApiRequestPaths']     = array_keys( $this->preloaded_api_requests );
 		// note this WILL wipe any data already registered to these keys because they are protected.
 		$this->data = array_replace_recursive( $settings, $core_data );
 	}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php b/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
index 5e02cff825c..d7c23efea32 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
@@ -189,4 +189,26 @@ class AssetDataRegistry extends \WP_UnitTestCase {
 			$features_controller->change_feature_enable( 'cart_save_for_later', $original_enabled );
 		}
 	}
+
+	/**
+	 * @testdox `preloadedApiRequestPaths` lists the paths passed to hydrate_api_request(), once each.
+	 */
+	public function test_preloaded_api_request_paths_lists_hydrated_paths() {
+		$this->registry->hydrate_api_request( '/wc/store/v1/cart' );
+		$this->registry->hydrate_api_request( '/wc/store/v1/test' );
+		$this->registry->hydrate_api_request( '/wc/store/v1/cart' );
+
+		$this->registry->initialize_core_data();
+
+		$this->assertSame( array( '/wc/store/v1/cart', '/wc/store/v1/test' ), $this->registry->get()['preloadedApiRequestPaths'] );
+	}
+
+	/**
+	 * @testdox `preloadedApiRequestPaths` is an empty list when nothing was preloaded.
+	 */
+	public function test_preloaded_api_request_paths_is_empty_without_hydration() {
+		$this->registry->initialize_core_data();
+
+		$this->assertSame( array(), $this->registry->get()['preloadedApiRequestPaths'] );
+	}
 }