Commit 40608fbc7cc for woocommerce

commit 40608fbc7ccac7d33c435c239ac901bf90bb0cfa
Author: ahmedevsaad <105542236+ahmedevsaad@users.noreply.github.com>
Date:   Thu Oct 8 20:08:12 2026 +0300

    Fix flat rate fee percentages losing decimals on comma-decimal stores (#69294)

diff --git a/plugins/woocommerce/changelog/69233-fix-flat-rate-fee-percent-decimals b/plugins/woocommerce/changelog/69233-fix-flat-rate-fee-percent-decimals
new file mode 100644
index 00000000000..4a7f1116c81
--- /dev/null
+++ b/plugins/woocommerce/changelog/69233-fix-flat-rate-fee-percent-decimals
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep the decimals of flat rate fee percentages and minimum or maximum fees on stores that use a comma as the decimal separator.
diff --git a/plugins/woocommerce/includes/shipping/flat-rate/class-wc-shipping-flat-rate.php b/plugins/woocommerce/includes/shipping/flat-rate/class-wc-shipping-flat-rate.php
index c17a7dd7470..7db2de075b2 100644
--- a/plugins/woocommerce/includes/shipping/flat-rate/class-wc-shipping-flat-rate.php
+++ b/plugins/woocommerce/includes/shipping/flat-rate/class-wc-shipping-flat-rate.php
@@ -140,6 +140,14 @@ class WC_Shipping_Flat_Rate extends WC_Shipping_Method {
 			'fee'
 		);

+		// Merchants type these in the store's number format, so read its decimal separator as a dot.
+		$atts = array_map(
+			function ( $value ) {
+				return str_replace( wc_get_price_decimal_separator(), '.', (string) $value );
+			},
+			$atts
+		);
+
 		$calculated_fee = 0;

 		if ( $atts['percent'] ) {
diff --git a/plugins/woocommerce/tests/php/includes/shipping/flat-rate/class-wc-shipping-flat-rate-test.php b/plugins/woocommerce/tests/php/includes/shipping/flat-rate/class-wc-shipping-flat-rate-test.php
index 5aa0e1c3598..fce05f07a05 100644
--- a/plugins/woocommerce/tests/php/includes/shipping/flat-rate/class-wc-shipping-flat-rate-test.php
+++ b/plugins/woocommerce/tests/php/includes/shipping/flat-rate/class-wc-shipping-flat-rate-test.php
@@ -147,9 +147,19 @@ class WC_Shipping_Flat_Rate_Test extends WC_Unit_Test_Case {
 	}

 	/**
-	 * Percent fee calculation works as expected with comma as decimal separator. Value after the comma is ignored.
+	 * Percent fee calculation keeps the decimals when the store uses a comma as the decimal separator.
 	 */
 	public function test_evaluate_cost_percent_fee_comma() {
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="10,1" min_fee="12,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 12.5, $val, 'The minimum fee should apply, read with a comma decimal.' );
+
 		$val = $this->call_evaluate_cost->call(
 			$this->sut,
 			'[fee percent="10,1"]',
@@ -158,7 +168,82 @@ class WC_Shipping_Flat_Rate_Test extends WC_Unit_Test_Case {
 				'cost' => 100,
 			)
 		);
-		$this->assertEquals( 10, $val );
+		$this->assertEquals( 10.1, $val, 'The percentage should keep its decimals.' );
+	}
+
+	/**
+	 * A fee below a comma-decimal minimum is raised to that minimum.
+	 */
+	public function test_evaluate_cost_percent_fee_below_comma_min_fee() {
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="5" min_fee="12,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 12.5, $val, 'A fee below the minimum should be raised to it.' );
+
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="12,25" min_fee="12,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 12.5, $val, 'A fee just below the minimum should be raised to it.' );
+	}
+
+	/**
+	 * A fee below a comma-decimal maximum is not capped.
+	 */
+	public function test_evaluate_cost_percent_fee_below_comma_max_fee() {
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="3" max_fee="20,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 3, $val, 'A fee below the maximum should not be capped.' );
+
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="20,25" max_fee="20,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 20.25, $val, 'A fee just below the maximum should not be capped.' );
+
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'[fee percent="30" max_fee="20,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 20.5, $val, 'A fee above the maximum should be capped to it.' );
+	}
+
+	/**
+	 * A comma-decimal percent fee keeps its decimals inside a cost formula.
+	 */
+	public function test_evaluate_cost_comma_percent_fee_in_formula() {
+		$val = $this->call_evaluate_cost->call(
+			$this->sut,
+			'10,5 + [fee percent="2,5"]',
+			array(
+				'qty'  => 1,
+				'cost' => 100,
+			)
+		);
+		$this->assertEquals( 13.0, $val, 'The fee should keep its decimals when added to a base cost.' );
 	}

 	/**