Repository: Automattic/woocommerce-payments
Pull request #10770: Handle state field requirements for Express Checkout in countries without states
Author: login: mgascam
Labels: name: pr: ready to merge
Created: 2025-05-09T13:48:03Z | Merged: 2025-05-21T11:15:38Z

Fixes WOOPMNT-4937
Fixes #10728

This PR addresses an issue with Express Checkout (Apple Pay/Google Pay) where certain countries that don't have state/region fields were causing validation errors in WooCommerce.

#### Changes proposed in this Pull Request

1. Add a new constant COUNTRIES_WITHOUT_STATES in Express_Checkout_Element_States class to identify countries where WooCommerce provides a state field but the Express Checkout Element API does not
2. Implement a new filter woocommerce_get_country_locale to modify country locale settings specifically for Express Checkout contexts
3. Make the state field optional and hidden for affected countries during Express Checkout to prevent validation errors

This solution was chosen because:

- It's a non-intrusive way to handle the state field requirements
- It only affects Express Checkout flows, preserving the normal checkout behavior

<!--
Title: A descriptive, yet concise, title.
-->

<!--
Description: Write a brief summary about this PR. As you compose your summary, consider each of these questions and address them if appropriate. Why is this change needed? What does this change do? Were there other solutions you considered? Why did you choose to pursue this solution? Describe any trade-offs you might have had to make.
-->

<!--
Questions for PR author:
- How can this code break?
- What are we doing to make sure this code doesn't break?
-->

<!--
Images or gifs: Include before and after screenshots or gifs/videos when it makes sense.
-->

#### Testing instructions

<!--
Testing instructions: How should this be tested and how can a reviewer test the end-user functionality? Are there known issues that you plan to address in a future PR? Are there any side effects that readers should be aware of?
-->

<!--
Add as many details as possible to help others reproduce the issue and test the fix.
"Before / After" screenshots can also be very helpful when the change is visual.
-->

**Pre-requisites**
1. Enable the `Enable Cart-Token implementation for ECE ` checbox in WCPay Dev tool
2. Enable Apple Pay / Google Pay in Payments > Settings
3. `npm run tube:start` so the Express Checkout buttons are available

**Testing with Google Pay**
1. With Google Chrome, Go to a page where the Express Checkout is available: product, cart or checkout.
2. Click the Google Pay button
4. In the Google Pay modal, select and address from an affected country i.e. Romania

<img width="610" alt="Screenshot 2025-05-09 at 16 04 10" src="https://github.com/user-attachments/assets/c0e829e1-44da-4349-805c-b20a52943180" />

5. Click the Pay now button to proceed with the payment.
6. Check that the order is correctly processed and there are no state/county related errors.

**Testing with Apple Pay**
On top of the other requirements you'll need:

- Use Safari browser for testing.
- You'll need an Apple ID from an affected country or you can use an Apple Sandbox account. There are instructions available here pcrvl0-1yh-p2.

-------------------

- [x] Run `npm run changelog` to add a changelog file, choose `patch` to leave it empty if the change is not significant. You can add multiple changelog files in one PR by running this command a few times. 
- [ ] Covered with tests (or have a good reason not to test in description ☝️)
- [ ] Tested on mobile (or does not apply)

**Post merge**

<!--
Make sure you edit the page for the current release when adding testing instructions.
We often create a blank page ahead of time for the next release.
If this PR need not be QA tested, edit to 'QA Testing Not Applicable'
-->

- [ ] Link to testing instructions from [release testing doc](https://github.com/Automattic/woocommerce-payments/wiki/Release-testing-instructions) following [these instructions](https://github.com/Automattic/woocommerce-payments/wiki/How-to-write-good-manual-testing-scenarios) : _Add link here / 'QA Testing Not Applicable'_
- [ ] Add or update [critical flows](https://github.com/Automattic/woocommerce-payments/wiki/Critical-flows) and [testing instructions for critical flows](https://github.com/Automattic/woocommerce-payments/wiki/Testing-instructions-for-critical-flows), if applicable.
- [ ] Add what's changed (description, screenshot, demo videos etc.) to the release announcement post, if applicable.

Linked issue #10728: Apple Pay checkout fails with WooPayments when billing address country does not require a state field
### Describe the bug
When using WooPayments with Apple Pay, if the billing address from the Apple Wallet belongs to a country where Apple does not require a “state” field (for example, Pakistan), the checkout fails.
The error displayed is:
“There was a problem with the provided billing address: State/county is required.”

This prevents customers from completing their purchase, even though the address is valid for that country per Apple Wallet standards.


### To Reproduce
	1. Set up Apple Pay with a billing address from a country where the state is not a required field (e.g., Pakistan).
	2. Go to a WooCommerce store using WooPayments and Apple Pay.	
	3. Attempt to complete a payment via Apple Pay.
	4. Checkout fails with a “State/county is required” error.

### Screenshots

![Image](https://github.com/user-attachments/assets/5357edfe-c1bc-403c-b957-2b06d1a4ac5a)

### Actual behavior
Apple Pay payment is rejected during checkout with an error message requiring the “state/county” field, even though Apple Wallet does not provide it for addresses where the field is non-existent.

### Expected behavior
Apple Pay checkout should not fail when the billing address country does not require a “state/county” field according to Apple’s regional address standards.
The checkout process should accept the billing address as valid in these cases.


The issue is also reported by a Stripe for WooCommerce user, but I cannot replicate/test it at the moment because Apple Pay is not working in test mode. I will create a separate issue for Stripe.
Linked issue comments: user: login: zain0ul0abiddin
created_at: 2025-04-30T22:05:47Z
body: The issue is also present on Stripe: https://github.com/woocommerce/woocommerce-gateway-stripe/issues/4283
author_association: NONE
user: login: frosso
created_at: 2025-05-05T10:12:38Z
body: Also commented on Slack: p1746439805840939/1746051736.396259-slack-C7U3Y3VMY

WooCommerce Core marks the "State/Country" for Pakistan as a "required" field. The Google Pay/Apple Pay buttons on a WooCommerce store inherit that behavior.

![Image](https://github.com/user-attachments/assets/b1873077-2842-4cdf-83e4-77909852604e)
author_association: CONTRIBUTOR
user: login: frosso
created_at: 2025-05-28T15:54:36Z
body: Fixed by https://github.com/Automattic/woocommerce-payments/pull/10770
author_association: CONTRIBUTOR
user: login: csnlima1
created_at: 2025-07-17T09:24:02Z
body: Hi there @frosso and @mgascam ! We received a contact from a merchant from Lithuania facing this issue. 9985068-zd-a8c

I can see that Lithuania is not on the list from [Stripe PR #4451](https://github.com/woocommerce/woocommerce-gateway-stripe/pull/4451). Is it possible to add it?
author_association: NONE
user: login: frosso
created_at: 2025-07-17T12:17:09Z
body: @csnlima1 sure thing, I verified the behavior and can confirm that LT addresses don't require the field in Google Pay, but they do in WC checkout.

Fixing in https://github.com/Automattic/woocommerce-payments/pull/10946
author_association: CONTRIBUTOR
user: login: csnlima1
created_at: 2025-07-17T15:05:44Z
body: Great! Thanks @frosso! 
author_association: NONE

Diff:
diff --git a/changelog/woopmnt-4937-apple-pay-checkout-fails-with-woopayments-when-billing b/changelog/woopmnt-4937-apple-pay-checkout-fails-with-woopayments-when-billing
new file mode 100644
index 00000000000..47c3ad15ee2
--- /dev/null
+++ b/changelog/woopmnt-4937-apple-pay-checkout-fails-with-woopayments-when-billing
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Handle state field requirements for Express Checkout in countries without states
diff --git a/includes/constants/class-express-checkout-element-states.php b/includes/constants/class-express-checkout-element-states.php
index d2e02532e22..7b1617a9c81 100644
--- a/includes/constants/class-express-checkout-element-states.php
+++ b/includes/constants/class-express-checkout-element-states.php
@@ -1159,4 +1159,42 @@ class Express_Checkout_Element_States {
 		Country_Code::ZAMBIA => [],
 	];
 	// phpcs:enable
+
+	/**
+	 * List of countries where WooCommerce provides a state/region field,
+	 * but the Apple Pay and Google Pay address forms do not include a state/region field.
+	 * For these countries, we need to make the state field optional and hidden to avoid validation errors,
+	 * since the wallet-based checkout may not supply this information.
+	 *
+	 * We could not find an official list of countries that have this characteristic,
+	 * so we are basing this list on libaddressinput: https://github.com/google/libaddressinput, as a proxy.
+	 * The libaddressinput project is used by Chromium based browsers in the native address dialog.
+	 *
+	 * @see https://github.com/google/libaddressinput/wiki/AddressValidationMetadata
+	 */
+	const COUNTRIES_WITHOUT_STATES = [
+		Country_Code::ANGOLA,
+		Country_Code::BANGLADESH,
+		Country_Code::BULGARIA,
+		Country_Code::BENIN,
+		Country_Code::BOLIVIA,
+		Country_Code::DOMINICAN_REPUBLIC,
+		Country_Code::ALGERIA,
+		Country_Code::GHANA,
+		Country_Code::GUATEMALA,
+		Country_Code::HUNGARY,
+		Country_Code::KENYA,
+		Country_Code::LAOS,
+		Country_Code::LIBERIA,
+		Country_Code::MOLDOVA,
+		Country_Code::NAMIBIA,
+		Country_Code::NEPAL,
+		Country_Code::PAKISTAN,
+		Country_Code::PARAGUAY,
+		Country_Code::ROMANIA,
+		Country_Code::TANZANIA,
+		Country_Code::UGANDA,
+		Country_Code::SOUTH_AFRICA,
+		Country_Code::ZAMBIA,
+	];
 }
diff --git a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
index 6a0aeb78637..ba90ad38efd 100644
--- a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
+++ b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
@@ -66,6 +66,7 @@ public function init() {
 				2
 			);
 			add_filter( 'rest_pre_dispatch', [ $this, 'tokenized_cart_store_api_address_normalization' ], 10, 3 );
+			add_filter( 'woocommerce_get_country_locale', [ $this, 'modify_country_locale_for_express_checkout' ], 20 );
 		}
 	}
 
@@ -606,4 +607,51 @@ private function transform_ece_address_postcode_data( $address ) {
 
 		return $address;
 	}
+
+	/**
+	 * Modify country locale settings to handle express checkout address requirements.
+	 *
+	 * @param array $locales Array of country locale settings.
+	 * @return array Modified locales array.
+	 */
+	public function modify_country_locale_for_express_checkout( $locales ) {
+		// Only modify locale settings if this is an express checkout AJAX request.
+		if ( ! $this->is_express_checkout_context() ) {
+			return $locales;
+		}
+
+		include_once WCPAY_ABSPATH . 'includes/constants/class-express-checkout-element-states.php';
+
+		// For countries that don't have state fields, make the state field optional.
+		foreach ( \WCPay\Constants\Express_Checkout_Element_States::COUNTRIES_WITHOUT_STATES as $country_code ) {
+			$locales[ $country_code ]['state']['required'] = false;
+		}
+
+		return $locales;
+	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	private function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! WC_Payments_Utils::is_store_api_request() ) {
+			return false;
+		}
+
+		// Check for the 'X-WooPayments-Tokenized-Cart' header using superglobals.
+		if ( 'true' !== sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WOOPAYMENTS_TOKENIZED_CART'] ?? '' ) ) ) {
+			return false;
+		}
+
+		// Verify the nonce from the 'X-WooPayments-Tokenized-Cart-Nonce' header using superglobals.
+		$nonce = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WOOPAYMENTS_TOKENIZED_CART_NONCE'] ?? '' ) );
+		if ( ! wp_verify_nonce( $nonce, 'woopayments_tokenized_cart_nonce' ) ) {
+			return false;
+		}
+
+		return true;
+	}
 }


Reviews:
user: login: htdat
body: I am only manually testing with Google Pay but I can not replicate the problem mentioned in the Linear issue. 

Instead, I only get this error - [screenshot](https://github.com/user-attachments/assets/5f8f872a-67f8-4b54-97a9-d776eb0d7e32): 
![Markup on 2025-05-15 at 14:01:49](https://github.com/user-attachments/assets/2db271f3-699f-4bfa-bef7-9245cfe1bd7f)



> The express checkout is not supported in United States (US) because some required fields couldn't be verified. Please proceed to the checkout page and try again. Shipping County is a required field.

When applied this PR, I am still getting the same error. 

Note that: I chose both my Woo customer account's billing country and the address on Google Pay model are Romania. 
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T07:03:13Z
user: login: frosso
body: Great work with using that filter for the Store API request when coming from the ECE.
I tested the changes locally, but I encountered some issues.

On `develop`, this is the error I get with a romanian address using Apple Pay:
<img width="1137" alt="Screenshot 2025-05-16 at 4 41 47 PM" src="https://github.com/user-attachments/assets/3364735a-84ab-41f1-b570-76ac487ff781" />

On this branch, instead, this is the error I get:
<img width="1145" alt="Screenshot 2025-05-16 at 4 38 12 PM" src="https://github.com/user-attachments/assets/042bc41c-87b0-4b29-8d5d-e19a6a8903da" />

I got this address from [the random address generator](https://www.fakeaddressgenerator.com/All_countries/address/country/Romania):
<img width="825" alt="Screenshot 2025-05-16 at 4 46 06 PM" src="https://github.com/user-attachments/assets/d2b7ac02-ddbd-4413-aed5-bcc0712fda23" />

And I entered it in Apple Pay:
<img width="706" alt="Screenshot 2025-05-16 at 4 45 49 PM" src="https://github.com/user-attachments/assets/37c8ff49-e212-4718-8827-f991a08f3aa2" />
<img width="376" alt="Screenshot 2025-05-16 at 4 45 01 PM" src="https://github.com/user-attachments/assets/d1a6731d-8ed4-40b1-b730-becfc9efe0eb" />
<img width="390" alt="Screenshot 2025-05-16 at 4 44 40 PM" src="https://github.com/user-attachments/assets/b3e75d9e-8d11-4d42-a303-26d73663d63d" />


I also tried with a PK address, as outlined in Slack, and I get a similar error:
<img width="1120" alt="Screenshot 2025-05-16 at 4 50 20 PM" src="https://github.com/user-attachments/assets/d6c1922d-bbd5-487e-af09-85befa579711" />
<img width="390" alt="Screenshot 2025-05-16 at 4 51 29 PM" src="https://github.com/user-attachments/assets/7ccc37cc-b413-4db9-83c9-df0cb8ee05df" />

So maybe we need to skip validation also for the zip code?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T14:51:49Z
user: login: frosso
body: A few suggestions for consideration, but nothing major.
I tested this again, and it also works with the Romanan user that is part of the Google Pay test addresses.
I like the workaround you implemented that uses the `woocommerce_get_country_locale` filter to mark the ZIP code as "optional" for some countries when the ECE buttons are used as payment methods.
You also convinced me and made a good argument about the scenario where the ZIP code is missing.

In https://github.com/Automattic/woocommerce-payments/pull/10784 I implemented the removal of the Tokenized ECE feature flag, so that it shouldn't create confusion anymore :)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T16:13:56Z
user: login: mgascam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T09:58:35Z
user: login: mgascam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:01:45Z
user: login: mgascam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:05:03Z
user: login: mgascam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:16:14Z
user: login: mgascam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:25:57Z
user: login: frosso
body: Tested again, still looking great! I can confirm that on `develop` I get the error message when attempting to checkout, but I don't get the message on this branch and I can checkout successfully!
<img width="779" alt="Screenshot 2025-05-21 at 12 39 02 PM" src="https://github.com/user-attachments/assets/8408c4cf-b39f-43d6-a2e6-22aec97bb963" />

Thank you again for the changes and for your patience with this! :shipit: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:39:24Z
user: login: frosso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:41:10Z

Review comments:
diff_hunk: @@ -1159,4 +1159,35 @@ class Express_Checkout_Element_States {
 		Country_Code::ZAMBIA => [],
 	];
 	// phpcs:enable
+
+	/**
+	 * List of countries where WooCommerce provides a state/region field but the Express Checkout
+	 * Element API does not. For these countries, we need to make the
+	 * state field optional and hidden to avoid validation errors.
path: includes/constants/class-express-checkout-element-states.php
user: login: htdat
body: What's the source for Express Checkout Element API? I would like to have a look at them. 

If Apple Pay / Google Pay do not require `state/county` for a specific country, Woo core should also revisit itself? 
created_at: 2025-05-15T06:08:30Z
author_association: MEMBER
diff_hunk: @@ -1250,4 +1249,109 @@ protected function calculate_shipping( $address = [] ) {
 
 		WC()->shipping->calculate_shipping( $packages );
 	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	public function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! $this->is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
+	}
+
+	/**
+	 * Check if the current request is an express checkout API request.
+	 *
+	 * @return bool True if this is an express checkout API request, false otherwise.
+	 */
+	private function is_store_api_request() {
path: includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
user: login: htdat
body: We have already had a few utils for this purpose, I don't think we need to have it here such as: 

- WC_Payments_Utils::is_store_api_request
- WCPay\MultiCurrency\Utils::is_store_api_request - from Multicurrency module, we should not use it as it's not always available but at least we can use `WC_Payments_Utils::is_store_api_request`. 
- WC()->is_store_api_request() - from Woo core. 

created_at: 2025-05-15T06:26:10Z
author_association: MEMBER
diff_hunk: @@ -1250,4 +1249,109 @@ protected function calculate_shipping( $address = [] ) {
 
 		WC()->shipping->calculate_shipping( $packages );
 	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	public function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! $this->is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
+	}
+
+	/**
+	 * Check if the current request is an express checkout API request.
+	 *
+	 * @return bool True if this is an express checkout API request, false otherwise.
+	 */
+	private function is_store_api_request() {
+		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+			return false;
+		}
+
+		$request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
+		return strpos( $request_uri, '/wp-json/wc/store/v1/checkout' ) !== false;
+	}
+
+	/**
+	 * Check if the payment data indicates an express payment type.
+	 *
+	 * @param array $payment_data The payment data to check.
+	 * @return bool True if this is an express payment, false otherwise.
+	 */
+	private function is_express_payment_type( $payment_data ) {
+		if ( ! is_array( $payment_data ) ) {
+			return false;
+		}
+
+		foreach ( $payment_data as $payment_data_item ) {
+			if ( ! is_array( $payment_data_item ) || ! isset( $payment_data_item['key'] ) || ! isset( $payment_data_item['value'] ) ) {
+				continue;
+			}
+
+			if ( 'payment_request_type' === $payment_data_item['key'] || 'express_payment_type' === $payment_data_item['key'] ) {
+				return in_array( $payment_data_item['value'], [ 'apple_pay', 'google_pay' ], true );
+			}
path: includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
user: login: htdat
body: The logic within this foreach loop can be simplified like the following: 

```php
			$key = $payment_data_item['key'] ?? null;
			$value = $payment_data_item['value'] ?? null;

			if (
				in_array( $key, ['payment_request_type', 'express_payment_type'], true )
				&& in_array( $value, [ 'apple_pay', 'google_pay' ], true )
			) {
				return true;
			}
``` 

created_at: 2025-05-15T06:37:17Z
author_association: MEMBER
diff_hunk: @@ -1250,4 +1249,109 @@ protected function calculate_shipping( $address = [] ) {
 
 		WC()->shipping->calculate_shipping( $packages );
 	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	public function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! $this->is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
+	}
+
+	/**
+	 * Check if the current request is an express checkout API request.
+	 *
+	 * @return bool True if this is an express checkout API request, false otherwise.
+	 */
+	private function is_store_api_request() {
+		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+			return false;
+		}
+
+		$request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
+		return strpos( $request_uri, '/wp-json/wc/store/v1/checkout' ) !== false;
+	}
+
+	/**
+	 * Check if the payment data indicates an express payment type.
+	 *
+	 * @param array $payment_data The payment data to check.
+	 * @return bool True if this is an express payment, false otherwise.
+	 */
+	private function is_express_payment_type( $payment_data ) {
+		if ( ! is_array( $payment_data ) ) {
+			return false;
+		}
+
+		foreach ( $payment_data as $payment_data_item ) {
+			if ( ! is_array( $payment_data_item ) || ! isset( $payment_data_item['key'] ) || ! isset( $payment_data_item['value'] ) ) {
+				continue;
+			}
+
+			if ( 'payment_request_type' === $payment_data_item['key'] || 'express_payment_type' === $payment_data_item['key'] ) {
+				return in_array( $payment_data_item['value'], [ 'apple_pay', 'google_pay' ], true );
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Modify country locale settings to handle express checkout address requirements.
+	 *
+	 * @param array $locales Array of country locale settings.
+	 * @return array Modified locales array.
+	 */
+	public function modify_country_locale( $locales ) {
+		// Only modify locale settings if we're in an express checkout context.
+		if ( ! $this->is_express_checkout_context() ) {
+			return $locales;
+		}
+
+		// Apply country locale overrides from Express Checkout Element States.
+		$overrides = $this->get_locale_overrides();
+		foreach ( $overrides as $country_code => $override ) {
+			$locales[ $country_code ] = $override;
+		}
path: includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
user: login: htdat
body: IMO, we would only change the most relevant arg `required` of `$locales`, so we would try to be as much granular as possible. 

```suggestion
		// No longer needs the `get_locale_overrides` method. 
		foreach ( Express_Checkout_Element_States::COUNTRIES_WITHOUT_STATES as $country_code ) {
			$locales[ $country_code ]['state']['required'] = false;
		}
```
created_at: 2025-05-15T06:41:27Z
author_association: MEMBER
diff_hunk: @@ -95,6 +95,7 @@ public function init() {
 		add_action( 'before_woocommerce_pay_form', [ $this, 'display_pay_for_order_page_html' ], 1 );
 		add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 );
 		add_action( 'woocommerce_checkout_order_processed', [ $this->express_checkout_helper, 'add_order_payment_method_title' ], 10, 2 );
+		add_filter( 'woocommerce_get_country_locale', [ $this->express_checkout_helper, 'modify_country_locale' ], 20 );
path: includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
user: login: frosso
body: There is a `includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php`, which is used to handle most (if not all?) of the incoming AJAX requests for the express checkout buttons. I think `WC_Payments_Express_Checkout_Ajax_Handler` would be a good spot for this filter, since it's used on AJAX requests.

What do you think about using that class for this filter?
created_at: 2025-05-16T14:29:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1250,4 +1249,109 @@ protected function calculate_shipping( $address = [] ) {
 
 		WC()->shipping->calculate_shipping( $packages );
 	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	public function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! $this->is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
+	}
+
+	/**
+	 * Check if the current request is an express checkout API request.
+	 *
+	 * @return bool True if this is an express checkout API request, false otherwise.
+	 */
+	private function is_store_api_request() {
path: includes/express-checkout/class-wc-payments-express-checkout-button-helper.php
user: login: frosso
body: I agree - I think that `\WC_Payments_Utils::is_store_api_request()` could be used instead, to be consistent with the other areas of the codebase?

I think we have `\WC_Payments_Utils::is_store_api_request()` because `WC()->is_store_api_request()` in WC Core is relatively new.
created_at: 2025-05-16T14:31:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1159,4 +1159,35 @@ class Express_Checkout_Element_States {
 		Country_Code::ZAMBIA => [],
 	];
 	// phpcs:enable
+
+	/**
+	 * List of countries where WooCommerce provides a state/region field but the Express Checkout
+	 * Element API does not. For these countries, we need to make the
+	 * state field optional and hidden to avoid validation errors.
path: includes/constants/class-express-checkout-element-states.php
user: login: frosso
body: If possible, it would be great to document where this list of countries has been retrieved :) did you manually do through the countries in Apple Pay, and looked at each that didn't provide a state? or were you able to get some documentation about this?
created_at: 2025-05-20T15:51:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -488,6 +488,7 @@ public static function init() {
 		include_once __DIR__ . '/constants/class-payment-capture-type.php';
 		include_once __DIR__ . '/constants/class-payment-method.php';
 		include_once __DIR__ . '/constants/class-track-events.php';
+		include_once __DIR__ . '/constants/class-express-checkout-element-states.php';
path: includes/class-wc-payments.php
user: login: frosso
body: Why move this files from a conditional `include_once` into being always included?
I think one of the benefits from conditionally including it was to avoid it being loaded when not needed, since it is a large key-value-pair that would otherwise need to be loaded in memory for each request.
I was looking at the query monitor, and noticed an increase of ~500KB of memory usage, compared to `develop`.
created_at: 2025-05-20T15:59:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -95,6 +95,7 @@ public function init() {
 		add_action( 'before_woocommerce_pay_form', [ $this, 'display_pay_for_order_page_html' ], 1 );
 		add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 );
 		add_action( 'woocommerce_checkout_order_processed', [ $this->express_checkout_helper, 'add_order_payment_method_title' ], 10, 2 );
+		add_filter( 'woocommerce_get_country_locale', [ $this->express_checkout_ajax_handler, 'modify_country_locale_for_express_checkout' ], 20 );
path: includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
user: login: frosso
body: What do you think about moving this filter registration to `includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php`, in its `init` method? This way the `modify_country_locale_for_express_checkout` method` can stay colocated with the filter registration.
created_at: 2025-05-20T16:05:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -606,4 +606,76 @@ private function transform_ece_address_postcode_data( $address ) {
 
 		return $address;
 	}
+
+	/**
+	 * Modify country locale settings to handle express checkout address requirements.
+	 *
+	 * @param array $locales Array of country locale settings.
+	 * @return array Modified locales array.
+	 */
+	public function modify_country_locale_for_express_checkout( $locales ) {
+		// Only modify locale settings if this is an express checkout AJAX request.
+		if ( ! $this->is_express_checkout_context() ) {
+			return $locales;
+		}
+
+		// For countries that don't have state fields, make the state field optional.
+		foreach ( \WCPay\Constants\Express_Checkout_Element_States::COUNTRIES_WITHOUT_STATES as $country_code ) {
+			$locales[ $country_code ]['state']['required'] = false;
+		}
+
+		return $locales;
+	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	private function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! WC_Payments_Utils::is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
path: includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
user: login: frosso
body: (minor and optional :) ) one alternative to these checks could be to implement similar checks as the ones defined on top of the `tokenized_cart_store_api_address_normalization` method: https://github.com/Automattic/woocommerce-payments/blob/c8bad250e705bfa62c68037de94bd3332ca25ce9/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php#L500-L508

This way, you don't need to inspect the request's payment data, and you can use the nonce validation that is already used in other methods.
created_at: 2025-05-20T16:09:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -488,6 +488,7 @@ public static function init() {
 		include_once __DIR__ . '/constants/class-payment-capture-type.php';
 		include_once __DIR__ . '/constants/class-payment-method.php';
 		include_once __DIR__ . '/constants/class-track-events.php';
+		include_once __DIR__ . '/constants/class-express-checkout-element-states.php';
path: includes/class-wc-payments.php
user: login: mgascam
body: Great catch, thanks for pointing it out since I was not aware of the drawback. I'm going back to conditionally including it in this commit [bf84367](https://github.com/Automattic/woocommerce-payments/pull/10770/commits/bf84367a84472a4286ee6779306fe2ee051d4161)
created_at: 2025-05-21T09:58:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -95,6 +95,7 @@ public function init() {
 		add_action( 'before_woocommerce_pay_form', [ $this, 'display_pay_for_order_page_html' ], 1 );
 		add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 );
 		add_action( 'woocommerce_checkout_order_processed', [ $this->express_checkout_helper, 'add_order_payment_method_title' ], 10, 2 );
+		add_filter( 'woocommerce_get_country_locale', [ $this->express_checkout_ajax_handler, 'modify_country_locale_for_express_checkout' ], 20 );
path: includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
user: login: mgascam
body: I agree, the thing is I tried once and the filter was not being triggered. But probably I was doing something wrong. I tried again and it worked fine so I refactored it in this commit [7937c7c](https://github.com/Automattic/woocommerce-payments/pull/10770/commits/7937c7c76f49505a74785ce34526431ac8220e8c)
created_at: 2025-05-21T10:01:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -606,4 +606,76 @@ private function transform_ece_address_postcode_data( $address ) {
 
 		return $address;
 	}
+
+	/**
+	 * Modify country locale settings to handle express checkout address requirements.
+	 *
+	 * @param array $locales Array of country locale settings.
+	 * @return array Modified locales array.
+	 */
+	public function modify_country_locale_for_express_checkout( $locales ) {
+		// Only modify locale settings if this is an express checkout AJAX request.
+		if ( ! $this->is_express_checkout_context() ) {
+			return $locales;
+		}
+
+		// For countries that don't have state fields, make the state field optional.
+		foreach ( \WCPay\Constants\Express_Checkout_Element_States::COUNTRIES_WITHOUT_STATES as $country_code ) {
+			$locales[ $country_code ]['state']['required'] = false;
+		}
+
+		return $locales;
+	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	private function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! WC_Payments_Utils::is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
path: includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
user: login: mgascam
body: I think that's a better way thanks for the suggestion. I tried the same approach in this commit [96d5616](https://github.com/Automattic/woocommerce-payments/pull/10770/commits/96d561697567a38fa649d9e111c6c629f36a05a0). The only difference is that   we don't have direct access to the request in the `woocommerce_get_country_locale` filter, so I'm using superglobals to access it. Please have a look and let me know what you think.
created_at: 2025-05-21T10:05:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1159,4 +1159,35 @@ class Express_Checkout_Element_States {
 		Country_Code::ZAMBIA => [],
 	];
 	// phpcs:enable
+
+	/**
+	 * List of countries where WooCommerce provides a state/region field but the Express Checkout
+	 * Element API does not. For these countries, we need to make the
+	 * state field optional and hidden to avoid validation errors.
path: includes/constants/class-express-checkout-element-states.php
user: login: mgascam
body: Thanks for double-checking the list of countries. I tried to find some official documentation from Apple or Google but couldn't find anything. Fortunately, I found a reference to libaddressinput in the [codebase](https://github.com/Automattic/woocommerce-payments/blob/737886b4517db5dec4a3f1cb9fe4f9fb89460880/includes/constants/class-express-checkout-element-states.php#L16), along with a list of countries where the address form doesn’t include a state field. I used that list as a starting point and manually confirmed that the native address form in the Mac wallet omits the state field for those countries.
created_at: 2025-05-21T10:16:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1159,4 +1159,35 @@ class Express_Checkout_Element_States {
 		Country_Code::ZAMBIA => [],
 	];
 	// phpcs:enable
+
+	/**
+	 * List of countries where WooCommerce provides a state/region field but the Express Checkout
+	 * Element API does not. For these countries, we need to make the
+	 * state field optional and hidden to avoid validation errors.
path: includes/constants/class-express-checkout-element-states.php
user: login: mgascam
body: > What's the source for Express Checkout Element API? I would like to have a look at them.

That comment was a bit misleading — it’s not the Express Checkout Element API, but rather the native address form used to enter address details that lacks the state field for some countries. I’ve updated the comment to clarify this.

> If Apple Pay / Google Pay do not require state/county for a specific country, Woo core should also revisit itself?

Great point, that was my initial thought as well. I tested making the state field optional in Woo core, and it did resolve the issue. However, I was hesitant to make a core change for something that feels quite specific to WooPayments and Apple Pay / Google Pay. So instead, I opted to use the `woocommerce_get_country_locale` hook to adjust field requirements only during Apple/Google Pay checkout.
created_at: 2025-05-21T10:25:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -606,4 +606,76 @@ private function transform_ece_address_postcode_data( $address ) {
 
 		return $address;
 	}
+
+	/**
+	 * Modify country locale settings to handle express checkout address requirements.
+	 *
+	 * @param array $locales Array of country locale settings.
+	 * @return array Modified locales array.
+	 */
+	public function modify_country_locale_for_express_checkout( $locales ) {
+		// Only modify locale settings if this is an express checkout AJAX request.
+		if ( ! $this->is_express_checkout_context() ) {
+			return $locales;
+		}
+
+		// For countries that don't have state fields, make the state field optional.
+		foreach ( \WCPay\Constants\Express_Checkout_Element_States::COUNTRIES_WITHOUT_STATES as $country_code ) {
+			$locales[ $country_code ]['state']['required'] = false;
+		}
+
+		return $locales;
+	}
+
+	/**
+	 * Check if we're in an express checkout context.
+	 *
+	 * @return bool True if we're in an express checkout context, false otherwise.
+	 */
+	private function is_express_checkout_context() {
+		// Only proceed if this is a Store API request.
+		if ( ! WC_Payments_Utils::is_store_api_request() ) {
+			return false;
+		}
+
+		// Check the payment data in the request.
+		$request_body = file_get_contents( 'php://input' );
+		$request_data = json_decode( $request_body, true );
+
+		if ( ! isset( $request_data['payment_data'] ) ) {
+			return false;
+		}
+
+		return $this->is_express_payment_type( $request_data['payment_data'] );
path: includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php
user: login: frosso
body: Using the superglobals seems fine, since `$request` isn't filled yet. We need to use those same superglobals in `WC_Payments_Payment_Request_Session` for the same reason :) 
created_at: 2025-05-21T10:41:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: botwoo
created_at: 2025-05-09T13:51:59Z
body: ### Test the build 

#### Option 1. Jetpack Beta

- Install and activate [Jetpack Beta](https://jetpack.com/download-jetpack-beta/).
- Use this build by searching for PR number `10770` or branch name `woopmnt-4937-apple-pay-checkout-fails-with-woopayments-when-billing` in your-test.site/wp-admin/admin.php?page=jetpack-beta&plugin=woocommerce-payments          

#### Option 2. Jurassic Ninja - available for logged-in A12s

:rocket: [Launch a JN site with this branch](https://jurassic.ninja/create/?jetpack-beta&shortlived&nojetpack&woocommerce&woocommerce-payments-dev-tools&branches.woocommerce-payments=woopmnt-4937-apple-pay-checkout-fails-with-woopayments-when-billing) :rocket:

:information_source: Install this [Tampermonkey script](https://github.com/Automattic/woocommerce-payments/tree/develop/bin/wcpay-live-branches) to get more options.

---

Build info: 

- Latest commit: e87d9a4f6aa79c224c813507503dfd62c1807e5f
- Build time: 2025-05-21 11:06:42 UTC

_Note: the build is updated when a new commit is pushed to this PR._
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-09T13:55:12Z
body: **Size Change:** 0 B 

**Total Size:** 1.26 MB



<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `release/woocommerce-payments/assets/css/admin.css` | 1.45 kB |
| `release/woocommerce-payments/assets/css/admin.rtl.css` | 1.45 kB |
| `release/woocommerce-payments/assets/css/success.css` | 1.06 kB |
| `release/woocommerce-payments/assets/css/success.rtl.css` | 1.06 kB |
| `release/woocommerce-payments/dist/blocks-checkout-rtl.css` | 2.69 kB |
| `release/woocommerce-payments/dist/blocks-checkout.css` | 2.69 kB |
| `release/woocommerce-payments/dist/blocks-checkout.js` | 55.8 kB |
| `release/woocommerce-payments/dist/cart-block.js` | 17.3 kB |
| `release/woocommerce-payments/dist/cart.js` | 5.73 kB |
| `release/woocommerce-payments/dist/checkout-rtl.css` | 1.08 kB |
| `release/woocommerce-payments/dist/checkout.css` | 1.08 kB |
| `release/woocommerce-payments/dist/checkout.js` | 34.1 kB |
| `release/woocommerce-payments/dist/express-checkout-rtl.css` | 367 B |
| `release/woocommerce-payments/dist/express-checkout.css` | 367 B |
| `release/woocommerce-payments/dist/express-checkout.js` | 15.8 kB |
| `release/woocommerce-payments/dist/frontend-tracks.js` | 854 B |
| `release/woocommerce-payments/dist/index-rtl.css` | 19.6 kB |
| `release/woocommerce-payments/dist/index.css` | 19.6 kB |
| `release/woocommerce-payments/dist/index.js` | 233 kB |
| `release/woocommerce-payments/dist/multi-currency-analytics.js` | 1.08 kB |
| `release/woocommerce-payments/dist/multi-currency-rtl.css` | 4.39 kB |
| `release/woocommerce-payments/dist/multi-currency-switcher-block.js` | 60.9 kB |
| `release/woocommerce-payments/dist/multi-currency.css` | 4.39 kB |
| `release/woocommerce-payments/dist/multi-currency.js` | 56.6 kB |
| `release/woocommerce-payments/dist/order-rtl.css` | 740 B |
| `release/woocommerce-payments/dist/order.css` | 740 B |
| `release/woocommerce-payments/dist/order.js` | 42.6 kB |
| `release/woocommerce-payments/dist/payment-gateways-rtl.css` | 1.34 kB |
| `release/woocommerce-payments/dist/payment-gateways.css` | 1.34 kB |
| `release/woocommerce-payments/dist/payment-gateways.js` | 37.6 kB |
| `release/woocommerce-payments/dist/plugins-page-rtl.css` | 375 B |
| `release/woocommerce-payments/dist/plugins-page.css` | 375 B |
| `release/woocommerce-payments/dist/plugins-page.js` | 20.1 kB |
| `release/woocommerce-payments/dist/product-details-rtl.css` | 433 B |
| `release/woocommerce-payments/dist/product-details.css` | 436 B |
| `release/woocommerce-payments/dist/product-details.js` | 12.6 kB |
| `release/woocommerce-payments/dist/settings-rtl.css` | 11.8 kB |
| `release/woocommerce-payments/dist/settings.css` | 11.7 kB |
| `release/woocommerce-payments/dist/settings.js` | 221 kB |
| `release/woocommerce-payments/dist/subscription-edit-page.js` | 703 B |
| `release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css` | 524 B |
| `release/woocommerce-payments/dist/subscription-product-onboarding-modal.css` | 524 B |
| `release/woocommerce-payments/dist/subscription-product-onboarding-modal.js` | 20.2 kB |
| `release/woocommerce-payments/dist/subscription-product-onboarding-toast.js` | 730 B |
| `release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css` | 120 B |
| `release/woocommerce-payments/dist/subscriptions-empty-state.css` | 120 B |
| `release/woocommerce-payments/dist/subscriptions-empty-state.js` | 19.3 kB |
| `release/woocommerce-payments/dist/success.js` | 6.03 kB |
| `release/woocommerce-payments/dist/tokenized-express-checkout-rtl.css` | 367 B |
| `release/woocommerce-payments/dist/tokenized-express-checkout.css` | 367 B |
| `release/woocommerce-payments/dist/tokenized-express-checkout.js` | 17.1 kB |
| `release/woocommerce-payments/dist/tos-rtl.css` | 235 B |
| `release/woocommerce-payments/dist/tos.css` | 235 B |
| `release/woocommerce-payments/dist/tos.js` | 21.8 kB |
| `release/woocommerce-payments/dist/woopay-direct-checkout.js` | 6.13 kB |
| `release/woocommerce-payments/dist/woopay-express-button.js` | 23.3 kB |
| `release/woocommerce-payments/dist/woopay-rtl.css` | 4.31 kB |
| `release/woocommerce-payments/dist/woopay.css` | 4.28 kB |
| `release/woocommerce-payments/dist/woopay.js` | 71.1 kB |
| `release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css` | 625 B |
| `release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js` | 814 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js` | 2.46 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-assets/build/jetpack-script-data.js` | 957 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js` | 1.02 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/script-data.js` | 69 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/babel.config.js` | 163 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.css` | 2.47 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.js` | 14.3 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.rtl.css` | 2.47 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.css` | 10.1 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.js` | 29.7 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.rtl.css` | 10.1 kB |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.css` | 198 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.js` | 280 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.rtl.css` | 198 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.css` | 625 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.js` | 333 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.rtl.css` | 626 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-users.js` | 417 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-users-connection.js` | 161 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js` | 521 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js` | 585 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.css` | 215 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.js` | 521 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css` | 721 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.js` | 412 B |
| `release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-users.js` | 625 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css` | 1.04 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-empty-state.css` | 294 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css` | 408 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css` | 3.59 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css` | 301 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css` | 746 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css` | 574 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css` | 414 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js` | 543 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js` | 9.4 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js` | 6.78 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js` | 3.84 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js` | 545 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js` | 2.52 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js` | 22.2 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js` | 11.7 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/payment-method-restrictions.js` | 1.29 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/wcs-meta-boxes-order.js` | 507 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js` | 358 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js` | 428 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/view-subscription.js` | 1.38 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/wcs-cart.js` | 782 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js` | 1.09 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js` | 1.26 kB |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css` | 391 B |
| `release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js` | 3.04 kB |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: mgascam
created_at: 2025-05-16T16:00:40Z
body: Thanks @frosso and @htdat for your review and testing, much appreciated!. I addressed most of your comments already, but still need to look into the issues you found. Will let you know when I'm ready for a new review round.
author_association: CONTRIBUTOR
user: login: mgascam
created_at: 2025-05-19T10:08:09Z
body: > I am only manually testing with Google Pay but I can not replicate the problem mentioned in the Linear issue.
> 
> Instead, I only get this error - [screenshot](https://github.com/user-attachments/assets/5f8f872a-67f8-4b54-97a9-d776eb0d7e32):

Thanks for testing, @htdat! I was able to reproduce the same issue you described by disabling the `Enable Cart-Token implementation for ECE` feature flag in the WCPay Dev tool:

<img width="801" alt="Screenshot 2025-05-19 at 11 54 27" src="https://github.com/user-attachments/assets/fb6be3d6-82e3-48b1-be46-e5b7a44c5a3c" />

Looking at this [method](https://github.com/Automattic/woocommerce-payments/blob/1e426d03c169582c100c23780f85237da85db366/includes/class-wc-payments-features.php#L51) — and also this [PR](https://github.com/Automattic/woocommerce-payments/pull/10415) — this feature is enabled by default unless explicitly set to false. The WCPay Dev tool disables it by default, which doesn't match the current behavior in production.

Could you please try enabling the Enable Cart-Token implementation for ECE flag in the dev tool and check again? I’ve also updated the PR description to clarify this.

Additionally, I’ll look into enabling this flag by default in the WCPay Dev tool so it aligns with production behavior.
author_association: CONTRIBUTOR
user: login: mgascam
created_at: 2025-05-19T10:28:27Z
body: > So maybe we need to skip validation also for the zip code?

Thanks for testing, @frosso! In my opinion, this looks like an inconsistent or buggy implementation in the Apple Wallet address form. I would expect the form validation to fail due to the missing postal code, since the field is present in the form and not marked as optional.

I tried your suggestion of using the filter to also mark the postal code as optional, and it does work — the payment goes through and the order is placed. However, I’m hesitant to adopt this change because the postal code seems important for merchants, especially for shipping purposes.

I see this as different from skipping the state validation for two main reasons:

The postal code is more critical, as the state can often be inferred from it.

Customers can manually add a postal code to their Apple Wallet address, whereas the state field isn’t even present, so there’s no way for customers to fix the issue themselves.

What do you think?
author_association: CONTRIBUTOR
user: login: mgascam
created_at: 2025-05-21T10:27:35Z
body: @frosso @htdat I believe I addressed all your latest comments, ready for a new review round when you have a moment 🙏 
author_association: CONTRIBUTOR