Commit a8191ed02de for woocommerce
commit a8191ed02dee43d9986bac549122611c9691cd41
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Thu Jul 2 13:26:23 2026 +0100
Fix optional WooPayments business structures (#66069)
* Fix optional WooPayments business structures
* Add changelog entry for WooPayments onboarding fix
* Address WooPayments business structure feedback
diff --git a/plugins/woocommerce/changelog/fix-woopayments-optional-business-structure b/plugins/woocommerce/changelog/fix-woopayments-optional-business-structure
new file mode 100644
index 00000000000..0146e5c02c3
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-woopayments-optional-business-structure
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Hide unnecessary WooPayments business structure selection during onboarding.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/data/business-verification-context.tsx b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/data/business-verification-context.tsx
index 39a4de42030..df29ebdc9eb 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/data/business-verification-context.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/data/business-verification-context.tsx
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import React, { createContext, useContext, useState } from 'react';
+import React, { createContext, useCallback, useContext, useState } from 'react';
import { isNil, omitBy } from 'lodash';
/**
@@ -16,16 +16,29 @@ const useBusinessVerificationContextValue = (
const [ errors, setErrors ] = useState( {} as OnboardingFields );
const [ touched, setTouched ] = useState( {} as OnboardingFields );
+ const updateData = useCallback(
+ ( value: Record< string, string | undefined > ) => {
+ setData( ( prev ) => ( { ...prev, ...value } ) );
+ },
+ []
+ );
+ const updateErrors = useCallback(
+ ( value: Record< string, string | undefined > ) => {
+ setErrors( ( prev ) => omitBy( { ...prev, ...value }, isNil ) );
+ },
+ []
+ );
+ const updateTouched = useCallback( ( value: Record< string, boolean > ) => {
+ setTouched( ( prev ) => ( { ...prev, ...value } ) );
+ }, [] );
+
return {
data,
- setData: ( value: Record< string, string | undefined > ) =>
- setData( ( prev ) => ( { ...prev, ...value } ) ),
+ setData: updateData,
errors,
- setErrors: ( value: Record< string, string | undefined > ) =>
- setErrors( ( prev ) => omitBy( { ...prev, ...value }, isNil ) ),
+ setErrors: updateErrors,
touched,
- setTouched: ( value: Record< string, boolean > ) =>
- setTouched( ( prev ) => ( { ...prev, ...value } ) ),
+ setTouched: updateTouched,
};
};
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/sections/business-details.tsx b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/sections/business-details.tsx
index 1d0300c5abc..8aa3df1c3bc 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/sections/business-details.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/sections/business-details.tsx
@@ -58,34 +58,66 @@ const BusinessDetails: React.FC = () => {
( type ) => type.key === data.business_type
);
+ const selectedBusinessStructures = selectedBusinessType?.structures ?? [];
+ const shouldDisplayBusinessStructure =
+ selectedBusinessStructures.length > 0 &&
+ selectedBusinessType?.requires_structure !== false &&
+ ! (
+ selectedBusinessStructures.length === 1 &&
+ selectedBusinessStructures[ 0 ].key === 'nil'
+ );
+ const selectedBusinessTypeKey = selectedBusinessType?.key;
+ const companyStructure = data[ 'company.structure' ];
+ const latestDataRef = React.useRef( data );
+ latestDataRef.current = data;
+
const selectedBusinessStructure =
- selectedBusinessType?.structures.length === 0 ||
- selectedBusinessType?.structures.find(
- ( structure ) => structure.key === data[ 'company.structure' ]
+ ! shouldDisplayBusinessStructure ||
+ selectedBusinessStructures.find(
+ ( structure ) => structure.key === companyStructure
);
- const updateBusinessVerificationData = (
- selfAssessmentData: OnboardingFields
- ): Promise< void > => {
- // Update the local state with the new data.
- setData( selfAssessmentData );
-
- const saveUrl = currentStep?.actions?.save?.href;
- if ( saveUrl ) {
- // Persist the data on the backend.
- return apiFetch( {
- url: saveUrl,
- method: 'POST',
- data: {
- self_assessment: selfAssessmentData,
- source: sessionEntryPoint,
- },
+ const updateBusinessVerificationData = React.useCallback(
+ ( selfAssessmentData: OnboardingFields ): Promise< void > => {
+ // Update the local state with the new data.
+ setData( selfAssessmentData );
+
+ const saveUrl = currentStep?.actions?.save?.href;
+ if ( saveUrl ) {
+ // Persist the data on the backend.
+ return apiFetch( {
+ url: saveUrl,
+ method: 'POST',
+ data: {
+ self_assessment: selfAssessmentData,
+ source: sessionEntryPoint,
+ },
+ } );
+ }
+
+ // Return a resolved promise to maintain consistency with the API.
+ return Promise.resolve();
+ },
+ [ currentStep?.actions?.save?.href, sessionEntryPoint, setData ]
+ );
+
+ React.useEffect( () => {
+ if (
+ selectedBusinessTypeKey &&
+ ! shouldDisplayBusinessStructure &&
+ companyStructure !== undefined
+ ) {
+ void updateBusinessVerificationData( {
+ ...latestDataRef.current,
+ 'company.structure': undefined,
} );
}
-
- // Return a resolved promise to maintain consistency with the API.
- return Promise.resolve();
- };
+ }, [
+ companyStructure,
+ selectedBusinessTypeKey,
+ shouldDisplayBusinessStructure,
+ updateBusinessVerificationData,
+ ] );
const handleTiedChange = (
name: keyof OnboardingFields,
@@ -142,16 +174,15 @@ const BusinessDetails: React.FC = () => {
</OnboardingSelectField>
</span>
) }
- { selectedBusinessType &&
- selectedBusinessType.structures.length > 0 && (
- <span data-testid={ 'business-structure-select' }>
- <OnboardingSelectField
- name="company.structure"
- options={ selectedBusinessType.structures }
- onChange={ handleTiedChange }
- />
- </span>
- ) }
+ { selectedBusinessType && shouldDisplayBusinessStructure && (
+ <span data-testid={ 'business-structure-select' }>
+ <OnboardingSelectField
+ name="company.structure"
+ options={ selectedBusinessStructures }
+ onChange={ handleTiedChange }
+ />
+ </span>
+ ) }
{ selectedCountry &&
selectedBusinessType &&
selectedBusinessStructure && (
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/actions.test.ts b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/actions.test.ts
new file mode 100644
index 00000000000..91830accfd9
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/actions.test.ts
@@ -0,0 +1,56 @@
+/**
+ * External dependencies
+ */
+import apiFetch from '@wordpress/api-fetch';
+
+/**
+ * Internal dependencies
+ */
+import { createEmbeddedKycSession } from '../utils/actions';
+
+jest.mock( '@wordpress/api-fetch', () => jest.fn() );
+
+const mockApiFetch = apiFetch as jest.Mock;
+
+describe( 'business verification actions', () => {
+ beforeEach( () => {
+ jest.clearAllMocks();
+ mockApiFetch.mockResolvedValue( {
+ session: {
+ clientSecret: 'test-secret',
+ expiresAt: 123,
+ accountId: 'acct_test',
+ isLive: false,
+ accountCreated: true,
+ publishableKey: 'pk_test',
+ locale: 'en_US',
+ },
+ } );
+ } );
+
+ it( 'omits company structure when it is unset', async () => {
+ await createEmbeddedKycSession(
+ {
+ country: 'JP',
+ business_type: 'company',
+ 'company.structure': undefined,
+ mcc: 'digital_products__books',
+ },
+ '/wc/v3/payments/onboarding/kyc/session',
+ 'settings'
+ );
+
+ expect( mockApiFetch ).toHaveBeenCalledWith( {
+ url: '/wc/v3/payments/onboarding/kyc/session',
+ method: 'POST',
+ data: {
+ self_assessment: {
+ country: 'JP',
+ business_type: 'company',
+ mcc: 'digital_products__books',
+ },
+ source: 'settings',
+ },
+ } );
+ } );
+} );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/business-details-flow.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/business-details-flow.test.tsx
index e0c4d2fb12d..f5a9f27cdef 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/business-details-flow.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/test/business-details-flow.test.tsx
@@ -16,6 +16,63 @@ import BusinessDetails from '../sections/business-details';
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
+jest.mock( '@wordpress/components', () => {
+ const ReactModule = jest.requireActual< typeof import('react') >( 'react' );
+
+ return {
+ Button: ReactModule.forwardRef(
+ (
+ {
+ children,
+ isBusy,
+ variant,
+ ...props
+ }: React.ButtonHTMLAttributes< HTMLButtonElement > & {
+ isBusy?: boolean;
+ variant?: string;
+ },
+ ref: React.Ref< HTMLButtonElement >
+ ) => (
+ <button
+ { ...props }
+ ref={ ref }
+ data-busy={ isBusy ? 'true' : undefined }
+ data-variant={ variant }
+ >
+ { children }
+ </button>
+ )
+ ),
+ TextControl: ReactModule.forwardRef(
+ (
+ {
+ label,
+ onChange,
+ value,
+ ...props
+ }: React.InputHTMLAttributes< HTMLInputElement > & {
+ label?: string;
+ onChange?: ( value: string ) => void;
+ },
+ ref: React.Ref< HTMLInputElement >
+ ) => (
+ <>
+ { label && <span>{ label }</span> }
+ <input
+ { ...props }
+ ref={ ref }
+ aria-label={ label }
+ value={ value }
+ onChange={ ( event ) =>
+ onChange?.( event.target.value )
+ }
+ />
+ </>
+ )
+ ),
+ };
+} );
+
jest.mock( '../../../data/onboarding-context', () => ( {
useOnboardingContext: jest.fn(),
} ) );
@@ -35,7 +92,9 @@ const mockNextStep = jest.fn();
const fields = {
available_countries: {
+ CA: 'Canada',
GB: 'United Kingdom (UK)',
+ JP: 'Japan',
US: 'United States (US)',
},
business_types: [
@@ -62,6 +121,23 @@ const fields = {
},
],
},
+ {
+ key: 'CA',
+ name: 'Canada',
+ types: [
+ {
+ key: 'company',
+ name: 'Company',
+ description: '',
+ structures: [
+ {
+ key: 'nil',
+ name: 'None',
+ },
+ ],
+ },
+ ],
+ },
{
key: 'GB',
name: 'United Kingdom (UK)',
@@ -74,6 +150,30 @@ const fields = {
},
],
},
+ {
+ key: 'JP',
+ name: 'Japan',
+ types: [
+ {
+ key: 'individual',
+ name: 'Individual',
+ description: '',
+ structures: [],
+ },
+ {
+ key: 'company',
+ name: 'Company',
+ description: '',
+ requires_structure: false,
+ structures: [
+ {
+ key: 'sole_proprietorship',
+ name: 'Sole proprietorship',
+ },
+ ],
+ },
+ ],
+ },
],
mccs_display_tree: [
{
@@ -210,6 +310,78 @@ describe( 'Business details onboarding flow', () => {
} );
} );
+ it( 'continues without showing optional business structure selection', async () => {
+ renderBusinessDetailsForm( {
+ country: 'JP',
+ business_type: 'company',
+ 'company.structure': '',
+ mcc: '5812',
+ } );
+
+ expect(
+ screen.queryByRole( 'combobox', {
+ name: 'What category of legal entity identify your business?',
+ } )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.getByRole( 'combobox', {
+ name: /What type of goods or services does your business sell?/,
+ } )
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText( /By using WooPayments/ )
+ ).toBeInTheDocument();
+
+ await waitFor( () => expect( mockApiFetch ).toHaveBeenCalled() );
+ const savePayload = mockApiFetch.mock.calls.at( -1 )?.[ 0 ].data;
+
+ expect( savePayload ).toEqual( {
+ self_assessment: {
+ country: 'JP',
+ business_type: 'company',
+ 'company.structure': undefined,
+ mcc: '5812',
+ },
+ source: 'settings',
+ } );
+ } );
+
+ it( 'continues without showing business structure when nil is the only option', async () => {
+ renderBusinessDetailsForm( {
+ country: 'CA',
+ business_type: 'company',
+ 'company.structure': 'nil',
+ mcc: '5812',
+ } );
+
+ expect(
+ screen.queryByRole( 'combobox', {
+ name: 'What category of legal entity identify your business?',
+ } )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.getByRole( 'combobox', {
+ name: /What type of goods or services does your business sell?/,
+ } )
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText( /By using WooPayments/ )
+ ).toBeInTheDocument();
+
+ await waitFor( () => expect( mockApiFetch ).toHaveBeenCalled() );
+ const savePayload = mockApiFetch.mock.calls.at( -1 )?.[ 0 ].data;
+
+ expect( savePayload ).toEqual( {
+ self_assessment: {
+ country: 'CA',
+ business_type: 'company',
+ 'company.structure': undefined,
+ mcc: '5812',
+ },
+ source: 'settings',
+ } );
+ } );
+
it( 'resets dependent business details when the country changes', async () => {
renderBusinessDetailsForm( {
business_type: 'company',
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/types.ts b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/types.ts
index 6e5c0ab8e8f..bcfffb6e213 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/types.ts
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/types.ts
@@ -26,6 +26,7 @@ export interface BusinessType {
key: string;
name: string;
description: string;
+ requires_structure?: boolean;
structures: BusinessStructure[];
}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/utils/index.ts b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/utils/index.ts
index 66857b7f8d2..32011082b0e 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/utils/index.ts
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/business-verification/utils/index.ts
@@ -14,7 +14,9 @@ export const fromDotNotation = (
record: Record< string, unknown >
): Record< string, unknown > =>
toPairs( record ).reduce( ( result, [ key, value ] ) => {
- return value !== null ? set( result, key, value ) : result;
+ return value !== null && value !== undefined
+ ? set( result, key, value )
+ : result;
}, {} );
export const getAvailableCountries = (