Commit bc742e9e4fe for woocommerce

commit bc742e9e4feefb63d77c0ac24e1316a97d6c0e27
Author: Ahmed <ahmed.el.azzabi@automattic.com>
Date:   Mon Jul 6 15:49:11 2026 +0100

    Replace payment method notice with WordPress UI (#66016)

    * refactor: Replace payment method notice with WordPress UI

    * chore: Add changelog for payment method notice

diff --git a/plugins/woocommerce/changelog/wooprd-3212-wp-ui-payment-method-notice b/plugins/woocommerce/changelog/wooprd-3212-wp-ui-payment-method-notice
new file mode 100644
index 00000000000..01e9604f779
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooprd-3212-wp-ui-payment-method-notice
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Use the standard WordPress UI Notice for payment method eligibility notices in WooPayments onboarding.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/list-item.tsx b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/list-item.tsx
index 9ab9f42b549..3c13e8c1a27 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/list-item.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/list-item.tsx
@@ -3,10 +3,9 @@
  */
 import { decodeEntities } from '@wordpress/html-entities';
 import { type RecommendedPaymentMethod } from '@woocommerce/data';
-import { ExternalLink, Icon, ToggleControl } from '@wordpress/components';
-import { info as infoIcon } from '@wordpress/icons';
-import { useEffect, useRef } from '@wordpress/element';
-import { speak } from '@wordpress/a11y';
+import { ToggleControl } from '@wordpress/components';
+import { useRef } from '@wordpress/element';
+import { Notice } from '@wordpress/ui';

 /**
  * Internal dependencies
@@ -82,14 +81,6 @@ export const PaymentMethodListItem = ( {

 	const shouldRender = isExpanded || baseVisibility;

-	// Announce notice to screen readers when the method is toggled on.
-	const isEnabled = paymentMethodsState[ method.id ] ?? false;
-	useEffect( () => {
-		if ( isEnabled && method.notice?.message ) {
-			speak( method.notice.message, 'polite' );
-		}
-	}, [ isEnabled, method.notice?.message ] );
-
 	if ( ! shouldRender ) {
 		return null;
 	}
@@ -209,26 +200,31 @@ export const PaymentMethodListItem = ( {
 			</div>
 			{ method.notice?.message &&
 				( paymentMethodsState[ method.id ] ?? false ) && (
-					<div
+					<Notice.Root
 						className="woocommerce-list__item-notice-info"
 						data-testid="payment-method-notice-info"
+						intent="info"
+						spokenMessage={ decodeEntities(
+							method.notice.message
+						) }
 					>
-						<Icon icon={ infoIcon } size={ 24 } />
-						<p>
-							{ method.notice.message }
-							{ method.notice.link_url &&
-								method.notice.link_text && (
-									<>
-										{ ' ' }
-										<ExternalLink
-											href={ method.notice.link_url }
-										>
-											{ method.notice.link_text }
-										</ExternalLink>
-									</>
-								) }
-						</p>
-					</div>
+						<Notice.Description>
+							{ decodeEntities( method.notice.message ) }
+						</Notice.Description>
+						{ method.notice.link_url && method.notice.link_text && (
+							<Notice.Actions>
+								<Notice.ActionLink
+									href={ method.notice.link_url }
+									openInNewTab
+									rel="noopener noreferrer"
+								>
+									{ decodeEntities(
+										method.notice.link_text
+									) }
+								</Notice.ActionLink>
+							</Notice.Actions>
+						) }
+					</Notice.Root>
 				) }
 		</div>
 	);
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/style.scss b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/style.scss
index 6734675162b..d00d42617a6 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/style.scss
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/style.scss
@@ -260,26 +260,5 @@

 .woocommerce-list__item-notice-info {
 	flex-basis: 100%;
-	display: flex;
-	gap: 4px;
-	align-items: flex-start;
 	margin: 20px 0 0;
-	padding: 12px;
-	background-color: #f5f9fd;
-	border: 1px solid #9fbcdd;
-	border-radius: 8px;
-
-	svg {
-		flex-shrink: 0;
-		fill: #006cd8;
-	}
-
-	p {
-		margin: 0;
-		padding: 2px 0;
-		font-size: 13px;
-		font-weight: 400;
-		line-height: 20px;
-		color: #001758;
-	}
 }
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/test/list-item.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/test/list-item.test.tsx
index 626b5ac8848..36ce33469d8 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/test/list-item.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/onboarding/providers/woopayments/steps/payment-methods-selection/test/list-item.test.tsx
@@ -3,10 +3,65 @@
  */
 import { render, screen } from '@testing-library/react';
 import { type RecommendedPaymentMethod } from '@woocommerce/data';
+import { type ReactNode } from 'react';

-const mockSpeak = jest.fn();
-jest.mock( '@wordpress/a11y', () => ( {
-	speak: ( ...args: unknown[] ) => mockSpeak( ...args ),
+type MockComponentProps = {
+	children?: ReactNode;
+	className?: string;
+	href?: string;
+	intent?: string;
+	openInNewTab?: boolean;
+	rel?: string;
+	spokenMessage?: string;
+	'data-testid'?: string;
+};
+
+jest.mock( '@wordpress/components', () => ( {
+	ToggleControl: ( { checked }: { checked?: boolean } ) => (
+		<input type="checkbox" checked={ checked } readOnly />
+	),
+} ) );
+
+jest.mock( '@wordpress/ui', () => ( {
+	Notice: {
+		Root: ( {
+			children,
+			className,
+			intent,
+			spokenMessage,
+			'data-testid': testId,
+		}: MockComponentProps ) => (
+			<div
+				className={ className }
+				data-intent={ intent }
+				data-spoken-message={ spokenMessage }
+				data-testid={ testId }
+			>
+				{ children }
+			</div>
+		),
+		Description: ( { children }: MockComponentProps ) => (
+			<span>{ children }</span>
+		),
+		Actions: ( { children }: MockComponentProps ) => (
+			<div>{ children }</div>
+		),
+		ActionLink: ( {
+			children,
+			href,
+			openInNewTab,
+			rel,
+		}: MockComponentProps ) => (
+			// eslint-disable-next-line react/jsx-no-target-blank -- The mock forwards rel so tests can assert the component contract.
+			<a
+				href={ href }
+				rel={ rel }
+				target={ openInNewTab ? '_blank' : undefined }
+			>
+				{ children }
+			</a>
+		),
+	},
 } ) );

 /**
@@ -93,14 +148,14 @@ describe( 'PaymentMethodListItem', () => {
 		} );
 	} );

-	describe( 'Warning notice', () => {
-		it( 'renders a warning notice when method is enabled and notice.message is set', () => {
+	describe( 'Payment method notice', () => {
+		it( 'renders a notice when method is enabled and notice.message is set', () => {
 			const method = createMethod( {
 				id: 'p24',
 				notice: {
 					badge: 'Verification required',
-					message: 'Strict requirements apply.',
-					link_text: 'Review requirements',
+					message: 'Strict requirements &amp; eligibility apply.',
+					link_text: 'Review requirements &amp; terms',
 					link_url: 'https://example.com/docs',
 				},
 			} );
@@ -113,15 +168,30 @@ describe( 'PaymentMethodListItem', () => {
 				/>
 			);

-			expect(
-				screen.getByText( 'Strict requirements apply.' )
-			).toBeInTheDocument();
-			expect(
-				screen.getByRole( 'link', { name: /review requirements/i } )
-			).toHaveAttribute( 'href', 'https://example.com/docs' );
+			const notice = screen.getByTestId( 'payment-method-notice-info' );
+			expect( notice ).toHaveAttribute( 'data-intent', 'info' );
+			expect( notice ).toHaveAttribute(
+				'data-spoken-message',
+				'Strict requirements & eligibility apply.'
+			);
+			expect( notice ).toHaveTextContent(
+				'Strict requirements & eligibility apply.'
+			);
+			const requirementsLink = screen.getByRole( 'link', {
+				name: /review requirements & terms/i,
+			} );
+			expect( requirementsLink ).toHaveAttribute(
+				'href',
+				'https://example.com/docs'
+			);
+			expect( requirementsLink ).toHaveAttribute( 'target', '_blank' );
+			expect( requirementsLink ).toHaveAttribute(
+				'rel',
+				'noopener noreferrer'
+			);
 		} );

-		it( 'does not render a warning notice when method is disabled', () => {
+		it( 'does not render a notice when method is disabled', () => {
 			const method = createMethod( {
 				id: 'p24',
 				notice: {
@@ -145,7 +215,7 @@ describe( 'PaymentMethodListItem', () => {
 			).not.toBeInTheDocument();
 		} );

-		it( 'does not render a warning notice when notice.message is empty', () => {
+		it( 'does not render a notice when notice.message is empty', () => {
 			const method = createMethod( {
 				notice: {
 					badge: 'Verification required',
diff --git a/plugins/woocommerce/client/admin/package.json b/plugins/woocommerce/client/admin/package.json
index 97fd3936d9d..a2539e4f775 100644
--- a/plugins/woocommerce/client/admin/package.json
+++ b/plugins/woocommerce/client/admin/package.json
@@ -100,6 +100,7 @@
 		"@wordpress/plugins": "catalog:wp-min",
 		"@wordpress/primitives": "catalog:wp-min",
 		"@wordpress/router": "0.7.0",
+		"@wordpress/ui": "catalog:wp-bundled",
 		"@wordpress/url": "catalog:wp-min",
 		"@wordpress/viewport": "catalog:wp-min",
 		"@wordpress/warning": "catalog:wp-min",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c8e3a5df95..85adf07e2c4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,6 +21,9 @@ catalogs:
     '@wordpress/interface':
       specifier: 9.18.5
       version: 9.18.5
+    '@wordpress/ui':
+      specifier: 0.15.1
+      version: 0.15.1
   wp-min:
     '@types/wordpress__block-editor':
       specifier: 15.0.6
@@ -2966,6 +2969,9 @@ importers:
       '@wordpress/router':
         specifier: 0.7.0
         version: 0.7.0(react@18.3.1)
+      '@wordpress/ui':
+        specifier: catalog:wp-bundled
+        version: 0.15.1(@date-fns/tz@1.4.1)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
       '@wordpress/url':
         specifier: catalog:wp-min
         version: 4.33.1
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 90bbccedd83..edc1961bb87 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -63,6 +63,7 @@ catalogs:
         '@wordpress/fields': 0.25.11
         '@wordpress/icons': 11.0.1
         '@wordpress/interface': 9.18.5
+        '@wordpress/ui': 0.15.1
         '@wordpress/undo-manager': 1.33.1
         '@wordpress/views': 1.0.8
         '@wordpress/base-styles': 6.9.1