Commit 3de8f28567c for woocommerce

commit 3de8f28567cfb6f8b1ef2a0218d349e813d707fe
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date:   Wed Aug 26 17:32:18 2026 +0100

    Fix hidden address fields being required in the classic checkout (#67706)

diff --git a/plugins/woocommerce/changelog/65412-fix-hidden-required-fields b/plugins/woocommerce/changelog/65412-fix-hidden-required-fields
new file mode 100644
index 00000000000..77ba9462215
--- /dev/null
+++ b/plugins/woocommerce/changelog/65412-fix-hidden-required-fields
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Skip required validation for hidden address fields on the classic checkout and My Account address form.
diff --git a/plugins/woocommerce/includes/class-wc-checkout.php b/plugins/woocommerce/includes/class-wc-checkout.php
index 6e5102b5eed..1483ef4dc01 100644
--- a/plugins/woocommerce/includes/class-wc-checkout.php
+++ b/plugins/woocommerce/includes/class-wc-checkout.php
@@ -907,7 +907,7 @@ class WC_Checkout {
 				if ( ! isset( $data[ $key ] ) ) {
 					continue;
 				}
-				$required    = ! empty( $field['required'] );
+				$required    = ! empty( $field['required'] ) && empty( $field['hidden'] );
 				$format      = array_filter( isset( $field['validate'] ) ? (array) $field['validate'] : array() );
 				$field_label = isset( $field['label'] ) ? $field['label'] : '';

diff --git a/plugins/woocommerce/includes/class-wc-countries.php b/plugins/woocommerce/includes/class-wc-countries.php
index bc174fe4dca..c87df90ec5d 100644
--- a/plugins/woocommerce/includes/class-wc-countries.php
+++ b/plugins/woocommerce/includes/class-wc-countries.php
@@ -1752,6 +1752,13 @@ class WC_Countries {
 		$locale = $this->get_country_locale();

 		if ( isset( $locale[ $country ] ) ) {
+			// wc_array_overlay() only copies keys that exist in the base array, so seed 'hidden' where the locale defines it.
+			foreach ( $locale[ $country ] as $field_key => $locale_field ) {
+				if ( isset( $fields[ $field_key ], $locale_field['hidden'] ) && is_array( $fields[ $field_key ] ) ) {
+					$fields[ $field_key ]['hidden'] = false;
+				}
+			}
+
 			$fields = wc_array_overlay( $fields, $locale[ $country ] );
 		}

diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php
index a189b36db97..d945c5a5b57 100644
--- a/plugins/woocommerce/includes/class-wc-form-handler.php
+++ b/plugins/woocommerce/includes/class-wc-form-handler.php
@@ -195,7 +195,7 @@ class WC_Form_Handler {
 			$value = apply_filters( 'woocommerce_process_myaccount_field_' . $key, $value );

 			// Validation: Required fields.
-			if ( ! empty( $field['required'] ) && empty( $value ) ) {
+			if ( ! empty( $field['required'] ) && empty( $field['hidden'] ) && empty( $value ) ) {
 				/* translators: %s: Field name. */
 				wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), $field['label'] ), 'error', array( 'id' => $key ) );
 			}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php b/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
index 63efd1b575e..9517be8f4cd 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
@@ -51,6 +51,7 @@ class WC_Checkout_Test extends \WC_Unit_Test_Case {
 	public function tearDown(): void {
 		remove_filter( 'woocommerce_checkout_registration_enabled', '__return_true' );
 		delete_option( 'woocommerce_calc_taxes' );
+		WC()->countries->locale = array();

 		foreach ( $this->extra_field_filters as $extra_field_filter ) {
 			remove_filter( 'woocommerce_checkout_fields', $extra_field_filter );
@@ -215,6 +216,40 @@ class WC_Checkout_Test extends \WC_Unit_Test_Case {
 		$this->assertEmpty( $errors->get_error_message( 'shipping_country_validation' ) );
 	}

+	/**
+	 * @testdox 'validate_posted_data' skips the required check for fields hidden via the country locale.
+	 *
+	 * @testWith [true]
+	 *           [false]
+	 *
+	 * @param bool $hidden Whether the locale marks the postcode field as hidden.
+	 */
+	public function test_validate_posted_data_skips_required_check_for_hidden_fields( $hidden ) {
+		$locale_filter = function ( $locale ) use ( $hidden ) {
+			$locale['ES']['postcode']['hidden'] = $hidden;
+			return $locale;
+		};
+		add_filter( 'woocommerce_get_country_locale', $locale_filter );
+		WC()->countries->locale   = array();
+		$_POST['billing_country'] = 'ES';
+
+		$data   = array(
+			'billing_country'           => 'ES',
+			'billing_postcode'          => '',
+			'ship_to_different_address' => false,
+		);
+		$errors = new WP_Error();
+
+		$this->sut->validate_posted_data( $data, $errors );
+
+		$required_error = $errors->get_error_message( 'billing_postcode_required' );
+		if ( $hidden ) {
+			$this->assertEmpty( $required_error, 'Hidden fields should not trigger a required-field error.' );
+		} else {
+			$this->assertNotEmpty( $required_error, 'Visible required fields should still trigger a required-field error.' );
+		}
+	}
+
 	/**
 	 * @testdox 'validate_posted_data' reports a required field error, and doesn't throw, for an empty phone field in a fieldset without a country.
 	 *
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-countries-test.php b/plugins/woocommerce/tests/php/includes/class-wc-countries-test.php
index d1cb705812f..37a8a984c25 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-countries-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-countries-test.php
@@ -18,6 +18,40 @@ class WC_Countries_Test extends \WC_Unit_Test_Case {
 		$this->assertEquals( $expected, wc()->countries->get_country_from_alpha_3_code( $country_code ) );
 	}

+	/**
+	 * @testdox 'get_address_fields' carries a locale-only 'hidden' flag through the merge with the default fields.
+	 *
+	 * @testWith [true]
+	 *           [false]
+	 *
+	 * @param bool $hidden Value of the 'hidden' flag set via the locale filter.
+	 * @return void
+	 */
+	public function test_get_address_fields_includes_hidden_flag_from_locale( $hidden ) {
+		$locale_filter = function ( $locale ) use ( $hidden ) {
+			$locale['ES']['postcode']['hidden'] = $hidden;
+			return $locale;
+		};
+		add_filter( 'woocommerce_get_country_locale', $locale_filter );
+
+		$fields = ( new WC_Countries() )->get_address_fields( 'ES', 'billing_' );
+
+		remove_filter( 'woocommerce_get_country_locale', $locale_filter );
+
+		$this->assertSame( $hidden, $fields['billing_postcode']['hidden'], 'The locale hidden flag should survive the merge with the default fields.' );
+	}
+
+	/**
+	 * @testdox 'get_address_fields' does not add a 'hidden' key when the locale does not define one.
+	 *
+	 * @return void
+	 */
+	public function test_get_address_fields_has_no_hidden_key_when_locale_does_not_define_one() {
+		$fields = ( new WC_Countries() )->get_address_fields( 'ES', 'billing_' );
+
+		$this->assertArrayNotHasKey( 'hidden', $fields['billing_postcode'] );
+	}
+
 	/**
 	 * Provider for `test_get_country_from_alpha_3_code`.
 	 *