From 9a654de68420bb8efa163c3c4cfae09468299e61 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 5 Dec 2024 10:24:16 +0100 Subject: [PATCH] Migrate to custom containers and better elements scoping (#9782) Co-authored-by: Timur Karimov Co-authored-by: Francesco --- changelog/scope-payment-elements-selectors | 4 + client/checkout/classic/event-handlers.js | 110 ++------ client/checkout/classic/payment-processing.js | 2 +- client/checkout/utils/test/upe.test.js | 178 ++++++++----- client/checkout/utils/upe.js | 238 +++++++++++++----- includes/class-wc-payments-checkout.php | 41 +-- .../unit/test-class-wc-payments-checkout.php | 2 +- 7 files changed, 345 insertions(+), 230 deletions(-) create mode 100644 changelog/scope-payment-elements-selectors diff --git a/changelog/scope-payment-elements-selectors b/changelog/scope-payment-elements-selectors new file mode 100644 index 00000000000..515bb60dc2e --- /dev/null +++ b/changelog/scope-payment-elements-selectors @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Ensure more robust selectors scoping to improve theme compatibility. diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index fe53b9b2a88..ca00bcbd7a6 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -1,4 +1,4 @@ -/* global jQuery, wc_address_i18n_params */ +/* global jQuery */ /** * Internal dependencies @@ -12,6 +12,7 @@ import { hasPaymentMethodCountryRestrictions, isUsingSavedPaymentMethod, togglePaymentMethodForCountry, + isBillingInformationMissing, } from '../utils/upe'; import { processPayment, @@ -30,20 +31,10 @@ import apiRequest from '../utils/request'; import { handleWooPayEmailInput } from 'wcpay/checkout/woopay/email-input-iframe'; import { isPreviewing } from 'wcpay/checkout/preview'; import { recordUserEvent } from 'tracks'; -import { SHORTCODE_BILLING_ADDRESS_FIELDS } from 'wcpay/checkout/constants'; import '../utils/copy-test-number'; +import { SHORTCODE_BILLING_ADDRESS_FIELDS } from '../constants'; -function getParsedLocale() { - try { - return JSON.parse( - wc_address_i18n_params.locale.replace( /"/g, '"' ) - ); - } catch ( e ) { - return null; - } -} jQuery( function ( $ ) { - const locale = getParsedLocale(); enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); const publishableKey = getUPEConfig( 'publishableKey' ); @@ -85,7 +76,7 @@ jQuery( function ( $ ) { } ); $checkoutForm.on( generateCheckoutEventNames(), function () { - if ( isBillingInformationMissing() ) { + if ( isBillingInformationMissing( this ) ) { return; } @@ -93,11 +84,10 @@ jQuery( function ( $ ) { } ); $checkoutForm.on( 'click', '#place_order', function () { - const isWCPay = document.getElementById( - 'payment_method_woocommerce_payments' - )?.checked; + // Use the existing utility function to check if any WCPay payment method is selected + const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); - if ( ! isWCPay ) { + if ( ! selectedPaymentMethod ) { return; } @@ -231,11 +221,11 @@ jQuery( function ( $ ) { } async function maybeMountStripePaymentElement( elementsLocation ) { - if ( - $( '.wcpay-upe-element' ).length && - ! $( '.wcpay-upe-element' ).children().length - ) { - for ( const upeElement of $( '.wcpay-upe-element' ).toArray() ) { + const $upeForms = $( '.wcpay-upe-form' ); + const $upeElements = $upeForms.find( '.wcpay-upe-element' ); + + if ( $upeElements.length && ! $upeElements.children().length ) { + for ( const upeElement of $upeElements.toArray() ) { await mountStripePaymentElement( api, upeElement, @@ -251,71 +241,17 @@ jQuery( function ( $ ) { if ( hasPaymentMethodCountryRestrictions( upeElement ) ) { togglePaymentMethodForCountry( upeElement ); - // this event only applies to the checkout form, but not "place order" or "add payment method" pages. - $( '#billing_country' ).on( 'change', function () { - togglePaymentMethodForCountry( upeElement ); - } ); - } - } - - function isBillingInformationMissing() { - const enabledBillingFields = getUPEConfig( 'enabledBillingFields' ); - - // first name and last name are kinda special - we just need one of them to be at checkout - const name = `${ - document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name }` - )?.value || '' - } ${ - document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name }` - )?.value || '' - }`.trim(); - if ( - ! name && - ( enabledBillingFields[ - SHORTCODE_BILLING_ADDRESS_FIELDS.first_name - ] || - enabledBillingFields[ - SHORTCODE_BILLING_ADDRESS_FIELDS.last_name - ] ) - ) { - return true; + const billingInput = upeElement + ?.closest( 'form.checkout' ) + ?.querySelector( + `[name="${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }"]` + ); + if ( billingInput ) { + // this event only applies to the checkout form, but not "place order" or "add payment method" pages. + $( billingInput ).on( 'change', function () { + togglePaymentMethodForCountry( upeElement ); + } ); + } } - - const billingFieldsToValidate = [ - 'billing_email', - SHORTCODE_BILLING_ADDRESS_FIELDS.country, - SHORTCODE_BILLING_ADDRESS_FIELDS.address_1, - SHORTCODE_BILLING_ADDRESS_FIELDS.city, - SHORTCODE_BILLING_ADDRESS_FIELDS.postcode, - ].filter( ( field ) => enabledBillingFields[ field ] ); - - const country = billingFieldsToValidate.includes( - SHORTCODE_BILLING_ADDRESS_FIELDS.country - ) - ? document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }` - )?.value - : null; - - // We need to just find one field with missing information. If even only one is missing, just return early. - return Boolean( - billingFieldsToValidate.find( ( fieldName ) => { - const $field = document.querySelector( `#${ fieldName }` ); - let isRequired = enabledBillingFields[ fieldName ]?.required; - - if ( country && locale && fieldName !== 'billing_email' ) { - const key = fieldName.replace( 'billing_', '' ); - isRequired = - locale[ country ][ key ]?.required ?? - locale.default[ key ]?.required; - } - - const hasValue = $field?.value; - - return isRequired && ! hasValue; - } ) - ); } } ); diff --git a/client/checkout/classic/payment-processing.js b/client/checkout/classic/payment-processing.js index 56595bebf93..cfde44fcb5d 100644 --- a/client/checkout/classic/payment-processing.js +++ b/client/checkout/classic/payment-processing.js @@ -278,7 +278,7 @@ async function createStripePaymentElement( const elements = stripe.elements( options ); const createdStripePaymentElement = elements.create( 'payment', { - ...getUpeSettings(), + ...getUpeSettings( paymentMethodType ), wallets: { applePay: 'never', googlePay: 'never', diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index c846f832d87..7357840b51a 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -13,7 +13,9 @@ import { dispatchChangeEventFor, togglePaymentMethodForCountry, } from '../upe'; + import { getPaymentMethodsConstants } from '../../constants'; + import { getUPEConfig } from 'wcpay/utils/checkout'; jest.mock( 'wcpay/utils/checkout' ); @@ -27,22 +29,6 @@ jest.mock( '../../constants', () => { describe( 'UPE checkout utils', () => { describe( 'getSelectedUPEGatewayPaymentMethod', () => { let container; - let input; - - beforeAll( () => { - container = document.createElement( 'div' ); - container.innerHTML = ` -
    -
  • - -
  • -
  • - -
  • -
- `; - document.body.appendChild( container ); - } ); beforeEach( () => { getUPEConfig.mockImplementation( ( argument ) => { @@ -54,33 +40,42 @@ describe( 'UPE checkout utils', () => { return 'woocommerce_payments'; } } ); - } ); - afterEach( () => { - input.checked = false; - jest.clearAllMocks(); + // Create container for each test + container = document.createElement( 'div' ); + document.body.appendChild( container ); } ); - afterAll( () => { + afterEach( () => { + // Clean up after each test document.body.removeChild( container ); container = null; + jest.clearAllMocks(); } ); test( 'Selected UPE Payment Method is card', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments' - ); - input.checked = true; - + container.innerHTML = ``; expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'card' ); } ); test( 'Selected UPE Payment Method is bancontact', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments_bancontact' - ); - input.checked = true; - + container.innerHTML = ` + + `; expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'bancontact' ); } ); } ); @@ -195,10 +190,28 @@ describe( 'UPE checkout utils', () => {
  • - + +
    +
    +
  • - + +
    +
    +
`; @@ -254,24 +267,32 @@ describe( 'UPE checkout utils', () => { } ); it( 'should fall back to card as the default payment method if the selected payment method is toggled off', () => { - const input = document.querySelector( - '#payment_method_woocommerce_payments_bancontact' - ); - input.checked = true; - - const upeElement = document.querySelector( - '.payment_method_woocommerce_payments_bancontact' + const input = document.getElementById( + 'payment_method payment_method_woocommerce_payments_bancontact' ); + input.setAttribute( 'checked', 'checked' ); + + const upeElement = document + .querySelector( + `.wcpay-upe-form[data-payment-method-type="bancontact"]` + ) + .querySelector( '.wcpay-upe-element' ); + const upeContainer = upeElement.closest( '.wc_payment_method' ); document.getElementById( 'billing_country' ).value = 'US'; + const cardPaymentMethod = document + .querySelector( + `.wcpay-upe-form[data-payment-method-type="card"]` + ) + .closest( '.wc_payment_method' ) + .querySelector( + `input[name="payment_method"][value="woocommerce_payments"]` + ); - const cardPaymentMethod = document.querySelector( - '#payment_method_woocommerce_payments' - ); jest.spyOn( cardPaymentMethod, 'click' ); togglePaymentMethodForCountry( upeElement ); - expect( upeElement.style.display ).toBe( 'none' ); + expect( upeContainer.style.display ).toBe( 'none' ); expect( cardPaymentMethod.click ).toHaveBeenCalled(); } ); } ); @@ -312,6 +333,21 @@ describe( 'UPE checkout utils', () => { } ); it( 'should provide terms when cart does not contain subscriptions but the saving checkbox is checked', () => { + const container = document.createElement( 'div' ); + container.innerHTML = ` +
+
+ +
+ `; + document.body.appendChild( container ); + getUPEConfig.mockImplementation( ( argument ) => { if ( argument === 'paymentMethodsConfig' ) { return { @@ -329,9 +365,8 @@ describe( 'UPE checkout utils', () => { createCheckboxElementWhich( true ); - const upeSettings = getUpeSettings(); + const upeSettings = getUpeSettings( 'card' ); - // console.log(result); expect( upeSettings.terms.card ).toEqual( 'always' ); } ); @@ -566,18 +601,41 @@ describe( 'blocksShowLinkButtonHandler', () => { }, }; - beforeEach( () => { + beforeAll( () => { + const wcpayPaymentElement = document.createElement( 'div' ); + wcpayPaymentElement.className = 'wcpay-payment-element'; + + const form = document.createElement( 'form' ); + form.appendChild( wcpayPaymentElement ); + container = document.createElement( 'div' ); container.innerHTML = ` `; - document.body.appendChild( container ); + form.appendChild( container ); + + document.body.appendChild( form ); + } ); + + afterAll( () => { + document.body.innerHTML = ''; + } ); + + beforeEach( () => { + const emailInput = document.getElementById( 'email' ); + if ( emailInput ) { + emailInput.value = ''; + } } ); afterEach( () => { - document.body.removeChild( container ); - container = null; + const stripeLinkButton = document.querySelector( + '.wcpay-stripelink-modal-trigger' + ); + if ( stripeLinkButton ) { + stripeLinkButton.remove(); + } } ); test( 'should hide link button if email input is empty', () => { @@ -595,11 +653,11 @@ describe( 'blocksShowLinkButtonHandler', () => { blocksShowLinkButtonHandler( autofill ); - const stripeLinkButton = document.querySelector( + const linkButton = container.querySelector( '.wcpay-stripelink-modal-trigger' ); - expect( stripeLinkButton ).toBeDefined(); - expect( stripeLinkButton.style.display ).toEqual( 'inline-block' ); + expect( linkButton ).not.toBeNull(); + expect( linkButton.style.display ).toBe( 'inline-block' ); } ); } ); @@ -609,14 +667,18 @@ describe( 'isUsingSavedPaymentMethod', () => { beforeAll( () => { container = document.createElement( 'div' ); container.innerHTML = ` +
- + +
+
+ +
`; document.body.appendChild( container ); } ); diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index 761088fa664..500314b9f5b 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -1,11 +1,16 @@ +/* global wc_address_i18n_params */ + /** * Internal dependencies */ import { getUPEConfig } from 'wcpay/utils/checkout'; -import { getPaymentMethodsConstants } from '../constants'; +import { + getPaymentMethodsConstants, + SHORTCODE_BILLING_ADDRESS_FIELDS, +} from '../constants'; /** - * Generates terms parameter for UPE, with value set for reusable payment methods + * Generates terms for reusable payment methods * * @param {Object} paymentMethodsConfig Object mapping payment method strings to their settings. * @param {string} value The terms value for each available payment method. @@ -25,42 +30,38 @@ export const getTerms = ( paymentMethodsConfig, value = 'always' ) => { }; /** - * Finds selected payment gateway and returns matching Stripe payment method for gateway. + * Returns Stripe payment method (e.g. card, bancontact ) for selected payment gateway. * - * @return {string} Stripe payment method type + * @return {string} Payment method name */ export const getSelectedUPEGatewayPaymentMethod = () => { - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - const gatewayCardId = getUPEConfig( 'gatewayId' ); - let selectedGatewayId = null; - - // Handle payment method selection on the Checkout page or Add Payment Method page where class names differ. - const radio = document.querySelector( - 'li.wc_payment_method input.input-radio:checked, li.woocommerce-PaymentMethod input.input-radio:checked' + const selectedGateway = document.querySelector( + 'input[name="payment_method"][value*="woocommerce_payments"]:checked' ); - if ( radio !== null ) { - selectedGatewayId = radio.id; - } - if ( selectedGatewayId === 'payment_method_woocommerce_payments' ) { - selectedGatewayId = 'payment_method_woocommerce_payments_card'; + if ( ! selectedGateway ) { + return null; } - let selectedPaymentMethod = null; - - for ( const paymentMethodType in paymentMethodsConfig ) { - if ( - `payment_method_${ gatewayCardId }_${ paymentMethodType }` === - selectedGatewayId - ) { - selectedPaymentMethod = paymentMethodType; - break; - } - } - - return selectedPaymentMethod; + // 'woocommerce_payments_affirm' => 'affirm' + // 'woocommerce_payments_p24' -> 'p24' + // 'woocommerce_payments' -> '' + const paymentMethodType = selectedGateway.value + // non-card elements are prefixed with `woocommerce_payments_*` + .replace( 'woocommerce_payments_', '' ) + // the card element is just called `woocommerce_payments` - we need to account for variation in the name + .replace( 'woocommerce_payments', '' ); + + // if the string is empty, it's the card element + return paymentMethodType || 'card'; }; +/** + * Determines which billing fields should be hidden in the Stripe payment element. + * + * @param {Object} enabledBillingFields Object containing all the billing fields for the WooCommerce checkout. + * @return {Object} Object mapping billing field names to their hidden status. + */ export const getHiddenBillingFields = ( enabledBillingFields ) => { return { name: @@ -83,9 +84,18 @@ export const getHiddenBillingFields = ( enabledBillingFields ) => { }; }; -export const getUpeSettings = () => { +/** + * Generates payment method specific settings object for the Stripe Payment Elements. + * Includes terms visibility, billing fields configuration, and default customer values. + * + * @param {string} paymentMethodType The type of payment method being configured (e.g. card, bancontact) + * @return {Object} Settings object for Payment Elements + */ +export const getUpeSettings = ( paymentMethodType ) => { const upeSettings = {}; - const showTerms = shouldIncludeTerms() ? 'always' : 'never'; + const showTerms = shouldIncludeTerms( paymentMethodType ) + ? 'always' + : 'never'; upeSettings.terms = getTerms( getUPEConfig( 'paymentMethodsConfig' ), @@ -120,22 +130,31 @@ export const getUpeSettings = () => { return upeSettings; }; -function shouldIncludeTerms() { +function getGatewayIdBy( paymentMethodType ) { + const gatewayPrefix = 'woocommerce_payments'; + // Only append underscore and payment method type for non-card payments + return paymentMethodType === 'card' + ? gatewayPrefix + : `${ gatewayPrefix }_${ paymentMethodType }`; +} + +function shouldIncludeTerms( paymentMethodType ) { if ( getUPEConfig( 'cartContainsSubscription' ) ) { return true; } - const savePaymentMethodCheckbox = document.getElementById( - 'wc-woocommerce_payments-new-payment-method' + const paymentsForm = document.querySelector( + `.wcpay-upe-form[data-payment-method-type="${ paymentMethodType }"]` ); - if ( - savePaymentMethodCheckbox !== null && - savePaymentMethodCheckbox.checked - ) { - return true; + if ( ! paymentsForm ) { + return false; } - return false; + const savePaymentMethodCheckbox = paymentsForm.querySelector( + `#wc-${ getGatewayIdBy( paymentMethodType ) }-new-payment-method` + ); + + return savePaymentMethodCheckbox?.checked || false; } export const generateCheckoutEventNames = () => { @@ -183,17 +202,24 @@ export const appendFraudPreventionTokenInputToForm = ( $form ) => { * @return {boolean} Boolean indicating whether a saved payment method is being used. */ export function isUsingSavedPaymentMethod( paymentMethodType ) { - const prefix = '#wc-woocommerce_payments'; - const suffix = '-payment-token-new'; - const savedPaymentSelector = - paymentMethodType === 'card' || paymentMethodType === 'link' - ? prefix + suffix - : prefix + '_' + paymentMethodType + suffix; + const paymentsForm = document.querySelector( + `.wcpay-upe-form[data-payment-method-type="${ paymentMethodType }"]` + ); + if ( ! paymentsForm ) { + return false; + } - return ( - document.querySelector( savedPaymentSelector ) !== null && - ! document.querySelector( savedPaymentSelector ).checked + const newPaymentTokenInputId = `wc-${ getGatewayIdBy( + paymentMethodType + ) }-payment-token-new`; + const newPaymentTokenInput = paymentsForm.querySelector( + `input#${ newPaymentTokenInputId }` ); + if ( ! newPaymentTokenInput ) { + return false; + } + + return ! newPaymentTokenInput.checked; } export function dispatchChangeEventFor( element ) { @@ -279,12 +305,16 @@ export const getPaymentMethodTypes = ( paymentMethodType ) => { }; /** - * Returns the value of the email input on the blocks checkout page. + * Returns the email value from store API. * - * @return {string} The value of email input. + * @return {string} The email value. */ export const getBlocksEmailValue = () => { - return document.getElementById( 'email' ).value; + // .wcpay-payment-element container is rendered only when new payment method is selected + return document + .querySelector( '.wcpay-payment-element' ) + ?.closest( 'form' ) + ?.querySelector( '#email' )?.value; }; /** @@ -293,16 +323,20 @@ export const getBlocksEmailValue = () => { * @param {Object} linkAutofill Stripe Link Autofill instance. */ export const blocksShowLinkButtonHandler = ( linkAutofill ) => { - const emailInput = document.getElementById( 'email' ); + const upeContainer = document.querySelector( '.wcpay-payment-element' ); + if ( ! upeContainer ) return; + + const emailInput = upeContainer + .closest( 'form' ) + ?.querySelector( '#email' ); + if ( ! emailInput ) return; const stripeLinkButton = document.createElement( 'button' ); stripeLinkButton.setAttribute( 'class', 'wcpay-stripelink-modal-trigger' ); stripeLinkButton.style.display = emailInput.value ? 'inline-block' : 'none'; stripeLinkButton.addEventListener( 'click', ( event ) => { event.preventDefault(); - linkAutofill.launch( { - email: document.getElementById( 'email' ).value, - } ); + linkAutofill.launch( { email: emailInput.value } ); } ); emailInput.parentNode.appendChild( stripeLinkButton ); @@ -331,26 +365,98 @@ export const togglePaymentMethodForCountry = ( upeElement ) => { const supportedCountries = paymentMethodsConfig[ paymentMethodType ].countries; const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); + // Simplified approach - find the form ancestor and then search within it + let billingInput = upeElement + ?.closest( 'form.checkout, form#add_payment_method' ) + ?.querySelector( '[name="billing_country"]' ); + + // If not found, fallback to the search in the whole document + if ( ! billingInput ) { + billingInput = document.querySelector( '#billing_country' ); + } /* global wcpayCustomerData */ // in the case of "pay for order", there is no "billing country" input, so we need to rely on backend data. const billingCountry = - document.getElementById( 'billing_country' )?.value || - wcpayCustomerData?.billing_country || - ''; + billingInput?.value || wcpayCustomerData?.billing_country || ''; - const upeContainer = document.querySelector( - '.payment_method_woocommerce_payments_' + paymentMethodType - ); + const upeContainer = upeElement?.closest( '.wc_payment_method' ); if ( supportedCountries.includes( billingCountry ) ) { upeContainer.style.removeProperty( 'display' ); } else { upeContainer.style.display = 'none'; - // if the toggled off payment method was selected, we need to fall back to credit card if ( paymentMethodType === selectedPaymentMethod ) { - document - .querySelector( '#payment_method_woocommerce_payments' ) - .click(); + const cardPaymentForm = document.querySelector( + 'input[name="payment_method"][value="woocommerce_payments"]' + ); + + cardPaymentForm?.click(); } } }; + +function getParsedLocale() { + try { + return JSON.parse( + wc_address_i18n_params.locale.replace( /"/g, '"' ) + ); + } catch ( e ) { + return null; + } +} + +export const isBillingInformationMissing = ( form ) => { + const enabledBillingFields = getUPEConfig( 'enabledBillingFields' ); + + // first name and last name are kinda special - we just need one of them to be at checkout + const name = `${ + form.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name }` + )?.value || '' + } ${ + form.querySelector( `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name }` ) + ?.value || '' + }`.trim(); + if ( + ! name && + ( enabledBillingFields[ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name ] || + enabledBillingFields[ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name ] ) + ) { + return true; + } + + const billingFieldsToValidate = [ + 'billing_email', + SHORTCODE_BILLING_ADDRESS_FIELDS.country, + SHORTCODE_BILLING_ADDRESS_FIELDS.address_1, + SHORTCODE_BILLING_ADDRESS_FIELDS.city, + SHORTCODE_BILLING_ADDRESS_FIELDS.postcode, + ].filter( ( field ) => enabledBillingFields[ field ] ); + + const country = billingFieldsToValidate.includes( + SHORTCODE_BILLING_ADDRESS_FIELDS.country + ) + ? form.querySelector( `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }` ) + ?.value + : null; + + // We need to just find one field with missing information. If even only one is missing, just return early. + return Boolean( + billingFieldsToValidate.find( ( fieldName ) => { + const field = form.querySelector( `#${ fieldName }` ); + let isRequired = enabledBillingFields[ fieldName ]?.required; + const locale = getParsedLocale(); + + if ( country && locale && fieldName !== 'billing_email' ) { + const key = fieldName.replace( 'billing_', '' ); + isRequired = + locale[ country ][ key ]?.required ?? + locale.default[ key ]?.required; + } + + const hasValue = field?.value; + + return isRequired && ! hasValue; + } ) + ); +}; diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 7dd49c98b67..ee7a161f3b1 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -415,16 +415,23 @@ function () use ( $prepared_customer_data ) { ); } - // Output the form HTML. - if ( ! empty( $this->gateway->get_description() ) ) : ?> -

gateway->get_description() ); ?>

+ ?> +
gateway->get_description() ) ) : + ?> +

gateway->get_description() ); ?>

+ is_test() && false !== $this->gateway->get_payment_method()->get_testing_instructions( $this->account->get_account_country() ) ) : - ?> + if ( WC_Payments::mode()->is_test() && false !== $this->gateway->get_payment_method()->get_testing_instructions( $this->account->get_account_country() ) ) : + ?>

- '

gateway->id ); diff --git a/tests/unit/test-class-wc-payments-checkout.php b/tests/unit/test-class-wc-payments-checkout.php index 962c7bc4d8b..1fcbe1093ff 100644 --- a/tests/unit/test-class-wc-payments-checkout.php +++ b/tests/unit/test-class-wc-payments-checkout.php @@ -124,7 +124,7 @@ public function set_up() { // Use a callback to suppresses the output buffering being printed to the CLI. $this->setOutputCallback( function ( $output ) { - preg_match_all( '/.*.*/s', $output ); + preg_match_all( '/.*.*/s', $output ); } );