Commit ca3bad601c5 for woocommerce
commit ca3bad601c5e25530ac4c4090d3d11a30f2038c2
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Fri Aug 7 21:56:06 2026 +0300
[Payments NOX] Fix the default country for new BACS bank accounts (#67478)
* Start new bank accounts in the merchant's business location
Adding a bank account on the Direct bank transfer page opened the country
field on the store's base country. The Payments settings header lets the
merchant state a business location separately from the store address, and
that is the more direct answer to where they bank — it is also the country
the rest of the Payments screens already reason about.
Prefer it, keeping the base country as the fallback. That fallback now
drops the state suffix the option carries ('US:CA'): passed through whole
it matched no entry in the country field and no case in the rules that
pick the routing-number label, so a store with a state in its address got
neither a selected country nor the right routing field.
* Await the userEvent interactions in the BACS keyboard test
userEvent v14 returns a promise from every interaction. These tab calls
were not awaited, so each focus assertion ran against whatever state the
previous move happened to have reached — passing by timing rather than by
guarantee. eslint flags this as testing-library/await-async-events.
Unrelated to the rest of this branch; it surfaced when linting the file
alongside the change beside it.
* Add changelog entry for the bank account default country fix
* test: Cover the last-resort country for new BACS bank accounts
The country a new bank account starts in walks three sources in order: the
Payments business location, the store's base country, and a hardcoded 'US'.
Only the first two were covered, so nothing pinned the behavior when a store
has no location on record — a fresh install where the globals are absent, or
one where they are present but stored empty.
Add a case for each: empty strings must fall through the chain rather than
being taken as a location, and a missing global must not break the render.
Refs #67478
* fix: Guard the BACS account country against non-string settings
The country a new bank account starts in is read straight off two client
globals. Both are documented and typed as strings, but both arrive through
PHP filters, so a third party can put anything in them. Calling split() on
the store's base country therefore let a non-string value throw during
render and take the whole BACS settings screen down with it.
Read both locations through one helper that treats anything other than a
string as no answer. A bad value now falls through to the next source, and
ultimately to US, instead of crashing. Routing both reads through it also
means a business location would get its state suffix stripped too, should
one ever be stored there.
Refs #67478
* test: Cover the fall-through from an unusable business location
The country chain tries the business location, then the store's base
country, then US. Tests covered each link on its own: one asserted the
fall-through from a *missing* business location, others asserted that
empty and non-string values are rejected. None asserted a rejected value
falling through to a valid store country.
That left a real gap. An implementation that branches on whether the
business location is set, rather than on whether it yields a country,
passes all of those tests while sending stores with a malformed business
location to US instead of their own country. Mutation-testing that shape
against the suite confirmed it survived.
The new test asserts GB rather than US on purpose, so the assertion
cannot be satisfied by the last-resort fallback at the end of the chain.
Refs #67478
diff --git a/plugins/woocommerce/changelog/fix-bacs-account-default-country b/plugins/woocommerce/changelog/fix-bacs-account-default-country
new file mode 100644
index 00000000000..34bd8496d3b
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-bacs-account-default-country
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Start new bank accounts on the Direct bank transfer settings page in the business location set on the Payments settings screen. The store base country used before could arrive with a state suffix ("US:CA"), which matched no entry in the country field and no routing number format.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
index 4551172a21f..5939d979bdf 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
@@ -28,13 +28,39 @@ import {
type OfflineFormValues,
} from './dataform-controls';
+/**
+ * Reads a country code out of a location setting.
+ *
+ * These settings reach the client through filters, so treat anything that is
+ * not a string as no answer rather than assuming the declared type holds. A
+ * stored base location can also carry a state suffix ('US:CA'), which matches
+ * neither the country field's options nor the routing-number rules, so keep
+ * only the country.
+ *
+ * @param value The stored setting value.
+ * @return The ISO 3166-1 alpha-2 country code, or an empty string if there is none.
+ */
+const toCountryCode = ( value: unknown ): string =>
+ typeof value === 'string' ? value.split( ':' )[ 0 ] : '';
+
/**
* This page is used to manage the settings for the BACS (Direct bank transfer) payment gateway.
*/
export const SettingsPaymentsBacs = () => {
- const storeCountryCode =
- window.wcSettings?.admin?.preloadSettings?.general
- ?.woocommerce_default_country || 'US';
+ // The Payments settings header lets the merchant set a business location
+ // independently of the store address, and keeps this global in sync when
+ // they change it. Start a new account there, since it is the more recent
+ // statement of where they bank. The store's base country is the fallback.
+ const defaultAccountCountry =
+ toCountryCode(
+ window.wcSettings?.admin?.woocommerce_payments_nox_profile
+ ?.business_country_code
+ ) ||
+ toCountryCode(
+ window.wcSettings?.admin?.preloadSettings?.general
+ ?.woocommerce_default_country
+ ) ||
+ 'US';
const { createSuccessNotice, createErrorNotice } =
useDispatch( 'core/notices' );
@@ -257,7 +283,7 @@ export const SettingsPaymentsBacs = () => {
setAccounts( bankAccounts );
setHasChanges( true );
} }
- defaultCountry={ storeCountryCode }
+ defaultCountry={ defaultAccountCountry }
/>
) }
</Settings.Section>
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx
index 1cebc50602c..b6eccb713f7 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx
@@ -17,7 +17,12 @@ jest.mock( '@wordpress/data', () => ( {
} ) );
jest.mock( '~/settings-payments/components/bank-accounts-list', () => ( {
- BankAccountsList: () => <div data-testid="bank-accounts-list" />,
+ BankAccountsList: ( { defaultCountry }: { defaultCountry: string } ) => (
+ <div
+ data-testid="bank-accounts-list"
+ data-default-country={ defaultCountry }
+ />
+ ),
} ) );
const bacsSettings = {
@@ -90,6 +95,116 @@ describe( 'SettingsPaymentsBacs', () => {
).toBeInTheDocument();
} );
+ describe( 'country a new bank account starts in', () => {
+ const setWcSettings = ( admin: unknown ) => {
+ Object.defineProperty( window, 'wcSettings', {
+ value: { admin },
+ writable: true,
+ } );
+ };
+
+ afterEach( () => {
+ Object.defineProperty( window, 'wcSettings', {
+ value: undefined,
+ writable: true,
+ } );
+ } );
+
+ it( 'uses the business location set on the Payments settings screen', () => {
+ setWcSettings( {
+ woocommerce_payments_nox_profile: {
+ business_country_code: 'TN',
+ },
+ preloadSettings: {
+ general: { woocommerce_default_country: 'US:CA' },
+ },
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'TN' );
+ } );
+
+ it( "falls back to the store's base country, without its state suffix", () => {
+ setWcSettings( {
+ preloadSettings: {
+ general: { woocommerce_default_country: 'US:CA' },
+ },
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'US' );
+ } );
+
+ it( 'ignores locations that are stored empty', () => {
+ setWcSettings( {
+ woocommerce_payments_nox_profile: {
+ business_country_code: '',
+ },
+ preloadSettings: {
+ general: { woocommerce_default_country: '' },
+ },
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'US' );
+ } );
+
+ it( 'ignores locations that are not strings', () => {
+ setWcSettings( {
+ woocommerce_payments_nox_profile: {
+ business_country_code: { country: 'TN' },
+ },
+ preloadSettings: {
+ general: { woocommerce_default_country: true },
+ },
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'US' );
+ } );
+
+ it( 'falls through an unusable business location to the store', () => {
+ setWcSettings( {
+ woocommerce_payments_nox_profile: {
+ business_country_code: { country: 'TN' },
+ },
+ preloadSettings: {
+ // Deliberately not US, so this cannot be confused with
+ // the last-resort fallback at the end of the chain.
+ general: { woocommerce_default_country: 'GB' },
+ },
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'GB' );
+ } );
+
+ it( 'falls back to US when the store knows no location at all', () => {
+ setWcSettings( {} );
+
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toHaveAttribute( 'data-default-country', 'US' );
+ } );
+ } );
+
it( 'renders placeholders while loading', () => {
( useSelect as jest.Mock ).mockReturnValue( {
bacsSettings: null,
@@ -183,7 +298,7 @@ describe( 'SettingsPaymentsBacs', () => {
} );
} );
- it( 'supports keyboard navigation through the form fields', () => {
+ it( 'supports keyboard navigation through the form fields', async () => {
render( <SettingsPaymentsBacs /> );
// Make a change first so the Save button is enabled (and tabbable).
@@ -191,17 +306,17 @@ describe( 'SettingsPaymentsBacs', () => {
target: { value: 'Edited title' },
} );
- userEvent.tab();
+ await userEvent.tab();
expect(
screen.getByLabelText( 'Enable direct bank transfers' )
).toHaveFocus();
- userEvent.tab();
+ await userEvent.tab();
expect( screen.getByLabelText( 'Title' ) ).toHaveFocus();
- userEvent.tab();
+ await userEvent.tab();
expect( screen.getByLabelText( 'Description' ) ).toHaveFocus();
- userEvent.tab();
+ await userEvent.tab();
expect( screen.getByLabelText( 'Instructions' ) ).toHaveFocus();
- userEvent.tab();
+ await userEvent.tab();
expect(
screen.getByRole( 'button', { name: 'Save changes' } )
).toHaveFocus();