Skip to content

ECE: Add support for custom fields in block checkout #4390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from

Conversation

annemirasol
Copy link
Contributor

@annemirasol annemirasol commented Jun 3, 2025

Partially fixes STRIPE-205
Partially fixes #2969

Changes proposed in this Pull Request:

This PR adds support for ECE and custom checkout fields, limited to:

  • block checkout, and
  • fields added via Additional Checkout Fields API (i.e. woocommerce_register_additional_checkout_field()).

In subsequent PRs, we will add support for

Testing instructions

  1. Add custom checkout fields to the block checkout:
    • Install Code Snippets or a similar plugin, and add the snippet provided in the comment below.
  2. As a shopper, add a product to cart and go to block checkout.
  3. Notice the additional fields in the checkout form. Fill out these fields.
  4. Use express checkout to complete your purchase.
  5. Verify in the Order Confirmation page that the fields have been persisted.
    • You may also check the wp_wc_orders_meta.
    • The meta_key will be prefixed with _wc_billing/, _wc_shipping/ or _wc_other/.
  6. Try using ECE in the product or cart pages.
    • Verify there are no unexpected failures. See known limitations about required custom fields below.
    • Verify it avoids using stale session data: since the custom fields are not presented to the shopper inside these pages, there should be no custom data stored for these orders.

Known limitations:

  • When marking a custom field as "required" in woocommerce_register_additional_checkout_field(), express checkout will fail when the data is not provided. This invariably means ECE is not usable outside of the block checkout page.
  • When validation fails, the error message from the server is displayed but the input field is not highlighted in red

  • Covered with tests (or have a good reason not to test in description ☝️)
  • Tested on mobile (or does not apply)

Changelog entry

  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Comment

Comment

Post merge

@annemirasol annemirasol force-pushed the add/ece-support-custom-fields branch 2 times, most recently from 583132b to c97c74f Compare June 3, 2025 18:40
@annemirasol
Copy link
Contributor Author

annemirasol commented Jun 3, 2025

Sample code snippet to add custom fields via the Additional Checkout Fields API:

	'woocommerce_init',
	function() {
		woocommerce_register_additional_checkout_field(
			array(
				'id'            => 'my-plugin/gov-id',
				'label'         => 'Government ID',
				'optionalLabel' => 'Government ID (optional)',
				'location'      => 'address',
				'attributes'    => array(
					'autocomplete'     => 'government-id',
					'aria-describedby' => 'some-element',
					'aria-label'       => 'custom aria label',
					'pattern'          => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers.
					'title'            => 'Title to show on hover',
					'data-custom'      => 'custom data',
				),
			),
		);
		
		woocommerce_register_additional_checkout_field(
			array(
				'id'       => 'my-plugin/marketing-opt-in',
				'label'    => 'Do you want to subscribe to our newsletter?',
				'location' => 'contact',
				'type'     => 'checkbox',
			)
		);
		
		woocommerce_register_additional_checkout_field(
			array(
				'id'          => 'my-plugin/how-did-you-hear-about-us',
				'label'       => 'How did you hear about us?',
				'placeholder' => 'Select a source',
				'location'    => 'order',
				'type'        => 'select',
				'options'     => [
					[
						'value' => 'google',
						'label' => 'Google'
					],
					[
						'value' => 'facebook',
						'label' => 'Facebook'
					],
					[
						'value' => 'friend',
						'label' => 'From a friend'
					],
					[
						'value' => 'other',
						'label' => 'Other'
					],
				]
			)
		);
	}

@annemirasol annemirasol force-pushed the add/ece-support-custom-fields branch from 2bbe0dd to 4dfbba7 Compare June 4, 2025 20:15
@annemirasol annemirasol force-pushed the add/ece-support-custom-fields branch from 4dfbba7 to f82e492 Compare June 4, 2025 20:16
@annemirasol annemirasol self-assigned this Jun 4, 2025
@annemirasol annemirasol marked this pull request as ready for review June 4, 2025 20:28
@annemirasol annemirasol force-pushed the add/ece-support-custom-fields branch from f82e492 to 93ccf89 Compare June 4, 2025 21:50
Copy link

github-actions bot commented Jun 5, 2025

📈 PHP Unit Code Coverage Report

Package Line Rate Health
includes/payment-methods/class-wc-stripe-express-checkout-element.php 51%
Summary 44% (7168 / 16349)

@annemirasol annemirasol requested review from a team and Copilot and removed request for a team June 5, 2025 20:35
Copy link

@Copilot Copilot AI left a comment

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 adds support for passing custom checkout fields (registered via the Additional Checkout Fields API) through express payment methods in block checkout.

  • Exposes custom field configuration from PHP to JS for express checkout.
  • Enhances normalizeOrderData and related utilities to include custom billing, shipping, and additional fields.
  • Updates front-end error formatting and the API call to accept the full order payload.

Reviewed Changes

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

Show a summary per file
File Description
readme.txt Added changelog entry for block checkout custom fields support
includes/payment-methods/class-wc-stripe-express-checkout-element.php Added get_custom_checkout_fields() to localize custom field IDs
client/express-checkout/utils/normalize.js Extended normalizer to merge custom billing/shipping/additional fields
client/express-checkout/utils/index.js Improved safety and early-return in error‐message parsing
client/express-checkout/utils/tests/normalize.test.js Added tests covering additional fields and custom address fields
client/express-checkout/payment-flow.js Added REST parameter error concatenation and formatting
client/blocks/express-checkout/hooks.js Render multiline messages with <br> before setting error state
client/api/index.js Renamed and streamlined expressCheckoutECECreateOrder() to send full order data
changelog.txt Updated changelog entry
Comments suppressed due to low confidence (2)

includes/payment-methods/class-wc-stripe-express-checkout-element.php:235

  • The docblock says "field IDs" but the method returns an associative array of configs (key and location). Please update the @return description to reflect the actual structure.
* @return array Custom checkout field IDs.

client/express-checkout/utils/tests/normalize.test.js:137

  • There aren't tests for the non-block-checkout path of getAdditionalFieldsData or the custom-address helpers. Adding a test where isBlockCheckoutPage() is false would ensure stale data isn't sent.
describe( 'normalizeOrderData', () => {

@annemirasol annemirasol requested review from a team and wjrosa and removed request for a team June 9, 2025 14:27
@@ -3,7 +3,12 @@ import { getErrorMessageFromNotice, normalizeOrderData } from './utils';

const handlePaymentFlowException = ( event, exception, abortPayment ) => {
let errorMessage;
if ( exception.message ) {

if ( exception.code === 'rest_invalid_param' && exception.data?.params ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: I would move this to a constant.

postcode: shipping?.address?.postal_code ?? '',
method: [ event?.shippingRate?.id ?? null ],
},
billing_address: getBillingAddressData( event ),
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice refactor!


const data = {
first_name:
shipping?.name?.split( ' ' )?.slice( 0, 1 )?.join( ' ' ) ?? '',
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: Maybe we could have a helper method for the names, since both share the same logic


const customerData = getCustomerDataFromStore();

if ( ! customerData || ! customerData.shippingAddress ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if ( ! customerData || ! customerData.shippingAddress ) {
if ( ! customerData?.shippingAddress ) {

Copy link
Contributor

@wjrosa wjrosa left a comment

Choose a reason for hiding this comment

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

Code is good and works as expected! Left some optional comments.

  1. Verify in the Order Confirmation page that the fields have been persisted.
Screenshot 2025-06-11 at 12 37 37
  1. Try using ECE in the product or cart pages.

Verify there are no unexpected failures. See known limitations about required custom fields below.

Screenshot 2025-06-11 at 12 40 10

Verify it avoids using stale session data: since the custom fields are not presented to the shopper inside these pages, there should be no custom data stored for these orders.

Screenshot 2025-06-11 at 13 07 36 (custom field not displayed after checking out using the product page)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom checkout fields not getting included in checkout form data submitted by GPay/Apple Pay
2 participants