Commit ef22916604a for woocommerce

commit ef22916604a16090fff40adf43cfc6cc00a95901
Author: Thomas Roberts <5656702+opr@users.noreply.github.com>
Date:   Wed Oct 21 08:30:59 2026 +0100

    Allow decimal Store API price filters (#70036)

    * Allow decimal Store API price filters

    * Add changelog entry for decimal price filters

diff --git a/plugins/woocommerce/changelog/fix-store-api-decimal-price-filters b/plugins/woocommerce/changelog/fix-store-api-decimal-price-filters
new file mode 100644
index 00000000000..cce2d40e7e5
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-store-api-decimal-price-filters
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep legacy price filters working when URLs produce decimal price values.
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Products.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Products.php
index d20201f72bb..209b768221a 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Products.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Products.php
@@ -350,7 +350,7 @@ class Products extends AbstractRoute {
 		$params['min_price'] = array(
 			'description'       => __( 'Limit result set to products based on a minimum price, provided using the smallest unit of the currency.', 'woocommerce' ),
 			'type'              => 'string',
-			'pattern'           => '^[0-9]*$',
+			'pattern'           => '^(?:[0-9]+(?:\.[0-9]+)?|)$',
 			'sanitize_callback' => 'sanitize_text_field',
 			'validate_callback' => 'rest_validate_request_arg',
 		);
@@ -358,7 +358,7 @@ class Products extends AbstractRoute {
 		$params['max_price'] = array(
 			'description'       => __( 'Limit result set to products based on a maximum price, provided using the smallest unit of the currency.', 'woocommerce' ),
 			'type'              => 'string',
-			'pattern'           => '^[0-9]*$',
+			'pattern'           => '^(?:[0-9]+(?:\.[0-9]+)?|)$',
 			'sanitize_callback' => 'sanitize_text_field',
 			'validate_callback' => 'rest_validate_request_arg',
 		);
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/price-filter/price-filter.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/price-filter/price-filter.block_theme.spec.ts
index a321d7c4b4f..601ef739f96 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/price-filter/price-filter.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/price-filter/price-filter.block_theme.spec.ts
@@ -25,7 +25,7 @@ const blockData = {
 };

 const test = base.extend< { templateCompiler: TemplateCompiler } >( {
-	templateCompiler: async ( { requestUtils }, use ) => {
+	templateCompiler: async ( { requestUtils }, provideFixture ) => {
 		// Retry the template creation to handle socket hang up errors
 		const maxRetries = 3;
 		let retryCount = 0;
@@ -67,7 +67,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
 			throw lastError;
 		}

-		await use( compiler );
+		await provideFixture( compiler );
 	},
 } );

@@ -242,6 +242,27 @@ test.describe( `${ blockData.name } Block - with All products Block`, () => {
 		);
 		await expect( productTitles ).toHaveText( [ 'Single' ] );
 	} );
+
+	test( 'restores decimal URL prices without rejecting the Store API request', async ( {
+		page,
+	} ) => {
+		const filteredPageUrl = new URL( page.url() );
+		filteredPageUrl.searchParams.set( 'min_price', '1.1' );
+
+		const productsResponse = page.waitForResponse( ( response ) => {
+			const responseUrl = new URL( response.url() );
+
+			return (
+				responseUrl.pathname.endsWith( '/wc/store/v1/products' ) &&
+				responseUrl.searchParams.get( 'min_price' ) ===
+					'110.00000000000001'
+			);
+		} );
+
+		await page.goto( filteredPageUrl.toString() );
+
+		expect( ( await productsResponse ).status() ).toBe( 200 );
+	} );
 } );

 test.describe( `${ blockData.name } Block - with PHP classic template`, () => {
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
index 98393fcfe7c..09950d28347 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
@@ -217,7 +217,7 @@ class Products extends ControllerTestCase {
 	/**
 	 * @testdox Should return a bad request response for invalid price filter values.
 	 * @testWith ["min_price", "true"]
-	 *           ["max_price", "10.99"]
+	 *           ["max_price", "10.99invalid"]
 	 *           ["min_price", "-1"]
 	 *
 	 * @param string $param Price filter parameter.
@@ -234,6 +234,23 @@ class Products extends ControllerTestCase {
 		$this->assertSame( 'rest_invalid_param', $data['code'], 'The response should identify an invalid request parameter.' );
 	}

+	/**
+	 * @testdox Should allow decimal price filter values produced by product filter blocks.
+	 * @testWith ["min_price", "110.00000000000001"]
+	 *           ["max_price", "10.99"]
+	 *
+	 * @param string $param Price filter parameter.
+	 * @param string $value Price filter value.
+	 */
+	public function test_allows_decimal_price_filter_values( string $param, string $value ): void {
+		$request = new \WP_REST_Request( 'GET', '/wc/store/v1/products' );
+		$request->set_param( $param, $value );
+
+		$response = rest_get_server()->dispatch( $request );
+
+		$this->assertSame( 200, $response->get_status(), 'Decimal price filters should be accepted.' );
+	}
+
 	/**
 	 * @testdox Should allow empty price filter values used by product filter blocks.
 	 */