Commit 8f60e7f0f4 for woocommerce

commit 8f60e7f0f4192a3f30892cf822346269c646bc60
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Thu May 29 15:29:11 2025 +0300

    [NOX in-context flows] Add WooPayments onboarding preload mechanics (#58311)

    * Add PaymentEntity type and refactor types

    * test: Fixes after new types

    * refac: setUpPlugin receives payment entity object instead of id and slug

    * Optional type props fixes

    * refac: Remove needless temp variables

    * Add onboarding preload endpoint for WooPayments

    * Add mapping between suggestions and providers

    * Allow payment providers to enhance their extension suggestions details

    * More consistent HTTP responses

    * test: Add unit tests for onboarding preload

    * test: Add unit test for gateway enhancing extension suggestion details

    * test: Minor cleanup

    * test: Add WooPayments provider unit tests

    * test: Add integration tests for the WooPayments REST controller

    * Remove unused param

    * Add changelog

    * Use plural PaymentsEntity for clearer intent

    * Add changelog

    * Type fixes

    * test: Use constants instead of numeric HTTP response status codes

    * Fix naming

    * Use Jetpack Constants to check for constants

    * test: Add unit test for WooPayments at wrong version

diff --git a/packages/js/data/changelog/update-nox-in-context-onboarding-preload b/packages/js/data/changelog/update-nox-in-context-onboarding-preload
new file mode 100644
index 0000000000..ceb8d4d5f0
--- /dev/null
+++ b/packages/js/data/changelog/update-nox-in-context-onboarding-preload
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Refactor payment settings types for clearer hierarchy.
+
+
diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts
index 8c50f40edf..3c4dacd241 100644
--- a/packages/js/data/src/index.ts
+++ b/packages/js/data/src/index.ts
@@ -30,6 +30,7 @@ export { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME } from './product-variations
 export { EXPERIMENTAL_TAX_CLASSES_STORE_NAME } from './tax-classes';
 export { PaymentGateway } from './payment-gateways/types';
 export {
+	PaymentsEntity,
 	PaymentProvider,
 	PaymentProviderType,
 	PaymentProviderState,
diff --git a/packages/js/data/src/payment-settings/test-helpers/stub.ts b/packages/js/data/src/payment-settings/test-helpers/stub.ts
index b7c0f9e0de..81d38f94f1 100644
--- a/packages/js/data/src/payment-settings/test-helpers/stub.ts
+++ b/packages/js/data/src/payment-settings/test-helpers/stub.ts
@@ -17,7 +17,6 @@ export const providersStub: PaymentProvider[] = [
 		title: 'PayPal Payments',
 		description:
 			'Safe and secure payments using credit cards or your customer&#039;s PayPal account.',
-		short_description: '',
 		image: 'http://localhost:8082/wp-content/plugins/woocommerce/assets/images/onboarding/paypal.png',
 		icon: 'http://localhost:8082/wp-content/plugins/woocommerce/assets/images/payment_methods/72x72/paypal.png',
 		links: [
@@ -41,6 +40,9 @@ export const providersStub: PaymentProvider[] = [
 			file: 'woocommerce-paypal-payments/woocommerce-paypal-payments',
 			status: 'installed',
 		},
+		onboarding: {
+			type: 'external',
+		},
 		_links: {
 			hide: {
 				href: 'http://localhost:8082/wp-json/wc-admin/settings/payments/suggestion/paypal_full_stack/hide',
@@ -143,6 +145,7 @@ export const providersStub: PaymentProvider[] = [
 			file: 'woocommerce-payments/woocommerce-payments',
 			status: 'active',
 		},
+		_links: {},
 	},
 	{
 		id: '_wc_offline_payment_methods_group',
@@ -157,6 +160,7 @@ export const providersStub: PaymentProvider[] = [
 			status: 'active',
 		},
 		icon: 'http://localhost:8082/wp-content/plugins/woocommerce/assets/images/payment_methods/cod.svg',
+		_links: {},
 	},
 ];

@@ -202,6 +206,7 @@ export const offlinePaymentGatewaysStub: OfflinePaymentMethodProvider[] = [
 			file: 'woocommerce/woocommerce',
 			status: 'active',
 		},
+		_links: {},
 	},
 	{
 		id: 'cheque',
@@ -244,6 +249,7 @@ export const offlinePaymentGatewaysStub: OfflinePaymentMethodProvider[] = [
 			file: 'woocommerce/woocommerce',
 			status: 'active',
 		},
+		_links: {},
 	},
 	{
 		id: 'cod',
@@ -286,6 +292,7 @@ export const offlinePaymentGatewaysStub: OfflinePaymentMethodProvider[] = [
 			file: 'woocommerce/woocommerce',
 			status: 'active',
 		},
+		_links: {},
 	},
 ];

@@ -330,6 +337,7 @@ export const suggestionsStub: SuggestedPaymentExtension[] = [
 		],
 		tags: [],
 		category: 'psp',
+		_links: {},
 	},
 	{
 		id: 'square_in_person',
@@ -363,6 +371,7 @@ export const suggestionsStub: SuggestedPaymentExtension[] = [
 		],
 		tags: [ 'made_in_woo' ],
 		category: 'psp',
+		_links: {},
 	},
 ];

diff --git a/packages/js/data/src/payment-settings/types.ts b/packages/js/data/src/payment-settings/types.ts
index c41c69b6ca..147daaae89 100644
--- a/packages/js/data/src/payment-settings/types.ts
+++ b/packages/js/data/src/payment-settings/types.ts
@@ -3,6 +3,7 @@ export interface PaymentGatewayLink {
 	url: string;
 }

+// Represents the plugin details for a payment provider.
 export interface PluginData {
 	_type?: string;
 	slug: string; // The plugin slug (e.g. 'woocommerce'). This is also the directory name of the plugin.
@@ -73,39 +74,52 @@ export type PaymentProviderOnboardingState = {
 	completed: boolean;
 	test_mode: boolean;
 	wpcom_has_working_connection?: boolean;
+	wpcom_is_store_connected?: boolean;
+	wpcom_has_connected_owner?: boolean;
+	wpcom_is_connection_owner?: boolean;
 };

-// General payment provider type.
-export type PaymentProvider = {
+// Represents a payments entity, which can be a payment provider or a suggested payment extension outside providers.
+export type PaymentsEntity = {
 	id: string;
-	_order: number;
-	_type: PaymentProviderType;
 	title: string;
 	description: string;
 	icon: string;
+	plugin: PluginData;
+	onboarding?: {
+		_links?: {
+			preload?: LinkData;
+		};
+		type?: string;
+	};
+	_links: Record< string, LinkData >;
+};
+
+// Represents a payment provider for the main providers list.
+export type PaymentProvider = PaymentsEntity & {
+	_type: PaymentProviderType;
+	_order: number; // Used for sorting the providers in the UI.
 	image?: string;
 	supports?: string[];
-	plugin: PluginData;
-	short_description?: string;
 	management?: ManagementData;
 	state?: PaymentProviderState;
 	links?: PaymentGatewayLink[];
 	onboarding?: {
-		state: PaymentProviderOnboardingState;
-		_links: {
-			onboard: LinkData;
+		state?: PaymentProviderOnboardingState;
+		_links?: {
+			onboard?: LinkData; // For gateways, this is used to start the onboarding flow.
 		};
 		recommended_payment_methods?: RecommendedPaymentMethod[];
 		type?: string;
 	};
 	tags?: string[];
 	_suggestion_id?: string;
-	_links?: Record< string, LinkData >;
 	_incentive?: PaymentIncentive;
 };

-// Payment gateway provider type.
+// Represents a payment gateway in the main providers list.
 export type PaymentGatewayProvider = PaymentProvider & {
+	_order: number;
 	supports: string[];
 	management: ManagementData;
 	state: PaymentProviderState;
@@ -119,8 +133,9 @@ export type PaymentGatewayProvider = PaymentProvider & {
 	};
 };

-// Offline payment method provider type.
+// Represents an offline payment method provider in the main providers list.
 export type OfflinePaymentMethodProvider = PaymentProvider & {
+	_order: number;
 	supports: string[];
 	management: ManagementData;
 	state: PaymentProviderState;
@@ -132,34 +147,37 @@ export type OfflinePaymentMethodProvider = PaymentProvider & {
 	};
 };

-// Offline payment methods group provider type.
+// Represents an offline payment methods group provider in the main providers list.
 export type OfflinePmsGroupProvider = PaymentProvider & {
+	_order: number;
 	management: ManagementData;
-	state: PaymentProviderState;
 };

+// Represents a payment extension suggestion provider in the main providers list.
 export type PaymentExtensionSuggestionProvider = PaymentProvider & {
+	_order: number;
+	onboarding: {
+		state: PaymentProviderOnboardingState;
+		_links: {
+			preload?: LinkData;
+		};
+		type?: string;
+	};
 	_suggestion_id: string;
 	_links: {
 		hide: LinkData;
 	};
 };

-// Payment extension suggestion outside providers.
-export type SuggestedPaymentExtension = {
-	id: string;
+// Represents a suggested payment extension outside the main providers list.
+export type SuggestedPaymentExtension = PaymentsEntity & {
 	_type: string;
 	_priority: number;
 	category: string;
-	title: string;
-	description: string;
-	icon: string;
 	image: string;
 	short_description: string;
 	tags: string[];
-	plugin: PluginData;
 	links: PaymentGatewayLink[];
-	_links?: Record< string, LinkData >;
 	_incentive?: PaymentIncentive;
 };

diff --git a/packages/js/onboarding/changelog/update-nox-in-context-onboarding-preload b/packages/js/onboarding/changelog/update-nox-in-context-onboarding-preload
new file mode 100644
index 0000000000..da09434bda
--- /dev/null
+++ b/packages/js/onboarding/changelog/update-nox-in-context-onboarding-preload
@@ -0,0 +1,5 @@
+Significance: patch
+Type: tweak
+Comment: Use the renamed types from woocommerce/data.
+
+
diff --git a/plugins/woocommerce/changelog/update-nox-in-context-onboarding-preload b/plugins/woocommerce/changelog/update-nox-in-context-onboarding-preload
new file mode 100644
index 0000000000..10ba760e35
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-nox-in-context-onboarding-preload
@@ -0,0 +1,5 @@
+Significance: patch
+Type: tweak
+Comment: Add onboarding preload mechanics for NOX in-context flows.
+
+
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/ellipsis-menu-content/ellipsis-menu.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/ellipsis-menu-content/ellipsis-menu.tsx
index 36150a4270..0e72334043 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/ellipsis-menu-content/ellipsis-menu.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/ellipsis-menu-content/ellipsis-menu.tsx
@@ -41,8 +41,8 @@ export const EllipsisMenuWrapper = ( {
 		isWooPayments( provider.id ) &&
 		provider._type === 'gateway' &&
 		provider.state?.account_connected &&
-		( provider.onboarding?.state.test_mode ||
-			! provider.onboarding?.state.completed );
+		( provider.onboarding?.state?.test_mode ||
+			! provider.onboarding?.state?.completed );

 	return (
 		<>
@@ -74,7 +74,7 @@ export const EllipsisMenuWrapper = ( {
 			<WooPaymentsResetAccountModal
 				isOpen={ resetAccountModalVisible }
 				onClose={ () => setResetAccountModalVisible( false ) }
-				isTestMode={ provider.onboarding?.state.test_mode }
+				isTestMode={ provider.onboarding?.state?.test_mode }
 			/>
 		</>
 	);
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/incentive-banner.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/incentive-banner.tsx
index db6651fd76..7f0bc13e66 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/incentive-banner.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/incentive-banner.tsx
@@ -6,7 +6,11 @@ import { Button, Card, CardBody } from '@wordpress/components';
 import { __ } from '@wordpress/i18n';
 import { createInterpolateElement, useState } from '@wordpress/element';
 import { Link } from '@woocommerce/components';
-import { PaymentIncentive, PaymentProvider } from '@woocommerce/data';
+import {
+	PaymentIncentive,
+	PaymentProvider,
+	PaymentsEntity,
+} from '@woocommerce/data';

 /**
  * Internal dependencies
@@ -53,13 +57,12 @@ interface IncentiveBannerProps {
 	/**
 	 * Callback to set up the plugin.
 	 *
-	 * @param id            Extension ID.
-	 * @param slug          Extension slug.
-	 * @param onboardingUrl Onboarding URL (if available).
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
 	 */
-	setupPlugin: (
-		id: string,
-		slug: string,
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -76,7 +79,7 @@ export const IncentiveBanner = ( {
 	onboardingUrl,
 	onDismiss,
 	onAccept,
-	setupPlugin,
+	setUpPlugin,
 }: IncentiveBannerProps ) => {
 	const [ isSubmitted, setIsSubmitted ] = useState( false );
 	const [ isDismissed, setIsDismissed ] = useState( false );
@@ -114,9 +117,8 @@ export const IncentiveBanner = ( {
 		// But do not track this since it is not a true dismissal.
 		onDismiss( incentive._links.dismiss.href, context, true );
 		setIsSubmitted( true );
-		setupPlugin(
-			provider.id,
-			provider.plugin.slug,
+		setUpPlugin(
+			provider,
 			onboardingUrl,
 			provider.plugin.status === 'not_installed'
 				? provider._links?.attach?.href ?? null
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/test/incentive-banner.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/test/incentive-banner.test.tsx
index d42ae74cfd..1e6ed41e24 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/test/incentive-banner.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-banner/test/incentive-banner.test.tsx
@@ -52,6 +52,7 @@ const testProvider: PaymentProvider = {
 		file: 'test-plugin-file',
 		status: 'active',
 	} as PluginData,
+	_links: {},
 	_suggestion_id: 'test-suggestion-id',
 };

@@ -64,7 +65,7 @@ describe( 'IncentiveBanner', () => {
 				onboardingUrl="https://example.com"
 				onAccept={ jest.fn() }
 				onDismiss={ jest.fn() }
-				setupPlugin={ jest.fn() }
+				setUpPlugin={ jest.fn() }
 			/>
 		);

@@ -89,7 +90,7 @@ describe( 'IncentiveBanner', () => {
 				onboardingUrl="https://example.com"
 				onAccept={ onAccept }
 				onDismiss={ jest.fn() }
-				setupPlugin={ jest.fn() }
+				setUpPlugin={ jest.fn() }
 			/>
 		);

diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/incentive-modal.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/incentive-modal.tsx
index a8db1cdf52..c8aaab08a7 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/incentive-modal.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/incentive-modal.tsx
@@ -12,7 +12,11 @@ import {
 import { __ } from '@wordpress/i18n';
 import { createInterpolateElement, useState } from '@wordpress/element';
 import { Link } from '@woocommerce/components';
-import { PaymentIncentive, PaymentProvider } from '@woocommerce/data';
+import {
+	PaymentIncentive,
+	PaymentProvider,
+	PaymentsEntity,
+} from '@woocommerce/data';

 /**
  * Internal dependencies
@@ -59,13 +63,12 @@ interface IncentiveModalProps {
 	/**
 	 * Callback to set up the plugin.
 	 *
-	 * @param id            Extension ID.
-	 * @param slug          Extension slug.
-	 * @param onboardingUrl Onboarding URL (if available).
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
 	 */
-	setupPlugin: (
-		id: string,
-		slug: string,
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -86,7 +89,7 @@ export const IncentiveModal = ( {
 	onboardingUrl,
 	onAccept,
 	onDismiss,
-	setupPlugin,
+	setUpPlugin,
 }: IncentiveModalProps ) => {
 	const [ isBusy, setIsBusy ] = useState( false );
 	const [ isOpen, setIsOpen ] = useState( true );
@@ -102,7 +105,7 @@ export const IncentiveModal = ( {
 			suggestion_id: provider._suggestion_id ?? '',
 			display_context: context,
 		} );
-	}, [ incentive.promo_id, provider.id ] );
+	}, [ incentive, provider ] );

 	/**
 	 * Closes the modal.
@@ -130,9 +133,8 @@ export const IncentiveModal = ( {
 		// We also dismiss the incentive when it is accepted.
 		onDismiss( incentive._links.dismiss.href, context, true );
 		handleClose(); // Close the modal.
-		setupPlugin(
-			provider.id,
-			provider.plugin.slug,
+		setUpPlugin(
+			provider,
 			onboardingUrl,
 			provider.plugin.status === 'not_installed'
 				? provider._links?.attach?.href ?? null
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/test/incentive-modal.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/test/incentive-modal.test.tsx
index cb756d05ee..b52130dbcb 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/test/incentive-modal.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/incentive-modal/test/incentive-modal.test.tsx
@@ -52,6 +52,7 @@ const testProvider: PaymentProvider = {
 		file: 'test-plugin-file',
 		status: 'active',
 	} as PluginData,
+	_links: {},
 	_suggestion_id: 'test-suggestion-id',
 };

@@ -64,7 +65,7 @@ describe( 'IncentiveModal', () => {
 				onboardingUrl="https://example.com"
 				onAccept={ jest.fn() }
 				onDismiss={ jest.fn() }
-				setupPlugin={ jest.fn() }
+				setUpPlugin={ jest.fn() }
 			/>
 		);

@@ -89,7 +90,7 @@ describe( 'IncentiveModal', () => {
 				onboardingUrl="https://example.com"
 				onAccept={ onAccept }
 				onDismiss={ jest.fn() }
-				setupPlugin={ jest.fn() }
+				setUpPlugin={ jest.fn() }
 			/>
 		);

diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/other-payment-gateways/other-payment-gateways.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/other-payment-gateways/other-payment-gateways.tsx
index 20bf76e4fb..8587566136 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/other-payment-gateways/other-payment-gateways.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/other-payment-gateways/other-payment-gateways.tsx
@@ -8,6 +8,7 @@ import { useState, useMemo, useRef } from '@wordpress/element';
 import { __ } from '@wordpress/i18n';
 import { decodeEntities } from '@wordpress/html-entities';
 import {
+	PaymentsEntity,
 	SuggestedPaymentExtension,
 	SuggestedPaymentExtensionCategory,
 } from '@woocommerce/data';
@@ -34,11 +35,14 @@ interface OtherPaymentGatewaysProps {
 	 */
 	installingPlugin: string | null;
 	/**
-	 * Callback to handle plugin setup. Accepts the plugin ID, slug, and onboarding URL (if available).
+	 * Callback to set up the plugin.
+	 *
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
 	 */
-	setupPlugin: (
-		id: string,
-		slug: string,
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -61,7 +65,7 @@ export const OtherPaymentGateways = ( {
 	suggestions,
 	suggestionCategories,
 	installingPlugin,
-	setupPlugin,
+	setUpPlugin,
 	isFetching,
 	morePaymentOptionsLink,
 }: OtherPaymentGatewaysProps ) => {
@@ -286,10 +290,8 @@ export const OtherPaymentGateways = ( {
 												<Button
 													variant="link"
 													onClick={ () =>
-														setupPlugin(
-															extension.id,
-															extension.plugin
-																.slug,
+														setUpPlugin(
+															extension,
 															null, // Suggested gateways won't have an onboarding URL.
 															// Only provide the attach link if not already installed.
 															extension.plugin
@@ -335,7 +337,7 @@ export const OtherPaymentGateways = ( {
 	}, [
 		suggestionsByCategory,
 		installingPlugin,
-		setupPlugin,
+		setUpPlugin,
 		isFetching,
 		categoryIdWithPopoverVisible,
 	] );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/payment-extension-suggestion-list-item.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/payment-extension-suggestion-list-item.tsx
index a6398c130f..9966f26a9b 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/payment-extension-suggestion-list-item.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/payment-extension-suggestion-list-item.tsx
@@ -5,7 +5,10 @@ import { decodeEntities } from '@wordpress/html-entities';
 import { Button } from '@wordpress/components';
 import { __ } from '@wordpress/i18n';
 import { WooPaymentsMethodsLogos } from '@woocommerce/onboarding';
-import { PaymentExtensionSuggestionProvider } from '@woocommerce/data';
+import {
+	PaymentExtensionSuggestionProvider,
+	PaymentsEntity,
+} from '@woocommerce/data';

 /**
  * Internal dependencies
@@ -33,11 +36,14 @@ type PaymentExtensionSuggestionListItemProps = {
 	 */
 	installingPlugin: string | null;
 	/**
-	 * Callback function to handle the setup of the plugin. Receives the plugin ID, slug, and onboarding URL (if available).
+	 * Callback to set up the plugin.
+	 *
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
 	 */
-	setupPlugin: (
-		id: string,
-		slug: string,
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -63,7 +69,7 @@ type PaymentExtensionSuggestionListItemProps = {
 export const PaymentExtensionSuggestionListItem = ( {
 	suggestion,
 	installingPlugin,
-	setupPlugin,
+	setUpPlugin,
 	pluginInstalled,
 	acceptIncentive,
 	shouldHighlightIncentive = false,
@@ -151,11 +157,10 @@ export const PaymentExtensionSuggestionListItem = ( {
 									acceptIncentive( incentive.promo_id );
 								}

-								setupPlugin(
-									suggestion.id,
-									suggestion.plugin.slug,
-									suggestion.onboarding?._links.onboard
-										.href ?? null,
+								setUpPlugin(
+									suggestion,
+									suggestion.onboarding?._links?.onboard
+										?.href ?? null,
 									pluginInstalled
 										? null
 										: suggestion._links?.attach?.href ??
@@ -173,7 +178,7 @@ export const PaymentExtensionSuggestionListItem = ( {
 					<div className="woocommerce-list__item-after__actions">
 						<EllipsisMenu
 							label={ __(
-								'Payment provider options',
+								'Payment provider actions',
 								'woocommerce'
 							) }
 							provider={ suggestion }
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/test/payment-extension-suggestion-list-item.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/test/payment-extension-suggestion-list-item.test.tsx
index 099781f081..5209ba5a30 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/test/payment-extension-suggestion-list-item.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-extension-suggestion-list-item/test/payment-extension-suggestion-list-item.test.tsx
@@ -40,9 +40,10 @@ describe( 'PaymentExtensionSuggestionListItem', () => {
 					} as unknown as PaymentExtensionSuggestionProvider
 				}
 				installingPlugin={ null }
-				setupPlugin={ () => {} }
+				setUpPlugin={ () => {} }
 				pluginInstalled={ true }
 				acceptIncentive={ () => {} }
+				shouldHighlightIncentive={ false }
 			/>
 		);

diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
index 5ca3ae8dc1..d46ce2936e 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
@@ -2,6 +2,7 @@
  * External dependencies
  */
 import {
+	PaymentsEntity,
 	PaymentProvider,
 	PaymentProviderType,
 	PaymentGatewayProvider,
@@ -36,11 +37,14 @@ interface PaymentGatewayListProps {
 	 */
 	installingPlugin: string | null;
 	/**
-	 * Callback to handle the setup of a plugin. Receives the plugin ID, slug, and onboarding URL (if available).
+	 * Callback to set up the plugin.
+	 *
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
 	 */
-	setupPlugin: (
-		id: string,
-		slug: string,
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -74,7 +78,7 @@ export const PaymentGatewayList = ( {
 	providers,
 	installedPluginSlugs,
 	installingPlugin,
-	setupPlugin,
+	setUpPlugin,
 	acceptIncentive,
 	shouldHighlightIncentive,
 	updateOrdering,
@@ -103,7 +107,7 @@ export const PaymentGatewayList = ( {
 								{ PaymentExtensionSuggestionListItem( {
 									suggestion,
 									installingPlugin,
-									setupPlugin,
+									setUpPlugin,
 									pluginInstalled,
 									acceptIncentive,
 									shouldHighlightIncentive,
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateways/payment-gateways.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateways/payment-gateways.tsx
index a055f1290f..0c5253238c 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateways/payment-gateways.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateways/payment-gateways.tsx
@@ -5,13 +5,14 @@ import { __ } from '@wordpress/i18n';
 import apiFetch from '@wordpress/api-fetch';
 import clsx from 'clsx';
 import {
+	PaymentsEntity,
 	PaymentProvider,
 	paymentSettingsStore,
-	woopaymentsOnboardingStore,
 	WC_ADMIN_NAMESPACE,
+	woopaymentsOnboardingStore,
 } from '@woocommerce/data';
 import { useDispatch } from '@wordpress/data';
-import { useMemo, useState, useRef } from '@wordpress/element';
+import { useMemo, useRef, useState } from '@wordpress/element';
 import { decodeEntities } from '@wordpress/html-entities';
 import { Popover } from '@wordpress/components';
 import { Link } from '@woocommerce/components';
@@ -31,9 +32,15 @@ interface PaymentGatewaysProps {
 	providers: PaymentProvider[];
 	installedPluginSlugs: string[];
 	installingPlugin: string | null;
-	setupPlugin: (
-		id: string,
-		slug: string,
+	/**
+	 * Callback to set up the plugin.
+	 *
+	 * @param provider      Extension provider.
+	 * @param onboardingUrl Extension onboarding URL (if available).
+	 * @param attachUrl     Extension attach URL (if available).
+	 */
+	setUpPlugin: (
+		provider: PaymentsEntity,
 		onboardingUrl: string | null,
 		attachUrl: string | null
 	) => void;
@@ -55,7 +62,7 @@ export const PaymentGateways = ( {
 	providers,
 	installedPluginSlugs,
 	installingPlugin,
-	setupPlugin,
+	setUpPlugin,
 	acceptIncentive,
 	shouldHighlightIncentive,
 	updateOrdering,
@@ -140,27 +147,29 @@ export const PaymentGateways = ( {
 							) ?? { key: 'US', name: 'United States (US)' }
 						}
 						options={ countryOptions }
-						onChange={ ( value: string ) => {
+						onChange={ ( currentSelectedCountry: string ) => {
 							// Save selected country and refresh the store by invalidating getPaymentProviders.
 							apiFetch( {
 								path:
 									WC_ADMIN_NAMESPACE +
 									'/settings/payments/country',
 								method: 'POST',
-								data: { location: value },
+								data: { location: currentSelectedCountry },
 							} ).then( () => {
 								// Update UI.
-								setBusinessRegistrationCountry( value );
+								setBusinessRegistrationCountry(
+									currentSelectedCountry
+								);
 								// Update the window value - this will be updated by the backend on refresh but this keeps state persistent.
 								if (
 									window.wcSettings.admin
 										.woocommerce_payments_nox_profile
 								) {
 									window.wcSettings.admin.woocommerce_payments_nox_profile.business_country_code =
-										value;
+										currentSelectedCountry;
 								}
 								invalidateMainStore( 'getPaymentProviders', [
-									value,
+									currentSelectedCountry,
 								] );
 								invalidateWooPaymentsOnboardingStore(
 									'getOnboardingData',
@@ -249,7 +258,7 @@ export const PaymentGateways = ( {
 					providers={ providers }
 					installedPluginSlugs={ installedPluginSlugs }
 					installingPlugin={ installingPlugin }
-					setupPlugin={ setupPlugin }
+					setUpPlugin={ setUpPlugin }
 					acceptIncentive={ acceptIncentive }
 					shouldHighlightIncentive={ shouldHighlightIncentive }
 					updateOrdering={ updateOrdering }
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/index.tsx b/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
index 08467c71ea..30bbb7fa91 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
@@ -231,7 +231,7 @@ export const SettingsPaymentsMethods = () => {

 		// Get the onboarding URL or fallback to the test drive account link.
 		const onboardUrl =
-			wooPayments?.onboarding?._links.onboard.href ||
+			wooPayments?.onboarding?._links?.onboard?.href ||
 			getWooPaymentsTestDriveAccountLink();

 		// Combine the onboard URL with the query string and redirect to the onboard URL.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/settings-payments-main.tsx b/plugins/woocommerce/client/admin/client/settings-payments/settings-payments-main.tsx
index 08b22b4185..51d4eda027 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/settings-payments-main.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/settings-payments-main.tsx
@@ -7,6 +7,7 @@ import {
 	pluginsStore,
 	paymentSettingsStore,
 	PaymentProvider,
+	PaymentsEntity,
 } from '@woocommerce/data';
 import { resolveSelect, useDispatch, useSelect } from '@wordpress/data';
 import React, { useState, useEffect } from '@wordpress/element';
@@ -298,10 +299,9 @@ export const SettingsPaymentsMain = () => {
 		recordPaymentsEvent( 'recommendations_pageview', eventProps );
 	}, [ suggestions, providers, isFetching ] );

-	const setupPlugin = useCallback(
+	const setUpPlugin = useCallback(
 		(
-			id: string,
-			slug: string,
+			paymentsEntity: PaymentsEntity,
 			onboardingUrl: string | null,
 			attachUrl: string | null
 		) => {
@@ -309,17 +309,28 @@ export const SettingsPaymentsMain = () => {
 				return;
 			}

-			// A fail-safe to ensure that the onboarding URL is set for Woo Payments.
-			// Note: We should get rid this sooner rather than later!
-			if ( ! onboardingUrl && isWooPayments( id ) ) {
+			if ( paymentsEntity?.onboarding?._links?.preload?.href ) {
+				// We are not interested in the response; we just want to trigger the preload.
+				apiFetch( {
+					url: paymentsEntity?.onboarding?._links?.preload.href,
+					method: 'POST',
+					data: {
+						location: storeCountry,
+					},
+				} );
+			}
+
+			// A fail-safe to ensure that the onboarding URL is set for WooPayments.
+			// Note: We should get rid of this sooner rather than later!
+			if ( ! onboardingUrl && isWooPayments( paymentsEntity.id ) ) {
 				onboardingUrl = getWooPaymentsTestDriveAccountLink();
 			}

-			setInstallingPlugin( id );
+			setInstallingPlugin( paymentsEntity.id );
 			recordPaymentsEvent( 'recommendations_setup', {
-				extension_selected: slug,
+				extension_selected: paymentsEntity.plugin.slug,
 			} );
-			installAndActivatePlugins( [ slug ] )
+			installAndActivatePlugins( [ paymentsEntity.plugin.slug ] )
 				.then( async ( response ) => {
 					if ( attachUrl ) {
 						attachPaymentExtensionSuggestion( attachUrl );
@@ -332,7 +343,7 @@ export const SettingsPaymentsMain = () => {

 					// Record the plugin installation event.
 					recordPaymentsEvent( 'provider_installed', {
-						provider_id: id,
+						provider_id: paymentsEntity.id,
 					} );

 					// Wait for the state update and fetch the latest providers.
@@ -341,16 +352,16 @@ export const SettingsPaymentsMain = () => {
 					).getPaymentProviders( storeCountry );

 					// Find the matching provider in the updated list.
-					const updatedProvider = updatedProviders.find(
-						( provider: PaymentProvider ) =>
-							provider.id === id ||
-							provider?._suggestion_id === id || // For suggestions that were replaced by a gateway.
-							provider.plugin.slug === slug // Last resort to find the provider.
+					const updatedPaymentsEntity = updatedProviders.find(
+						( current: PaymentProvider ) =>
+							current.id === paymentsEntity.id ||
+							current?._suggestion_id === paymentsEntity.id || // For suggestions that were replaced by a gateway.
+							current.plugin.slug === paymentsEntity.plugin.slug // Last resort to find the provider.
 					);

-					// Record the event when user successfully enables a gateway.
+					// Record the event when the user successfully enables a gateway.
 					recordPaymentsEvent( 'provider_enable', {
-						provider_id: id,
+						provider_id: paymentsEntity.id,
 					} );

 					/**
@@ -358,7 +369,7 @@ export const SettingsPaymentsMain = () => {
 					 * Otherwise, we redirect to the onboarding URL or the payment methods page.
 					 */
 					if (
-						updatedProvider?.onboarding?.type ===
+						updatedPaymentsEntity?.onboarding?.type ===
 						'native_in_context'
 					) {
 						setIsOnboardingModalOpen( true );
@@ -368,7 +379,7 @@ export const SettingsPaymentsMain = () => {
 						// redirect to the payment methods page.
 						if (
 							(
-								updatedProvider?.onboarding
+								updatedPaymentsEntity?.onboarding
 									?.recommended_payment_methods ?? []
 							).length > 0
 						) {
@@ -455,12 +466,12 @@ export const SettingsPaymentsMain = () => {
 					incentive={ incentive }
 					provider={ incentiveProvider }
 					onboardingUrl={
-						incentiveProvider.onboarding?._links.onboard.href ??
+						incentiveProvider.onboarding?._links?.onboard?.href ??
 						null
 					}
 					onDismiss={ dismissIncentive }
 					onAccept={ acceptIncentive }
-					setupPlugin={ setupPlugin }
+					setUpPlugin={ setUpPlugin }
 				/>
 			) }
 			{ errorMessage && (
@@ -480,12 +491,12 @@ export const SettingsPaymentsMain = () => {
 					incentive={ incentive }
 					provider={ incentiveProvider }
 					onboardingUrl={
-						incentiveProvider.onboarding?._links.onboard.href ??
+						incentiveProvider.onboarding?._links?.onboard?.href ??
 						null
 					}
 					onDismiss={ dismissIncentive }
 					onAccept={ acceptIncentive }
-					setupPlugin={ setupPlugin }
+					setUpPlugin={ setUpPlugin }
 				/>
 			) }
 			<div className="settings-payments-main__container">
@@ -493,7 +504,7 @@ export const SettingsPaymentsMain = () => {
 					providers={ sortedProviders || providers }
 					installedPluginSlugs={ installedPluginSlugs }
 					installingPlugin={ installingPlugin }
-					setupPlugin={ setupPlugin }
+					setUpPlugin={ setUpPlugin }
 					acceptIncentive={ acceptIncentive }
 					shouldHighlightIncentive={ shouldHighlightIncentive }
 					updateOrdering={ handleOrderingUpdate }
@@ -515,7 +526,7 @@ export const SettingsPaymentsMain = () => {
 						suggestions={ suggestions }
 						suggestionCategories={ suggestionCategories }
 						installingPlugin={ installingPlugin }
-						setupPlugin={ setupPlugin }
+						setUpPlugin={ setUpPlugin }
 						isFetching={ isFetching }
 						morePaymentOptionsLink={ morePaymentOptionsLink }
 					/>
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders.php
index 2cd85b041d..98d2ed8265 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders.php
@@ -75,6 +75,23 @@ class PaymentProviders {
 		'woo-mercado-pago-*'        => MercadoPago::class,
 	);

+	/**
+	 * The map of payment extension suggestion IDs to their respective provider classes.
+	 *
+	 * This is used to instantiate providers to provide details for the payment extension suggestions, pre-attachment.
+	 *
+	 * @var \class-string[]
+	 */
+	private array $payment_extension_suggestions_providers_class_map = array(
+		ExtensionSuggestions::WOOPAYMENTS       => WooPayments::class,
+		ExtensionSuggestions::PAYPAL_FULL_STACK => PayPal::class,
+		ExtensionSuggestions::PAYPAL_WALLET     => PayPal::class,
+		ExtensionSuggestions::STRIPE            => Stripe::class,
+		ExtensionSuggestions::MOLLIE            => Mollie::class,
+		ExtensionSuggestions::AMAZON_PAY        => AmazonPay::class,
+		ExtensionSuggestions::MERCADO_PAGO      => MercadoPago::class,
+	);
+
 	/**
 	 * The instances of the payment providers.
 	 *
@@ -209,6 +226,43 @@ class PaymentProviders {
 		return $this->instances[ $gateway_id ];
 	}

+	/**
+	 * Get the payment extension suggestion (PES) provider instance.
+	 *
+	 * @param string $pes_id The payment extension suggestion ID.
+	 *
+	 * @return PaymentGateway The payment extension suggestion provider instance.
+	 *                        Will return the general provider of no specific provider is found.
+	 */
+	public function get_payment_extension_suggestion_provider_instance( string $pes_id ): PaymentGateway {
+		if ( isset( $this->instances[ $pes_id ] ) ) {
+			return $this->instances[ $pes_id ];
+		}
+
+		/**
+		 * The provider class for the payment extension suggestion (PES).
+		 *
+		 * @var PaymentGateway|null $provider_class
+		 */
+		$provider_class = null;
+		if ( isset( $this->payment_extension_suggestions_providers_class_map[ $pes_id ] ) ) {
+			$provider_class = $this->payment_extension_suggestions_providers_class_map[ $pes_id ];
+		}
+
+		// If the gateway ID is not mapped to a provider class, return the generic provider.
+		if ( is_null( $provider_class ) ) {
+			if ( ! isset( $this->instances['generic'] ) ) {
+				$this->instances['generic'] = new PaymentGateway();
+			}
+
+			return $this->instances['generic'];
+		}
+
+		$this->instances[ $pes_id ] = new $provider_class();
+
+		return $this->instances[ $pes_id ];
+	}
+
 	/**
 	 * Get the payment gateways details.
 	 *
@@ -1113,6 +1167,10 @@ class PaymentProviders {
 			}
 		}

+		// Finally, allow the extension suggestion's matching provider to add further details.
+		$gateway_provider = $this->get_payment_extension_suggestion_provider_instance( $extension['id'] );
+		$extension        = $gateway_provider->enhance_extension_suggestion( $extension );
+
 		return $extension;
 	}

diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/PaymentGateway.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/PaymentGateway.php
index e8701ab5c5..53b64ec1e2 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/PaymentGateway.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/PaymentGateway.php
@@ -86,6 +86,27 @@ class PaymentGateway {
 		);
 	}

+	/**
+	 * Enhance this provider's payment extension suggestion with additional information.
+	 *
+	 * The details added do not require the payment extension to be active or a gateway instance.
+	 *
+	 * @param array $extension_suggestion The extension suggestion details.
+	 *
+	 * @return array The enhanced payment extension suggestion details.
+	 */
+	public function enhance_extension_suggestion( array $extension_suggestion ): array {
+		if ( empty( $extensionp['onboarding'] ) || ! is_array( $extension_suggestion['onboarding'] ) ) {
+			$extension_suggestion['onboarding'] = array();
+		}
+
+		if ( ! isset( $extension_suggestion['onboarding']['type'] ) ) {
+			$extension_suggestion['onboarding']['type'] = self::ONBOARDING_TYPE_EXTERNAL;
+		}
+
+		return $extension_suggestion;
+	}
+
 	/**
 	 * Get the provider title of the payment gateway.
 	 *
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments.php
index b69ac8e2aa..9cfed8ccdf 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments.php
@@ -4,12 +4,16 @@ declare( strict_types=1 );
 namespace Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;

 use Automattic\Jetpack\Connection\Manager as WPCOM_Connection_Manager;
+use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Admin\PluginsHelper;
 use Automattic\WooCommerce\Admin\WCAdminHelper;
 use Automattic\WooCommerce\Enums\OrderInternalStatus;
 use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WooPayments\WooPaymentsRestController;
 use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
 use Automattic\WooCommerce\Internal\Admin\Settings\Utils;
+use Automattic\WooCommerce\Internal\Logging\SafeGlobalFunctionProxy;
 use WC_Abstract_Order;
 use WC_Payment_Gateway;
 use WooCommerce\Admin\Experimental_Abtest;
@@ -43,23 +47,12 @@ class WooPayments extends PaymentGateway {
 		$details['onboarding']['type'] = self::ONBOARDING_TYPE_NATIVE;

 		// Add WPCOM/Jetpack connection details to the onboarding state.
-		$wpcom_connection_manager       = new WPCOM_Connection_Manager( 'woocommerce' );
-		$is_connected                   = $wpcom_connection_manager->is_connected();
-		$has_connected_owner            = $wpcom_connection_manager->has_connected_owner();
-		$details['onboarding']['state'] = array_merge(
-			$details['onboarding']['state'],
-			array(
-				'wpcom_has_working_connection' => $is_connected && $has_connected_owner,
-				'wpcom_is_store_connected'     => $is_connected,
-				'wpcom_has_connected_owner'    => $has_connected_owner,
-				'wpcom_is_connection_owner'    => $has_connected_owner && $wpcom_connection_manager->is_connection_owner(),
-			)
-		);
+		$details['onboarding']['state'] = array_merge( $details['onboarding']['state'], $this->get_wpcom_connection_state() );

 		// If the WooPayments installed version is less than minimum required version,
 		// we can't use the in-context onboarding flows.
-		if ( defined( 'WCPAY_VERSION_NUMBER' ) &&
-			version_compare( WCPAY_VERSION_NUMBER, PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION, '<' ) ) {
+		if ( Constants::is_defined( 'WCPAY_VERSION_NUMBER' ) &&
+			version_compare( Constants::get_constant( 'WCPAY_VERSION_NUMBER' ), PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION, '<' ) ) {

 			return $details;
 		}
@@ -76,6 +69,101 @@ class WooPayments extends PaymentGateway {
 		return $details;
 	}

+	/**
+	 * Enhance this provider's payment extension suggestion with additional information.
+	 *
+	 * The details added do not require the payment extension to be active or a gateway instance.
+	 *
+	 * @param array $extension_suggestion The extension suggestion details.
+	 *
+	 * @return array The enhanced payment extension suggestion details.
+	 */
+	public function enhance_extension_suggestion( array $extension_suggestion ): array {
+		$extension_suggestion = parent::enhance_extension_suggestion( $extension_suggestion );
+
+		// If the extension is installed, we can get the plugin data and act upon it.
+		if ( ! empty( $extension_suggestion['plugin']['file'] ) &&
+			isset( $extension_suggestion['plugin']['status'] ) &&
+			in_array( $extension_suggestion['plugin']['status'], array( PaymentProviders::EXTENSION_INSTALLED, PaymentProviders::EXTENSION_ACTIVE ), true ) ) {
+
+			// Switch to the native in-context onboarding type if the WooPayments extension its version is compatible.
+			// We need to put back the '.php' extension to construct the plugin filename.
+			$plugin_data = PluginsHelper::get_plugin_data( $extension_suggestion['plugin']['file'] . '.php' );
+			if ( $plugin_data && ! empty( $plugin_data['Version'] ) &&
+				version_compare( $plugin_data['Version'], PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION, '>=' ) ) {
+
+				$extension_suggestion['onboarding']['type'] = self::ONBOARDING_TYPE_NATIVE_IN_CONTEXT;
+			}
+		} else {
+			// We assume the latest version of the WooPayments extension will be installed.
+			$extension_suggestion['onboarding']['type'] = self::ONBOARDING_TYPE_NATIVE_IN_CONTEXT;
+		}
+
+		// Add onboarding state.
+		if ( ! isset( $extension_suggestion['onboarding']['state'] ) || ! is_array( $extension_suggestion['onboarding']['state'] ) ) {
+			$extension_suggestion['onboarding']['state'] = array();
+		}
+		// Add the store's WPCOM/Jetpack connection state to the onboarding state.
+		$extension_suggestion['onboarding']['state'] = array_merge(
+			$extension_suggestion['onboarding']['state'],
+			$this->get_wpcom_connection_state()
+		);
+
+		// Add onboarding links.
+		if ( empty( $extension_suggestion['onboarding']['_links'] ) || ! is_array( $extension_suggestion['onboarding']['_links'] ) ) {
+			$extension_suggestion['onboarding']['_links'] = array();
+		}
+
+		// We only add the preload link if we don't have a working WPCOM connection.
+		// This is because WooPayments onboarding preloading focuses on hydrating the WPCOM connection.
+		if ( ! $extension_suggestion['onboarding']['state']['wpcom_has_working_connection'] ) {
+			try {
+				/**
+				 * The WooPayments REST controller instance.
+				 *
+				 * @var WooPaymentsRestController $rest_controller
+				 */
+				$rest_controller = wc_get_container()->get( WooPaymentsRestController::class );
+
+				// Add the onboarding preload URL.
+				$extension_suggestion['onboarding']['_links']['preload'] = array(
+					'href' => rest_url( $rest_controller->get_rest_url_path( 'onboarding/preload' ) ),
+				);
+			} catch ( \Throwable $e ) {
+				// If the REST controller is not available, we can't preload the onboarding data.
+				// This is not a critical error, so we just ignore it.
+				// Log so we can investigate.
+				SafeGlobalFunctionProxy::wc_get_logger()->error(
+					'Failed to get the WooPayments REST controller instance: ' . $e->getMessage(),
+					array(
+						'source' => 'settings-payments',
+						'error'  => $e,
+					)
+				);
+			}
+		}
+
+		return $extension_suggestion;
+	}
+
+	/**
+	 * Get the current state of the store's WPCOM/Jetpack connection.
+	 *
+	 * @return array The store's WPCOM/Jetpack connection state.
+	 */
+	private function get_wpcom_connection_state(): array {
+		$wpcom_connection_manager = new WPCOM_Connection_Manager( 'woocommerce' );
+		$is_connected             = $wpcom_connection_manager->is_connected();
+		$has_connected_owner      = $wpcom_connection_manager->has_connected_owner();
+
+		return array(
+			'wpcom_has_working_connection' => $is_connected && $has_connected_owner,
+			'wpcom_is_store_connected'     => $is_connected,
+			'wpcom_has_connected_owner'    => $has_connected_owner,
+			'wpcom_is_connection_owner'    => $has_connected_owner && $wpcom_connection_manager->is_connection_owner(),
+		);
+	}
+
 	/**
 	 * Check if the payment gateway needs setup.
 	 *
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestController.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestController.php
index d3127f560b..938aef1b1b 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestController.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestController.php
@@ -282,6 +282,28 @@ class WooPaymentsRestController extends RestApiControllerBase {
 			),
 			$override
 		);
+		register_rest_route(
+			$this->route_namespace,
+			'/' . $this->rest_base . '/onboarding/preload',
+			array(
+				array(
+					'methods'             => \WP_REST_Server::CREATABLE,
+					'callback'            => fn( $request ) => $this->run( $request, 'handle_onboarding_preload' ),
+					'validation_callback' => 'rest_validate_request_arg',
+					'permission_callback' => fn( $request ) => $this->check_permissions( $request ),
+					'args'                => array(
+						'location' => array(
+							'description'       => esc_html__( 'ISO3166 alpha-2 country code. Defaults to the stored providers business location country code.', 'woocommerce' ),
+							'type'              => 'string',
+							'pattern'           => '[a-zA-Z]{2}', // Two alpha characters.
+							'required'          => false,
+							'validate_callback' => fn( $value, $request ) => $this->check_location_arg( $value, $request ),
+						),
+					),
+				),
+			),
+			$override
+		);
 		register_rest_route(
 			$this->route_namespace,
 			'/' . $this->rest_base . '/onboarding/reset',
@@ -313,7 +335,6 @@ class WooPaymentsRestController extends RestApiControllerBase {
 						),
 					),
 				),
-				'schema' => fn() => $this->get_schema_for_get_onboarding_details(),
 			),
 			$override
 		);
@@ -365,6 +386,22 @@ class WooPaymentsRestController extends RestApiControllerBase {
 		);
 	}

+	/**
+	 * Get the controller's REST URL path.
+	 *
+	 * @param string $relative_path Optional. Relative path to append to the REST URL.
+	 *
+	 * @return string The REST URL path.
+	 */
+	public function get_rest_url_path( string $relative_path = '' ): string {
+		$path = '/' . trim( $this->route_namespace, '/' ) . '/' . trim( $this->rest_base, '/' );
+		if ( ! empty( $relative_path ) ) {
+			$path .= '/' . ltrim( $relative_path, '/' );
+		}
+
+		return $path;
+	}
+
 	/**
 	 * Initialize the class instance.
 	 *
@@ -644,6 +681,34 @@ class WooPaymentsRestController extends RestApiControllerBase {
 		return rest_ensure_response( $response );
 	}

+	/**
+	 * Handle the onboarding preload action.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 *
+	 * @return WP_Error|WP_REST_Response The response or error.
+	 */
+	protected function handle_onboarding_preload( WP_REST_Request $request ) {
+		$location = $request->get_param( 'location' );
+		if ( empty( $location ) ) {
+			// Fall back to the providers country if no location is provided.
+			$location = $this->payments->get_country();
+		}
+
+		try {
+			$response = $this->woopayments->onboarding_preload( $location );
+		} catch ( ApiException $e ) {
+			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
+		}
+
+		// If there is no success key in the response, we assume the operation was successful.
+		if ( ! isset( $response['success'] ) ) {
+			$response['success'] = true;
+		}
+
+		return rest_ensure_response( $response );
+	}
+
 	/**
 	 * Handle the onboarding reset action.
 	 *
@@ -1045,20 +1110,4 @@ class WooPaymentsRestController extends RestApiControllerBase {
 			),
 		);
 	}
-
-	/**
-	 * Get the controller's REST URL path.
-	 *
-	 * @param string $relative_path Optional. Relative path to append to the REST URL.
-	 *
-	 * @return string The REST URL path.
-	 */
-	private function get_rest_url_path( string $relative_path = '' ): string {
-		$path = '/' . trim( $this->route_namespace, '/' ) . '/' . trim( $this->rest_base, '/' );
-		if ( ! empty( $relative_path ) ) {
-			$path .= '/' . ltrim( $relative_path, '/' );
-		}
-
-		return $path;
-	}
 }
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsService.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsService.php
index 82be590b8c..39a9d269ff 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsService.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsService.php
@@ -4,6 +4,7 @@ declare( strict_types=1 );
 namespace Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WooPayments;

 use Automattic\Jetpack\Connection\Manager as WPCOM_Connection_Manager;
+use Automattic\Jetpack\Constants;
 use Automattic\WooCommerce\Internal\Admin\Settings\Exceptions\ApiArgumentException;
 use Automattic\WooCommerce\Internal\Admin\Settings\Exceptions\ApiException;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
@@ -187,7 +188,7 @@ class WooPaymentsService {
 			throw new ApiArgumentException(
 				'woocommerce_woopayments_onboarding_invalid_step_id',
 				esc_html__( 'Invalid onboarding step ID.', 'woocommerce' ),
-				(int) WP_Http::NOT_ACCEPTABLE
+				(int) WP_Http::BAD_REQUEST
 			);
 		}

@@ -717,7 +718,7 @@ class WooPaymentsService {
 			throw new ApiArgumentException(
 				'woocommerce_woopayments_onboarding_invalid_step_data',
 				esc_html__( 'Invalid onboarding step data.', 'woocommerce' ),
-				(int) WP_Http::NOT_ACCEPTABLE
+				(int) WP_Http::BAD_REQUEST
 			);
 		}

@@ -1226,6 +1227,49 @@ class WooPaymentsService {
 		return $response;
 	}

+	/**
+	 * Preload the onboarding process.
+	 *
+	 * This method is used to run the heavier logic required for onboarding ahead of time,
+	 * so that we can be quicker to respond to the user when they start the onboarding process.
+	 *
+	 * @return array An array containing the success status and any errors encountered during the preload.
+	 *               'success' => true if the preload was successful, false otherwise.
+	 *               'errors'  => An array of error messages if any errors occurred, empty if no errors.
+	 * @throws ApiException If the onboarding preload failed or the onboarding is locked.
+	 */
+	public function onboarding_preload(): array {
+		// If the onboarding is locked, we shouldn't do anything.
+		if ( $this->is_onboarding_locked() ) {
+			throw new ApiException(
+				'woocommerce_woopayments_onboarding_locked',
+				esc_html__( 'Another onboarding action is already in progress. Please wait for it to finish.', 'woocommerce' ),
+				(int) WP_Http::CONFLICT
+			);
+		}
+
+		$result = true;
+
+		// Register the site to WPCOM if it is not already registered.
+		// This sets up the site for connection. For new sites, this tends to take a while.
+		// It is a prerequisite to generating the WPCOM/Jetpack authorization URL.
+		if ( ! $this->wpcom_connection_manager->is_connected() ) {
+			$result = $this->wpcom_connection_manager->try_registration();
+			if ( is_wp_error( $result ) ) {
+				throw new ApiException(
+					'woocommerce_woopayments_onboarding_action_error',
+					esc_html( $result->get_error_message() ),
+					(int) WP_Http::INTERNAL_SERVER_ERROR,
+					map_deep( (array) $result->get_error_data(), 'esc_html' )
+				);
+			}
+		}
+
+		return array(
+			'success' => $result,
+		);
+	}
+
 	/**
 	 * Reset onboarding.
 	 *
@@ -1422,7 +1466,8 @@ class WooPaymentsService {
 		}

 		// If the WooPayments installed version is less than the minimum required version, we can't do anything.
-		if ( defined( 'WCPAY_VERSION_NUMBER' ) && version_compare( WCPAY_VERSION_NUMBER, self::EXTENSION_MINIMUM_VERSION, '<' ) ) {
+		if ( Constants::is_defined( 'WCPAY_VERSION_NUMBER' ) &&
+			version_compare( Constants::get_constant( 'WCPAY_VERSION_NUMBER' ), self::EXTENSION_MINIMUM_VERSION, '<' ) ) {
 			throw new ApiException(
 				'woocommerce_woopayments_onboarding_extension_version',
 				/* translators: %s: WooPayments. */
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsRestController.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsRestController.php
index 8d7f654663..f919a29d4a 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsRestController.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsRestController.php
@@ -824,6 +824,20 @@ class PaymentsRestController extends RestApiControllerBase {
 							'context'    => array( 'view', 'edit' ),
 							'readonly'   => true,
 							'properties' => array(
+								'preload' => array(
+									'type'        => 'object',
+									'description' => esc_html__( 'The onboarding preload link for the payment gateway.', 'woocommerce' ),
+									'context'     => array( 'view', 'edit' ),
+									'readonly'    => true,
+									'properties'  => array(
+										'href' => array(
+											'type'        => 'string',
+											'description' => esc_html__( 'The URL to do onboarding preload for the payment gateway.', 'woocommerce' ),
+											'context'     => array( 'view', 'edit' ),
+											'readonly'    => true,
+										),
+									),
+								),
 								'onboard' => array(
 									'type'        => 'object',
 									'description' => esc_html__( 'The start/continue onboarding link for the payment gateway.', 'woocommerce' ),
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/PaymentGatewayTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/PaymentGatewayTest.php
index 324110acb3..765a6de47a 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/PaymentGatewayTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/PaymentGatewayTest.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentProviders;

 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\PaymentGateway;
+use Automattic\WooCommerce\Internal\Admin\Suggestions\PaymentExtensionSuggestions;
 use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
 use stdClass;
 use WC_Unit_Test_Case;
@@ -199,6 +200,51 @@ class PaymentGatewayTest extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * Test enhance_extension_suggestion.
+	 */
+	public function test_enhance_extension_suggestion() {
+		// Arrange.
+		$extension_suggestion = array(
+			'id'          => 'woopayments',
+			'title'       => 'WooPayments',
+			'description' => 'Accept payments with WooPayments.',
+			'icon'        => 'https://example.com/icon.png',
+			'image'       => 'https://example.com/image.png',
+			'category'    => PaymentProviders::CATEGORY_PSP,
+			'links'       => array(
+				'about' => array(
+					'_type' => 'about',
+					'url'   => 'https://example.com/about',
+				),
+			),
+			'plugin'      => array(
+				'_type'  => PaymentProviders::EXTENSION_TYPE_WPORG,
+				'slug'   => 'woocommerce-payments',
+				'file'   => 'woocommerce-payments/woocommerce-payments',
+				'status' => PaymentProviders::EXTENSION_NOT_INSTALLED,
+			),
+			'tags'        => array(
+				'made_in_woo',
+			),
+			'_priority'   => 1,
+			'_type'       => PaymentExtensionSuggestions::TYPE_PSP,
+		);
+
+		// Act.
+		$enhanced_suggestion = $this->sut->enhance_extension_suggestion( $extension_suggestion );
+
+		// Assert.
+		// The onboarding entry should be added.
+		$this->assertArrayHasKey( 'onboarding', $enhanced_suggestion );
+		$this->assertEquals(
+			array(
+				'type' => PaymentGateway::ONBOARDING_TYPE_EXTERNAL,
+			),
+			$enhanced_suggestion['onboarding']
+		);
+	}
+
 	/**
 	 * Test get_title.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WCCoreTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WCCoreTest.php
index 682cf81763..d90150394e 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WCCoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WCCoreTest.php
@@ -3,7 +3,6 @@ declare( strict_types=1 );

 namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentProviders;

-use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WCCore;
 use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
 use WC_Unit_Test_Case;
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerIntegrationTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerIntegrationTest.php
new file mode 100644
index 0000000000..9334b3de2e
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerIntegrationTest.php
@@ -0,0 +1,1220 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentProviders\WooPayments;
+
+use Automattic\Jetpack\Connection\Manager as WPCOM_Connection_Manager;
+use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WooPayments\WooPaymentsService;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WooPayments\WooPaymentsRestController;
+use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
+use Automattic\WooCommerce\Internal\Admin\Settings\Utils;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
+use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use PHPUnit\Framework\MockObject\MockObject;
+use WC_REST_Unit_Test_Case;
+use WP_REST_Request;
+
+/**
+ * WooPaymentsRestController API controller integration test.
+ *
+ * @class WooPaymentsRestController
+ */
+class WooPaymentsRestControllerIntegrationTest extends WC_REST_Unit_Test_Case {
+	/**
+	 * Endpoint.
+	 *
+	 * @var string
+	 */
+	const ENDPOINT = '/wc-admin/settings/payments/woopayments';
+
+	/**
+	 * @var WooPaymentsRestController
+	 */
+	protected WooPaymentsRestController $controller;
+
+	/**
+	 * @var PaymentProviders
+	 */
+	protected PaymentProviders $providers_service;
+
+	/**
+	 * @var WooPaymentsService
+	 */
+	protected WooPaymentsService $woopayments_provider_service;
+
+	/**
+	 * @var MockableLegacyProxy|MockObject
+	 */
+	protected $mockable_proxy;
+
+	/**
+	 * @var WPCOM_Connection_Manager|MockObject
+	 */
+	protected $mock_wpcom_connection_manager;
+
+	/**
+	 * The ID of the store admin user.
+	 *
+	 * @var int
+	 */
+	protected $store_admin_id;
+
+	/**
+	 * The current time in seconds.
+	 *
+	 * Use it instead of time() to avoid using the real time in tests.
+	 *
+	 * @var int
+	 */
+	protected int $current_time;
+
+	/**
+	 * Gateways mock.
+	 *
+	 * @var callable
+	 */
+	private $gateways_mock_ref;
+
+	/**
+	 * The initial country that is set before running tests in this test suite.
+	 *
+	 * @var string $initial_country
+	 */
+	private static string $initial_country = '';
+
+	/**
+	 * The initial currency that is set before running tests in this test suite.
+	 *
+	 * @var string $initial_currency
+	 */
+	private static string $initial_currency = '';
+
+	/**
+	 * Saves values of initial country and currency before running test suite.
+	 */
+	public static function wpSetUpBeforeClass(): void {
+		self::$initial_country  = WC()->countries->get_base_country();
+		self::$initial_currency = get_woocommerce_currency();
+	}
+
+	/**
+	 * Restores initial values of country and currency after running test suite.
+	 */
+	public static function wpTearDownAfterClass(): void {
+		update_option( 'woocommerce_default_country', self::$initial_country );
+		update_option( 'woocommerce_currency', self::$initial_currency );
+	}
+
+	/**
+	 * Set up test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		$this->store_admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
+		wp_set_current_user( $this->store_admin_id );
+
+		$this->current_time = 1234567890;
+
+		// Arrange the version constant to meet the minimum requirements for the native in-context onboarding.
+		Constants::set_constant( 'WCPAY_VERSION_NUMBER', PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION );
+
+		$this->providers_service = wc_get_container()->get( PaymentProviders::class );
+
+		$this->mock_wpcom_connection_manager = $this->getMockBuilder( WPCOM_Connection_Manager::class )
+													->onlyMethods(
+														array(
+															'is_connected',
+															'has_connected_owner',
+															'is_connection_owner',
+															'try_registration',
+														)
+													)
+													->getMock();
+
+		$this->mockable_proxy = wc_get_container()->get( LegacyProxy::class );
+		$this->mockable_proxy->register_class_mocks(
+			array(
+				WPCOM_Connection_Manager::class => $this->mock_wpcom_connection_manager,
+			)
+		);
+
+		$this->mock_account_service = $this->getMockBuilder( \stdClass::class )
+											->addMethods( array( 'is_stripe_account_valid', 'get_account_status_data' ) )
+											->getMock();
+
+		$this->mock_gateway = new FakePaymentGateway(
+			'woocommerce_payments',
+			array(
+				'enabled'                     => false,
+				'account_connected'           => false,
+				'needs_setup'                 => true,
+				'test_mode'                   => true,
+				'dev_mode'                    => true,
+				'onboarding_started'          => false,
+				'onboarding_completed'        => false,
+				'onboarding_test_mode'        => false,
+				'plugin_slug'                 => 'woocommerce-payments',
+				'plugin_file'                 => 'woocommerce-payments/woocommerce-payments.php',
+				'recommended_payment_methods' => array(
+					array(
+						'id'          => 'card',
+						'_order'      => 0,
+						'enabled'     => true,
+						'required'    => true,
+						'title'       => 'Credit/debit card (required)',
+						'description' => 'Accepts all major credit and debit cards',
+						'icon'        => 'https://example.com/card-icon.png',
+					),
+					array(
+						'id'          => 'woopay',
+						'_order'      => 1,
+						'enabled'     => false,
+						'title'       => 'WooPay',
+						'description' => 'WooPay express checkout',
+						'icon'        => 'https://example.com/woopay-icon.png',
+					),
+				),
+			)
+		);
+
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				'\WC_Payments'         => array(
+					'get_gateway'         => function () {
+						return $this->mock_gateway;
+					},
+					'get_account_service' => function () {
+						return $this->mock_account_service;
+					},
+				),
+				'\WC_Payments_Account' => array(
+					'get_connect_url'       => function () {
+						return 'https://example.com/kyc_fallback';
+					},
+					'get_overview_page_url' => function () {
+						return 'https://example.com/overview_page?from=' . WooPaymentsService::FROM_NOX_IN_CONTEXT;
+					},
+				),
+				'\WC_Payments_Utils'   => array(
+					'supported_countries' => function () {
+						return $this->get_woopayments_supported_countries();
+					},
+				),
+			)
+		);
+
+		$this->mockable_proxy->register_function_mocks(
+			array(
+				// Mock the current time.
+				'time'         => function () {
+					return $this->current_time;
+				},
+				'class_exists' => function ( $class_to_check ) {
+					// By default, the WooPayments extension is mocked as active.
+					if ( '\WC_Payments' === $class_to_check ) {
+						return true;
+					}
+
+					return false;
+				},
+			),
+		);
+
+		// Reinitialize the controller with the mocked dependencies.
+		$this->woopayments_provider_service = new WooPaymentsService();
+		$this->woopayments_provider_service->init( $this->providers_service, $this->mockable_proxy );
+		$sut = new WooPaymentsRestController();
+		$sut->init( wc_get_container()->get( Payments::class ), $this->woopayments_provider_service );
+		$sut->register_routes( true );
+		// Replace the controller in the container so that it can be used during tests.
+		wc_get_container()->replace( WooPaymentsRestController::class, $sut );
+
+		$this->gateways_mock_ref = function ( \WC_Payment_Gateways $wc_payment_gateways ) {
+			$mock_gateways = array(
+				'woocommerce_payments' => $this->mock_gateway,
+			);
+			$order         = 99999;
+			foreach ( $mock_gateways as $gateway_id => $fake_gateway ) {
+				$wc_payment_gateways->payment_gateways[ $order++ ] = $fake_gateway;
+			}
+		};
+	}
+
+	/**
+	 * Tear down.
+	 */
+	public function tearDown(): void {
+		$this->unmock_payment_gateways();
+		delete_option( 'woocommerce_gateway_order' );
+
+		delete_option( WooPaymentsService::NOX_PROFILE_OPTION_KEY );
+	}
+
+	/**
+	 * Test getting onboarding details by a user without the needed capabilities.
+	 */
+	public function test_get_onboarding_details_by_user_without_caps() {
+		// Arrange.
+		// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
+		$filter_callback = fn( $caps ) => array(
+			'manage_woocommerce' => false, // This is needed.
+			'install_plugins'    => true,  // This is not needed.
+		);
+		add_filter( 'user_has_cap', $filter_callback );
+
+		// Act.
+		$request  = new WP_REST_Request( 'GET', self::ENDPOINT . '/onboarding' );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( rest_authorization_required_code(), $response->get_status() );
+
+		// Clean up.
+		remove_filter( 'user_has_cap', $filter_callback );
+	}
+
+	/**
+	 * Test getting payment providers by a user with the needed permissions.
+	 */
+	public function test_get_onboarding_details_by_manager() {
+		// Arrange.
+		$country_code = 'US';
+		// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
+		$filter_callback = fn( $caps ) => array(
+			'manage_woocommerce' => true,  // This is needed.
+			'install_plugins'    => false, // This is not needed.
+		);
+		add_filter( 'user_has_cap', $filter_callback );
+
+		// Act.
+		$request = new WP_REST_Request( 'GET', self::ENDPOINT . '/onboarding' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+
+		// Assert all the entries are in the response.
+		$this->assertArrayHasKey( 'state', $data );
+		$this->assertArrayHasKey( 'steps', $data );
+		$this->assertArrayHasKey( 'context', $data );
+
+		// Check that the payment methods step has all the fields.
+		$step = $data['steps'][0];
+		$this->assertArrayHasKey( 'id', $step );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS, $step['id'] );
+		$this->assertArrayHasKey( 'path', $step );
+		$this->assertSame( '/woopayments/onboarding/' . WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS, $step['path'] );
+		$this->assertArrayHasKey( 'required_steps', $step );
+		$this->assertSame( array(), $step['required_steps'] );
+		$this->assertArrayHasKey( 'status', $step );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_NOT_STARTED, $step['status'] );
+		$this->assertArrayHasKey( 'errors', $step );
+		$this->assertSame( array(), $step['errors'] );
+		$this->assertArrayHasKey( 'actions', $step );
+		$this->assertArrayHasKey( 'context', $step );
+		// Check that we have all the actions.
+		$this->assertArrayHasKey( 'start', $step['actions'] );
+		$this->assertArrayHasKey( 'save', $step['actions'] );
+		$this->assertArrayHasKey( 'check', $step['actions'] );
+		$this->assertArrayHasKey( 'finish', $step['actions'] );
+		$this->assertArrayHasKey( 'clean', $step['actions'] );
+
+		// Clean up.
+		remove_filter( 'user_has_cap', $filter_callback );
+	}
+
+	/**
+	 * Test getting onboarding details without specifying a location.
+	 *
+	 * It should default to the store's base location country.
+	 */
+	public function test_get_onboarding_details_with_no_location() {
+		// Arrange.
+		$country_code = 'LI'; // Liechtenstein.
+		update_option( 'woocommerce_default_country', $country_code ); // Liechtenstein.
+
+		// Act.
+		$request  = new WP_REST_Request( 'GET', self::ENDPOINT . '/onboarding' );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+
+		// Assert all the entries are in the response.
+		$this->assertArrayHasKey( 'state', $data );
+		$this->assertArrayHasKey( 'steps', $data );
+		$this->assertArrayHasKey( 'context', $data );
+	}
+
+	/**
+	 * Test getting onboarding details with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_get_onboarding_details_with_invalid_location( string $location ) {
+		// Arrange.
+
+		// Act.
+		$request = new WP_REST_Request( 'GET', self::ENDPOINT . '/onboarding' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step start.
+	 */
+	public function test_onboarding_step_start() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/start' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		$this->assertArrayHasKey( 'previous_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_NOT_STARTED, $data['previous_status'] );
+		$this->assertArrayHasKey( 'current_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_STARTED, $data['current_status'] );
+	}
+
+	/**
+	 * Test handling onboarding step start with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_step_start_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/start' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step start with invalid step.
+	 */
+	public function test_onboarding_step_start_with_invalid_step() {
+		// Arrange.
+		$step_id      = 'invalid_step';
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/start' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step save.
+	 */
+	public function test_onboarding_step_save() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		$request_params = array(
+			'payment_methods' => array(
+				'card'   => true,
+				'woopay' => false,
+			),
+			'another_key'     => 'another_value', // This should be ignored and not saved.
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/save' );
+		$request->set_param( 'location', $country_code );
+		foreach ( $request_params as $key => $value ) {
+			$request->set_param( $key, $value );
+		}
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+
+		$this->assertSame(
+			array(
+				'onboarding' => array(
+					$country_code => array(
+						'steps' => array(
+							$step_id => array(
+								'data' => array(
+									'payment_methods' => $request_params['payment_methods'],
+								),
+							),
+						),
+					),
+				),
+			),
+			get_option( WooPaymentsService::NOX_PROFILE_OPTION_KEY )
+		);
+	}
+
+	/**
+	 * Test handling onboarding step save with invalid data.
+	 */
+	public function test_onboarding_step_save_with_invalid_data() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		$request_params = array(
+			// The `payment_methods` entry is missing.
+			'some_key'    => 'some_value',
+			'another_key' => 'another_value',
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/save' );
+		$request->set_param( 'location', $country_code );
+		foreach ( $request_params as $key => $value ) {
+			$request->set_param( $key, $value );
+		}
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+
+		$this->assertSame( false, get_option( WooPaymentsService::NOX_PROFILE_OPTION_KEY ) );
+	}
+
+	/**
+	 * Test handling onboarding step save with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_step_save_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/save' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step save with invalid step.
+	 */
+	public function test_onboarding_step_save_with_invalid_step() {
+		// Arrange.
+		$step_id      = 'invalid_step';
+		$country_code = 'US';
+
+		$request_params = array(
+			'another_key' => 'another_value',
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/save' );
+		$request->set_param( 'location', $country_code );
+		foreach ( $request_params as $key => $value ) {
+			$request->set_param( $key, $value );
+		}
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step check.
+	 */
+	public function test_onboarding_step_check() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/check' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		$this->assertArrayHasKey( 'status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_NOT_STARTED, $data['status'] );
+	}
+
+	/**
+	 * Test handling onboarding step check with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_step_check_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/check' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step check with invalid step.
+	 */
+	public function est_onboarding_step_check_with_invalid_step() {
+		// Arrange.
+		$step_id      = 'invalid_step';
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/check' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step finish.
+	 */
+	public function test_onboarding_step_finish() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/finish' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		$this->assertArrayHasKey( 'previous_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_NOT_STARTED, $data['previous_status'] );
+		$this->assertArrayHasKey( 'current_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_COMPLETED, $data['current_status'] );
+	}
+
+	/**
+	 * Test handling onboarding step finish with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_step_finish_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/finish' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step finish with invalid step.
+	 */
+	public function test_onboarding_step_finish_with_invalid_step() {
+		// Arrange.
+		$step_id      = 'invalid_step';
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/finish' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step clean.
+	 */
+	public function test_onboarding_step_clean() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+		$country_code = 'US';
+
+		// The step is started.
+		update_option(
+			WooPaymentsService::NOX_PROFILE_OPTION_KEY,
+			array(
+				'onboarding' => array(
+					$country_code => array(
+						'steps' => array(
+							$step_id => array(
+								'statuses' => array(
+									WooPaymentsService::ONBOARDING_STEP_STATUS_STARTED => 1234,
+								),
+							),
+						),
+					),
+				),
+			),
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/clean' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		$this->assertArrayHasKey( 'previous_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_STARTED, $data['previous_status'] );
+		$this->assertArrayHasKey( 'current_status', $data );
+		$this->assertSame( WooPaymentsService::ONBOARDING_STEP_STATUS_NOT_STARTED, $data['current_status'] );
+	}
+
+	/**
+	 * Test handling onboarding step clean with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_step_clean_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_PAYMENT_METHODS;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/clean' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test handling onboarding step clean with invalid step.
+	 */
+	public function test_onboarding_step_clean_with_invalid_step() {
+		// Arrange.
+		$step_id      = 'invalid_step';
+		$country_code = 'US';
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/clean' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test onboarding test account init.
+	 */
+	public function test_onboarding_test_account_init() {
+		// Arrange.
+		$step_id      = WooPaymentsService::ONBOARDING_STEP_TEST_ACCOUNT;
+		$country_code = 'US';
+		$source       = 'test_source';
+
+		// Arrange the WPCOM connection.
+		// Make it connected to pass the step requirements.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'is_connected' )
+			->willReturn( true );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'has_connected_owner' )
+			->willReturn( true );
+
+		// Intercept the request to our platform (proxied by the client).
+		$requested_urls = array();
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				Utils::class => array(
+					'rest_endpoint_post_request' => function ( $url ) use ( &$requested_urls ) {
+						$requested_urls[] = $url;
+
+						return array( 'success' => true );
+					},
+				),
+			)
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/init' );
+		$request->set_param( 'location', $country_code );
+		$request->set_param( 'source', $source );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertContains( '/wc/v3/payments/onboarding/test_drive_account/init', $requested_urls );
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+	}
+
+	/**
+	 * Test onboarding test account init with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_test_account_init_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_TEST_ACCOUNT;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/init' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test onboarding business verification step KYC session init.
+	 */
+	public function test_onboarding_business_verification_step_kyc_session_init() {
+		// Arrange.
+		$step_id         = WooPaymentsService::ONBOARDING_STEP_BUSINESS_VERIFICATION;
+		$country_code    = 'US';
+		$self_assessment = array(
+			'some_data' => 'some_value',
+		);
+		$session_data    = array(
+			'some_session_data' => 'some_session_value',
+			'locale'            => 'en_US',
+		);
+
+		// Arrange the WPCOM connection.
+		// Make it connected to pass the step requirements.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'is_connected' )
+			->willReturn( true );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'has_connected_owner' )
+			->willReturn( true );
+
+		// Intercept the request to our platform (proxied by the client).
+		$requested_urls = array();
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				Utils::class => array(
+					'rest_endpoint_post_request' => function ( $url ) use ( $session_data, &$requested_urls ) {
+						$requested_urls[] = $url;
+
+						return $session_data;
+					},
+				),
+			)
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/kyc_session' );
+		$request->set_param( 'location', $country_code );
+		$request->set_param( 'self_assessment', $self_assessment );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertContains( '/wc/v3/payments/onboarding/kyc/session', $requested_urls );
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		$this->assertArrayHasKey( 'session', $data );
+		$this->assertSame( $session_data, $data['session'] );
+	}
+
+	/**
+	 * Test onboarding business verification step KYC session init with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_business_verification_step_kyc_session_init_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_BUSINESS_VERIFICATION;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/kyc_session' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test onboarding business verification step KYC session finish.
+	 */
+	public function test_onboarding_business_verification_step_kyc_session_finish() {
+		// Arrange.
+		$step_id         = WooPaymentsService::ONBOARDING_STEP_BUSINESS_VERIFICATION;
+		$country_code    = 'US';
+		$source          = 'test_source';
+		$finish_response = array(
+			'some_data' => 'some_value',
+		);
+
+		// Arrange the WPCOM connection.
+		// Make it connected to pass the step requirements.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'is_connected' )
+			->willReturn( true );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->atLeastOnce() )
+			->method( 'has_connected_owner' )
+			->willReturn( true );
+
+		// Intercept the request to our platform (proxied by the client).
+		$requested_urls = array();
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				Utils::class => array(
+					'rest_endpoint_post_request' => function ( $url ) use ( $finish_response, &$requested_urls ) {
+						$requested_urls[] = $url;
+
+						return $finish_response;
+					},
+				),
+			)
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/kyc_session/finish' );
+		$request->set_param( 'location', $country_code );
+		$request->set_param( 'source', $source );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertContains( '/wc/v3/payments/onboarding/kyc/finalize', $requested_urls );
+		$this->assertSame( 200, $response->get_status() );
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+		foreach ( $finish_response as $key => $value ) {
+			$this->assertArrayHasKey( $key, $data );
+			$this->assertSame( $value, $data[ $key ] );
+		}
+	}
+
+	/**
+	 * Test onboarding business verification step KYC session finish with invalid location.
+	 *
+	 * @dataProvider provider_invalid_location_provider
+	 *
+	 * @param string $location The location to test.
+	 */
+	public function test_onboarding_business_verification_step_kyc_session_finish_with_invalid_location( string $location ) {
+		// Arrange.
+		$step_id = WooPaymentsService::ONBOARDING_STEP_BUSINESS_VERIFICATION;
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/step/' . $step_id . '/kyc_session/finish' );
+		$request->set_param( 'location', $location );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( \WP_Http::BAD_REQUEST, $response->get_status() );
+	}
+
+	/**
+	 * Test onboarding preload.
+	 */
+	public function test_handle_onboarding_preload() {
+		// Arrange.
+		$country_code = 'US';
+
+		// Arrange the WPCOM connection.
+		// Make it not connected.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'is_connected' )
+			->willReturn( false );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'has_connected_owner' )
+			->willReturn( false );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->once() )
+			->method( 'try_registration' )
+			->willReturn( true );
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/preload' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+	}
+
+	/**
+	 * Test onboarding reset.
+	 */
+	public function test_onboarding_reset() {
+		// Arrange.
+		$from         = 'test-from';
+		$source       = 'test-source';
+		$country_code = 'US';
+
+		// Put some data in the option to reset.
+		update_option(
+			WooPaymentsService::NOX_PROFILE_OPTION_KEY,
+			array(
+				'onboarding' => array(
+					$country_code => array(
+						'steps' => array(
+							WooPaymentsService::ONBOARDING_STEP_BUSINESS_VERIFICATION => array(
+								'statuses' => array(
+									WooPaymentsService::ONBOARDING_STEP_STATUS_STARTED => 1234,
+								),
+							),
+						),
+					),
+				),
+			),
+		);
+
+		// Intercept the request to our platform (proxied by the client).
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				Utils::class => array(
+					'rest_endpoint_post_request' => function () {
+						return array( 'success' => true );
+					},
+				),
+			)
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/reset' );
+		$request->set_param( 'from', $from );
+		$request->set_param( 'source', $source );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+
+		// Assert that the option is reset.
+		$this->assertFalse( get_option( WooPaymentsService::NOX_PROFILE_OPTION_KEY ) );
+	}
+
+	/**
+	 * Test disable test account.
+	 */
+	public function test_disable_test_account() {
+		// Arrange.
+		$location = 'US';
+		$from     = 'test-from';
+		$source   = 'test-source';
+
+		// Arrange the WPCOM connection.
+		// Make it connected to pass the step requirements.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'is_connected' )
+			->willReturn( true );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'has_connected_owner' )
+			->willReturn( true );
+
+		// Intercept the request to our platform (proxied by the client).
+		$requested_urls = array();
+		$this->mockable_proxy->register_static_mocks(
+			array(
+				Utils::class => array(
+					'rest_endpoint_post_request' => function ( $url ) use ( &$requested_urls ) {
+						$requested_urls[] = $url;
+
+						return array( 'success' => true );
+					},
+				),
+			)
+		);
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/test_account/disable' );
+		$request->set_param( 'location', $location );
+		$request->set_param( 'from', $from );
+		$request->set_param( 'source', $source );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertContains( '/wc/v3/payments/onboarding/test_drive_account/disable', $requested_urls );
+
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+
+		// Assert the test account step status.
+		$this->assertSame(
+			WooPaymentsService::ONBOARDING_STEP_STATUS_COMPLETED,
+			$this->woopayments_provider_service->get_onboarding_step_status( WooPaymentsService::ONBOARDING_STEP_TEST_ACCOUNT, $location )
+		);
+	}
+
+	/**
+	 * Provider for invalid location test cases.
+	 *
+	 * @return array[]
+	 */
+	public function provider_invalid_location_provider(): array {
+		return array(
+			'empty'       => array( '' ),
+			'single_char' => array( 'U' ),
+			'number'      => array( '12' ),
+			'long_string' => array( 'USA' ),
+		);
+	}
+
+	/**
+	 * Mock the WC payment gateways.
+	 */
+	protected function mock_payment_gateways() {
+		// Hook into the payment gateways initialization to mock the gateways.
+		add_action( 'wc_payment_gateways_initialized', $this->gateways_mock_ref, 100 );
+		// Reinitialize the WC gateways.
+		WC()->payment_gateways()->payment_gateways = array();
+		WC()->payment_gateways()->init();
+
+		$this->providers_service->reset_memo();
+	}
+
+	/**
+	 * Unmock the WC payment gateways.
+	 */
+	private function unmock_payment_gateways() {
+		remove_all_actions( 'wc_payment_gateways_initialized' );
+		// Reinitialize the WC gateways.
+		WC()->payment_gateways()->payment_gateways = array();
+		WC()->payment_gateways()->init();
+
+		$this->providers_service->reset_memo();
+	}
+
+	/**
+	 * Get the mock WooPayments supported countries.
+	 *
+	 * @return string[]
+	 */
+	private function get_woopayments_supported_countries(): array {
+		return array(
+			'AE' => 'United Arab Emirates',
+			'AT' => 'Austria',
+			'AU' => 'Australia',
+			'BE' => 'Belgium',
+			'BG' => 'Bulgaria',
+			'CA' => 'Canada',
+			'CH' => 'Switzerland',
+			'CY' => 'Cyprus',
+			'CZ' => 'Czech Republic',
+			'DE' => 'Germany',
+			'DK' => 'Denmark',
+			'EE' => 'Estonia',
+			'FI' => 'Finland',
+			'ES' => 'Spain',
+			'FR' => 'France',
+			'HR' => 'Croatia',
+			'JP' => 'Japan',
+			'LU' => 'Luxembourg',
+			'GB' => 'United Kingdom (UK)',
+			'GR' => 'Greece',
+			'HK' => 'Hong Kong',
+			'HU' => 'Hungary',
+			'IE' => 'Ireland',
+			'IT' => 'Italy',
+			'LT' => 'Lithuania',
+			'LV' => 'Latvia',
+			'MT' => 'Malta',
+			'NL' => 'Netherlands',
+			'NO' => 'Norway',
+			'NZ' => 'New Zealand',
+			'PL' => 'Poland',
+			'PT' => 'Portugal',
+			'RO' => 'Romania',
+			'SE' => 'Sweden',
+			'SI' => 'Slovenia',
+			'SK' => 'Slovakia',
+			'SG' => 'Singapore',
+			'US' => 'United States (US)',
+			'PR' => 'Puerto Rico',
+		);
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerTest.php
index 691097916f..c5567f9f38 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsRestControllerTest.php
@@ -135,6 +135,7 @@ class WooPaymentsRestControllerTest extends WC_REST_Unit_Test_Case {
 		$this->assertArrayHasKey( 'kyc_session', $step['actions'] );
 		$this->assertArrayHasKey( 'kyc_session_finish', $step['actions'] );
 		$this->assertArrayHasKey( 'kyc_fallback', $step['actions'] );
+		$this->assertArrayHasKey( 'clean', $step['actions'] );

 		// Clean up.
 		remove_filter( 'user_has_cap', $filter_callback );
@@ -949,6 +950,58 @@ class WooPaymentsRestControllerTest extends WC_REST_Unit_Test_Case {
 		$this->assertSame( $expected_http_code, $response->get_status() );
 	}

+	/**
+	 * Test onboarding preload.
+	 */
+	public function test_handle_onboarding_preload() {
+		// Arrange.
+		$country_code = 'US';
+
+		$this->mock_woopayments_service
+			->expects( $this->once() )
+			->method( 'onboarding_preload' )
+			->with( $country_code )
+			->willReturn( array( 'success' => true ) );
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/preload' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( 200, $response->get_status() );
+
+		$data = $response->get_data();
+		$this->assertArrayHasKey( 'success', $data );
+		$this->assertTrue( $data['success'] );
+	}
+
+	/**
+	 * Test onboarding preload with exception.
+	 */
+	public function test_handle_onboarding_preload_with_exception() {
+		// Arrange.
+		$country_code = 'US';
+
+		$expected_code      = 'test_exception';
+		$expected_message   = 'Test exception message.';
+		$expected_http_code = 123;
+		$this->mock_woopayments_service
+			->expects( $this->once() )
+			->method( 'onboarding_preload' )
+			->willThrowException( new ApiException( $expected_code, $expected_message, $expected_http_code ) );
+
+		// Act.
+		$request = new WP_REST_Request( 'POST', self::ENDPOINT . '/onboarding/preload' );
+		$request->set_param( 'location', $country_code );
+		$response = $this->server->dispatch( $request );
+
+		// Assert.
+		$this->assertSame( $expected_code, $response->get_data()['code'] );
+		$this->assertSame( $expected_message, $response->get_data()['message'] );
+		$this->assertSame( $expected_http_code, $response->get_status() );
+	}
+
 	/**
 	 * Test onboarding reset.
 	 */
@@ -1137,6 +1190,10 @@ class WooPaymentsRestControllerTest extends WC_REST_Unit_Test_Case {
 							'type' => WooPaymentsService::ACTION_TYPE_REDIRECT,
 							'href' => 'https://example.com/kyc_fallback',
 						),
+						'clean'              => array(
+							'type' => WooPaymentsService::ACTION_TYPE_REST,
+							'href' => rest_url( self::ENDPOINT . '/step1/clean' ),
+						),
 					),
 					'context'        => array(),
 				),
@@ -1169,6 +1226,10 @@ class WooPaymentsRestControllerTest extends WC_REST_Unit_Test_Case {
 						// No kyc_session step for this step.
 						// No kyc_session_finish step for this step.
 						// No kyc_fallback step for this step.
+						'clean'  => array(
+							'type' => WooPaymentsService::ACTION_TYPE_REST,
+							'href' => rest_url( self::ENDPOINT . '/step2/clean' ),
+						),
 					),
 					'context'        => array(),
 				),
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsServiceTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsServiceTest.php
index b76777b7fb..b1c640aab8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsServiceTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPayments/WooPaymentsServiceTest.php
@@ -4,6 +4,7 @@ declare( strict_types=1 );
 namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentProviders\WooPayments;

 use Automattic\Jetpack\Connection\Manager as WPCOM_Connection_Manager;
+use Automattic\Jetpack\Constants;
 use Automattic\WooCommerce\Internal\Admin\Settings\Exceptions\ApiException;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\PaymentGateway;
@@ -63,7 +64,7 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 	/**
 	 * The current time in seconds.
 	 *
-	 * Use it instead of $this->current_time to avoid using the real time in tests.
+	 * Use it instead of time() to avoid using the real time in tests.
 	 *
 	 * @var int
 	 */
@@ -105,6 +106,7 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 					'is_connected',
 					'has_connected_owner',
 					'is_connection_owner',
+					'try_registration',
 				)
 			)
 			->getMock();
@@ -118,7 +120,7 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 		);
 		$this->mockable_proxy->register_function_mocks(
 			array(
-				// Mock the $this->current_time.
+				// Mock the current time.
 				'time'         => function () {
 					return $this->current_time;
 				},
@@ -137,6 +139,9 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 			),
 		);

+		// Arrange the version constant to meet the minimum requirements for the native in-context onboarding.
+		Constants::set_constant( 'WCPAY_VERSION_NUMBER', PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION );
+
 		$this->mock_account_service = $this->getMockBuilder( \stdClass::class )
 			->addMethods( array( 'is_stripe_account_valid', 'get_account_status_data' ) )
 			->getMock();
@@ -201,6 +206,26 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 		}
 	}

+	/**
+	 * Test get onboarding details when the extension is NOT at the right version.
+	 */
+	public function test_get_onboarding_details_throws_when_extension_at_wrong_version(): void {
+		// Arrange.
+		$location = 'US';
+
+		// Arrange the version constant to NOT meet the minimum requirements for the native in-context onboarding.
+		Constants::set_constant( 'WCPAY_VERSION_NUMBER', '1.0.0' );
+
+		// Act.
+		try {
+			$this->sut->get_onboarding_details( $location, '/some/path' );
+
+			$this->fail( 'Expected ApiException not thrown.' );
+		} catch ( ApiException $e ) {
+			$this->assertSame( 'woocommerce_woopayments_onboarding_extension_version', $e->getErrorCode() );
+		}
+	}
+
 	/**
 	 * Test get onboarding details when the extension is active but the onboarding is locked.
 	 *
@@ -7308,6 +7333,143 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * Test onboarding_preload throws exception when onboarding is locked.
+	 *
+	 * @return void
+	 * @throws \Exception When trying to mock uncallable user functions.
+	 */
+	public function test_onboarding_preload_throws_with_onboarding_locked() {
+		// Arrange.
+		$location = 'US';
+
+		// Arrange the WPCOM connection.
+		// Make it not connected.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'is_connected' )
+			->willReturn( false );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'has_connected_owner' )
+			->willReturn( false );
+
+		// Arrange the onboarding locked DB option.
+		$this->mockable_proxy->register_function_mocks(
+			array(
+				'get_option' => function ( $option_name, $default_value = null ) {
+					if ( WooPaymentsService::NOX_ONBOARDING_LOCKED_KEY === $option_name ) {
+						return 'yes';
+					}
+
+					return $default_value;
+				},
+			)
+		);
+
+		// Act.
+		try {
+			$this->sut->onboarding_preload( $location );
+
+			$this->fail( 'Expected ApiException not thrown.' );
+		} catch ( ApiException $e ) {
+			$this->assertEquals( 'woocommerce_woopayments_onboarding_locked', $e->getErrorCode() );
+		}
+	}
+
+	/**
+	 * Test that onboarding_preload throws an exception when the WPCOM connection registration fails.
+	 *
+	 * @return void
+	 * @throws \Exception On POST request not mocked.
+	 */
+	public function test_onboarding_preload_throws_on_error_response() {
+		// Arrange.
+		$location = 'US';
+
+		// Arrange the WPCOM connection.
+		$expected_error = array(
+			'code'    => 'error',
+			'message' => 'Error message',
+		);
+		// Make it not connected.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'is_connected' )
+			->willReturn( false );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->once() )
+			->method( 'try_registration' )
+			->willReturn( new WP_Error( $expected_error['code'], $expected_error['message'] ) );
+
+		// Arrange the onboarding locked DB option.
+		$this->mockable_proxy->register_function_mocks(
+			array(
+				'get_option' => function ( $option_name, $default_value = null ) {
+					if ( WooPaymentsService::NOX_ONBOARDING_LOCKED_KEY === $option_name ) {
+						return 'no';
+					}
+
+					return $default_value;
+				},
+			)
+		);
+
+		// Assert.
+		$this->expectException( \Exception::class );
+		$this->expectExceptionMessage( $expected_error['message'] );
+
+		// Act.
+		$this->sut->onboarding_preload( $location );
+	}
+
+	/**
+	 * Test onboarding_preload.
+	 *
+	 * @return void
+	 * @throws \Exception When trying to mock uncallable user functions.
+	 */
+	public function test_onboarding_preload() {
+		// Arrange.
+		$location                           = 'US';
+		$expected_wpcom_registration_result = false;
+
+		// Arrange the WPCOM connection.
+		// Make it not connected.
+		$this->mock_wpcom_connection_manager
+			->expects( $this->any() )
+			->method( 'is_connected' )
+			->willReturn( false );
+		$this->mock_wpcom_connection_manager
+			->expects( $this->once() )
+			->method( 'try_registration' )
+			->willReturn( $expected_wpcom_registration_result );
+
+		// Arrange the onboarding locked DB option.
+		$this->mockable_proxy->register_function_mocks(
+			array(
+				'get_option' => function ( $option_name, $default_value = null ) {
+					if ( WooPaymentsService::NOX_ONBOARDING_LOCKED_KEY === $option_name ) {
+						return 'no';
+					}
+
+					return $default_value;
+				},
+			)
+		);
+
+		// Act.
+		$result = $this->sut->onboarding_preload( $location );
+
+		// Assert.
+		self::assertEquals(
+			array(
+				'success' => $expected_wpcom_registration_result,
+			),
+			$result
+		);
+	}
+
 	/**
 	 * Test reset_onboarding throws exception when extension is not active.
 	 *
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPaymentsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPaymentsTest.php
new file mode 100644
index 0000000000..230554e812
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentProviders/WooPaymentsTest.php
@@ -0,0 +1,207 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentProviders;
+
+use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\PaymentGateway;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentProviders\WooPayments;
+use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
+use Automattic\WooCommerce\Internal\Admin\Settings\Utils;
+use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use WC_Unit_Test_Case;
+
+/**
+ * WooPayments payment gateway provider service test.
+ *
+ * @class WCCore
+ */
+class WooPaymentsTest extends WC_Unit_Test_Case {
+
+	/**
+	 * @var WooPayments
+	 */
+	protected $sut;
+
+	/**
+	 * Set up test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		$this->sut = new WooPayments();
+	}
+
+	/**
+	 * Test get_details.
+	 */
+	public function test_get_details() {
+		// Arrange.
+		$fake_gateway = new FakePaymentGateway(
+			'woocommerce_payments',
+			array(
+				'enabled'                     => true,
+				'account_connected'           => true,
+				'needs_setup'                 => true,
+				'test_mode'                   => true,
+				'dev_mode'                    => true,
+				'onboarding_started'          => true,
+				'onboarding_completed'        => true,
+				'test_mode_onboarding'        => true,
+				'plugin_slug'                 => 'woocommerce-payments',
+				'plugin_file'                 => 'woocommerce-payments/woocommerce-payments.php',
+				'method_title'                => 'WooPayments has a very long title that should be truncated after some length like this',
+				'method_description'          => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+				'supports'                    => array( 'products', 'something', 'bogus' ),
+				'icon'                        => 'https://example.com/icon.png',
+				'recommended_payment_methods' => array(
+					// Basic PM.
+					array(
+						'id'       => 'basic',
+						// No order, should be last.
+						'enabled'  => true,
+						'title'    => 'Title',
+						'category' => PaymentGateway::PAYMENT_METHOD_CATEGORY_SECONDARY,
+					),
+					// Basic PM with priority instead of order.
+					array(
+						'id'       => 'basic2',
+						'priority' => 30,
+						'enabled'  => false,
+						'title'    => 'Title',
+						'category' => 'unknown', // This should be ignored and replaced with the default category (primary).
+					),
+					array(
+						'id'          => 'card',
+						'order'       => 20,
+						'enabled'     => true,
+						'required'    => true,
+						'title'       => '<b>Credit/debit card (required)</b>', // All tags should be stripped.
+						// Paragraphs and line breaks should be stripped.
+						'description' => '<p><strong>Accepts</strong> <b>all major</b></br><em>credit</em> and <a href="#" target="_blank">debit cards</a>.</p>',
+						'icon'        => 'https://example.com/card-icon.png',
+						// No category means it should be primary (default category).
+					),
+					array(
+						'id'          => 'woopay',
+						'order'       => 10,
+						'enabled'     => false,
+						'title'       => 'WooPay',
+						'description' => 'WooPay express checkout',
+						// Not a good URL.
+						'icon'        => 'not_good_url/icon.svg',
+						'category'    => PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY,
+					),
+					// Invalid PM, should be ignored. No data.
+					array(),
+					// Invalid PM, should be ignored. No ID.
+					array( 'title' => 'Card' ),
+					// Invalid PM, should be ignored. No title.
+					array( 'id' => 'card' ),
+				),
+			),
+		);
+
+		// Arrange the version constant to meet the minimum requirements for the native in-context onboarding.
+		Constants::set_constant( 'WCPAY_VERSION_NUMBER', PaymentProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION );
+
+		// Act.
+		$gateway_details = $this->sut->get_details( $fake_gateway, 999 );
+
+		// Assert that we have all the details.
+		$this->assertEquals(
+			array(
+				'id'          => 'woocommerce_payments',
+				'_order'      => 999,
+				'title'       => 'WooPayments has a very long title that should be truncated after some length',
+				'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim…',
+				'icon'        => 'https://example.com/icon.png',
+				'supports'    => array( 'products', 'something', 'bogus' ),
+				'state'       => array(
+					'enabled'           => true,
+					'account_connected' => true,
+					'needs_setup'       => true,
+					'test_mode'         => true,
+					'dev_mode'          => true,
+				),
+				'management'  => array(
+					'_links' => array(
+						'settings' => array(
+							'href' => 'https://example.com/wp-admin/admin.php?page=wc-settings&tab=checkout&section=bogus_settings',
+						),
+					),
+				),
+				'plugin'      => array(
+					'_type'  => PaymentProviders::EXTENSION_TYPE_WPORG,
+					'slug'   => 'woocommerce-payments',
+					'file'   => 'woocommerce-payments/woocommerce-payments',
+					'status' => PaymentProviders::EXTENSION_ACTIVE,
+				),
+				'onboarding'  => array(
+					'type'                        => PaymentGateway::ONBOARDING_TYPE_NATIVE_IN_CONTEXT,
+					'state'                       => array(
+						'started'                      => true,
+						'completed'                    => true,
+						'test_mode'                    => true,
+						'wpcom_has_working_connection' => false,
+						'wpcom_is_store_connected'     => false,
+						'wpcom_has_connected_owner'    => false,
+						'wpcom_is_connection_owner'    => false,
+					),
+					'_links'                      => array(
+						'onboard' => array(
+							'href' => Utils::wc_payments_settings_url( '/woopayments/onboarding', array( 'from' => Payments::FROM_PAYMENTS_SETTINGS ) ),
+						),
+					),
+					'recommended_payment_methods' => array(
+						array(
+							'id'          => 'woopay',
+							'_order'      => 0,
+							'enabled'     => false,
+							'required'    => false,
+							'title'       => 'WooPay',
+							'description' => 'WooPay express checkout',
+							'icon'        => '', // The icon with an invalid URL is ignored.
+							'category'    => PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY,
+						),
+						array(
+							'id'          => 'card',
+							'_order'      => 1,
+							'enabled'     => true,
+							'required'    => true,
+							'title'       => 'Credit/debit card (required)',
+							'description' => '<strong>Accepts</strong> <b>all major</b><em>credit</em> and <a href="#" target="_blank">debit cards</a>.',
+							'icon'        => 'https://example.com/card-icon.png',
+							'category'    => PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY,
+						),
+						array(
+							'id'          => 'basic2',
+							'_order'      => 2,
+							'enabled'     => false,
+							'required'    => false,
+							'title'       => 'Title',
+							'description' => '',
+							'icon'        => '',
+							'category'    => PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY,
+						),
+						array(
+							'id'          => 'basic',
+							'_order'      => 3,
+							'enabled'     => true,
+							'required'    => false,
+							'title'       => 'Title',
+							'description' => '',
+							'icon'        => '',
+							'category'    => PaymentGateway::PAYMENT_METHOD_CATEGORY_SECONDARY,
+						),
+					),
+				),
+			),
+			$gateway_details
+		);
+
+		// Clean up.
+		Constants::clear_constants();
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsRestControllerIntegrationTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsRestControllerIntegrationTest.php
index 1feee0091e..973238f037 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsRestControllerIntegrationTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsRestControllerIntegrationTest.php
@@ -8,7 +8,6 @@ use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsRestController;
 use Automattic\WooCommerce\Internal\Admin\Suggestions\Incentives\Incentive;
 use Automattic\WooCommerce\Internal\Admin\Suggestions\PaymentExtensionSuggestions;
-use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
 use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
 use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
 use WC_REST_Unit_Test_Case;