Skip to content

fix(blocks): fix express checkout address validation in modal checkout#597

Open
rbcorrales wants to merge 7 commits into
releasefrom
hotfix/remove-hidden-checkout-fields
Open

fix(blocks): fix express checkout address validation in modal checkout#597
rbcorrales wants to merge 7 commits into
releasefrom
hotfix/remove-hidden-checkout-fields

Conversation

@rbcorrales

@rbcorrales rbcorrales commented Jul 9, 2026

Copy link
Copy Markdown
Member

All Submissions:

Changes proposed in this Pull Request:

Express checkout wallets (Apple Pay, Google Pay) in the modal checkout submit to the Store API (wc/store/v1/checkout), whose address validation rejects any state value not in the country's list. Wallets can supply values the buyer never sees and cannot correct (for example a locality in the state slot), hard-failing the payment on a field the site is configured not to collect.

The fix is scoped strictly to the modal checkout, so standard Woo checkout flows keep stock behavior (per review discussion, the modal-only scoping of the billing fields setting is deliberate):

  • A rest_pre_dispatch scrub drops configured-off state/postcode values from modal-originated wc/store/v1/checkout requests, and only when the value would fail validation anyway; valid wallet values pass through untouched and are stored on the order. Modal origin is detected via the referer (the modal iframe URL carries modal_checkout=1), since express submissions carry no request params.
  • A woocommerce_get_country_locale filter relaxes the required flag for configured-off state/postcode on modal-originated requests, so the scrubbed value's absence is accepted. It never applies when the cart needs a shipping address.
  • The existing modal-only field removal is unchanged; its docblock now records that the modal-only scoping is by design.

Everything outside the modal is byte-for-byte stock: the standard Woo checkout page, the blocks checkout, express buttons on product/cart pages, My Account flows, and any cart needing a shipping address.

How to test the changes in this Pull Request:

  1. In Audience > Configuration > Checkout & Payment > Billing Fields, uncheck the state and address fields (keep name and email) and save.
  2. Open the modal checkout via a donate block and confirm the form shows only the configured fields.
  3. Complete an express payment (Apple Pay / Google Pay) in the modal using a wallet address whose state the store rejects (to simulate, temporarily filter woocommerce_states to replace the state list for your wallet's country). Confirm checkout completes with the invalid state dropped, instead of failing with "The provided state (...) is not valid".
  4. Open the standard checkout page with a virtual product in the cart. Confirm the full address field set renders despite the reduced configuration.
  5. On that standard checkout page, attempt the same express payment. Confirm stock behavior (the address validation error appears), since the fix must not apply outside the modal.
  6. Add a physical (shippable) product and confirm standard checkout still renders and requires the full address field set.
  7. Pay for an order from My Account and confirm those fields are unchanged.
  8. Clear the billing fields configuration (default field set) and confirm modal express checkout validates addresses as stock.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

rbcorrales and others added 2 commits July 9, 2026 14:14
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 21:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates newspack-blocks’ modal checkout integration to ensure billing fields configured off in Audience → Payment are unregistered anywhere WooCommerce renders/validates checkout fields (not just during modal checkout), preventing express wallet checkouts from failing validation on hidden fields (NPPM-2937).

Changes:

  • Expand Modal_Checkout::woocommerce_checkout_fields() so configured-off billing fields are removed for standard checkout contexts too, while preserving the existing My Account and shippable-cart exclusions (plus a null-cart guard).
  • Add unit tests covering standard checkout behavior, no-config behavior, shipping-required carts, cart-unavailable contexts, and My Account exclusions.
  • Introduce minimal test mocks for Newspack\WooCommerce_My_Account and WooCommerce globals needed to exercise the updated behavior in the newspack-blocks test environment.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
plugins/newspack-blocks/includes/class-modal-checkout.php Widens checkout field removal beyond modal requests; adds My Account + null-cart/shipping guards.
plugins/newspack-blocks/tests/test-modal-checkout.php Adds mocks and new unit tests validating field removal across contexts.
plugins/newspack-blocks/tests/mocks/newspack-plugin-mocks.php Adds a minimal WooCommerce_My_Account mock for test environments without newspack-plugin.

Comment thread plugins/newspack-blocks/tests/test-modal-checkout.php Outdated
rbcorrales and others added 2 commits July 9, 2026 18:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread plugins/newspack-blocks/includes/class-modal-checkout.php
Comment thread plugins/newspack-blocks/includes/class-modal-checkout.php
Comment thread plugins/newspack-blocks/tests/test-modal-checkout.php Outdated
rbcorrales and others added 2 commits July 9, 2026 18:55
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread plugins/newspack-blocks/includes/class-modal-checkout.php
@rbcorrales rbcorrales marked this pull request as ready for review July 10, 2026 00:37
@rbcorrales rbcorrales requested a review from a team as a code owner July 10, 2026 00:37
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rbcorrales rbcorrales changed the title fix(blocks): remove configured-off billing fields from all checkout contexts fix(blocks): fix express checkout address validation in modal checkout Jul 10, 2026
@dkoo dkoo self-assigned this Jul 10, 2026

@dkoo dkoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested end-to-end on a dev site with Apple Pay enabled. All testing steps pass as described—the invalid address fields are scrubbed on an express checkout request during a modal checkout where those fields are disabled, allowing the checkout to complete, while default validation prevails when address fields are required.

Approved with comments. This is a careful, well-tested hotfix. The two new Store-API hooks are correctly wired (right rest_pre_dispatch signature and null-return contract, set_param writes to the bucket the Store API reads, cart/shipping guards in place), and the scrub only ever drops values that would fail WooCommerce validation anyway — valid wallet values pass through and are stored. I verified the core logic end-to-end against a live WooCommerce env (invalid state dropped, valid code kept, configured-on field untouched, required relaxed only under a modal referer) and confirmed no regression in standard checkout. The referer-trust surface is benign: a forged referer can only loosen required on two optional fields for the caller's own non-shipping cart — no control is bypassed and there's no cross-user effect. All prior Copilot comments are addressed.

One thing worth fixing before merge, plus two minor notes below. Not blocking.

$states = \WC()->countries->get_states( $country );

if ( is_array( $states ) && ! empty( $states ) ) {
$code_match = array_key_exists( strtoupper( $address['state'] ), array_change_key_case( $states, CASE_UPPER ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The scrub runs strtoupper() / strtolower() / WC_Validation::is_postcode() on the raw address value here, on rest_pre_dispatch — before the Store API schema sanitizes or rejects the request. If a modal checkout request sends billing_address.state (or .postcode) as a JSON array instead of a string, these throw a PHP TypeError and the endpoint returns HTTP 500 instead of the clean 400 the schema would have returned. I reproduced this on PHP 8.3 (strtoupper(): Argument #1 ($string) must be of type string, array given). Only reachable for modal-originated requests on sites with the field configured off, and the request was going to fail either way — but a one-line is_string() guard on the value before transforming turns the 500 back into the intended 400.

return $result;
}

$address = $request->get_param( 'billing_address' );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The scrub only touches billing_address and bails entirely when the cart needs shipping. WooCommerce's CheckoutSchema registers a validate_callback on the shipping_address argument itself, which runs whenever a shipping_address payload is submitted regardless of cart shipping state — so a wallet that attaches a shipping_address with an invalid state on a virtual/donation cart (shipping not needed) could still hard-fail the same validation this fix targets, unscrubbed. Likelihood is low (wallets generally return only billing contact when shipping isn't requested), so this is a consider-scrubbing-symmetrically note rather than a blocker — or document why shipping_address is intentionally out of scope.

*
* @return bool
*/
private static function is_modal_checkout_referer() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: is_modal_checkout_referer() matches with a raw false !== strpos( $referer, 'modal_checkout=1' ) against the whole referer URL, whereas this class already parses the referer properly a few lines down in is_express_checkout() via wp_parse_url( …, PHP_URL_QUERY ) + wp_parse_str. The strpos form also matches modal_checkout=1 appearing in a path segment, fragment, or another param's value. It's functionally fine for the real iframe URL and the security impact is nil (see the review summary), but reusing the existing wp_parse_url/wp_parse_str idiom would keep the two modal-detection paths consistent and slightly harder to trip accidentally.

@github-actions github-actions Bot added [Status] Approved Pull request has been approved and removed [Status] Needs Review labels Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Approved Pull request has been approved

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants