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.
*/