Commit b9ab88e333 for woocommerce
commit b9ab88e333c0c91970432204f3dc3867ed7059fd
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Tue Nov 4 13:53:17 2025 +0200
[Payments NOX] Fix payment providers tests using fill in classes (#61789)
* Add guards against invalid payment providers
* test: Add tests for getting payments providers instances
* refac: No need for call_user_func_array
* Inject the LegacyProxy into providers for proper test mocking
* Use proxy calls in the WooPayments provider for easy mocking in tests
* Remove WooPayments onboarding router experiment
* test: Improve and expand WooPayments provider unit tests
* test: Fix WCCore providers unit tests
* test: Fix generic PaymentGateway provider unit tests
* test: Improve generic PaymentGateway unit test coverage
* Add changelog
* Use unresolved string constants
* test: Remove duplicate assertions
* docs: Fix types
* Better guard against failure to get WPCOM Connection Manager instance
* No need to bail on missing query params
* Guard against magic __call methods
* test: Update proxy mocks to include method_exists
* Do not use resolved class or function names
* test: Do not use resolved class or function names
* Lint fixes
* test: Have mocks fallback on PHP native functions when not mocked
diff --git a/plugins/woocommerce/changelog/fix-providers-tests-using-fill-in-classes b/plugins/woocommerce/changelog/fix-providers-tests-using-fill-in-classes
new file mode 100644
index 0000000000..c2e7f54f4a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-providers-tests-using-fill-in-classes
@@ -0,0 +1,5 @@
+Significance: patch
+Type: update
+Comment: Update payments providers classes to use the LegacyProxy when making externals calls.
+
+
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
index 976ab401d3..47ad39c9b1 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
@@ -366,7 +366,7 @@ class PaymentsProviders {
/**
* The provider class for the gateway.
*
- * @var PaymentGateway|null $provider_class
+ * @var class-string<PaymentGateway>|null $provider_class
*/
$provider_class = null;
if ( isset( $this->payment_gateways_providers_class_map[ $gateway_id ] ) ) {
@@ -386,16 +386,31 @@ class PaymentsProviders {
}
}
+ // Check that the provider class extends the PaymentGateway class.
+ if ( ! is_null( $provider_class ) && ! is_subclass_of( $provider_class, PaymentGateway::class ) ) {
+ wc_doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: %s: Gateway ID. */
+ esc_html__( 'The provider class for gateway ID "%s" must extend the PaymentGateway class.', 'woocommerce' ),
+ $gateway_id
+ ),
+ '10.4.0'
+ );
+ // Return the generic provider as a fallback.
+ $provider_class = null;
+ }
+
// 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();
+ $this->instances['generic'] = new PaymentGateway( $this->proxy );
}
return $this->instances['generic'];
}
- $this->instances[ $gateway_id ] = new $provider_class();
+ $this->instances[ $gateway_id ] = new $provider_class( $this->proxy );
return $this->instances[ $gateway_id ];
}
@@ -416,23 +431,36 @@ class PaymentsProviders {
/**
* The provider class for the payment extension suggestion (PES).
*
- * @var PaymentGateway|null $provider_class
+ * @var class-string<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 ( ! is_subclass_of( $this->payment_extension_suggestions_providers_class_map[ $pes_id ], PaymentGateway::class ) ) {
+ wc_doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: %s: Payment extension suggestion ID. */
+ esc_html__( 'The provider class for payment extension suggestion ID "%s" must extend the PaymentGateway class.', 'woocommerce' ),
+ $pes_id
+ ),
+ '10.4.0'
+ );
+ // Return the generic provider as a fallback.
+ } else {
+ $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();
+ $this->instances['generic'] = new PaymentGateway( $this->proxy );
}
return $this->instances['generic'];
}
- $this->instances[ $pes_id ] = new $provider_class();
+ $this->instances[ $pes_id ] = new $provider_class( $this->proxy );
return $this->instances[ $pes_id ];
}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/PaymentGateway.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/PaymentGateway.php
index 868590d4fb..b5f20aa38e 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/PaymentGateway.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/PaymentGateway.php
@@ -8,6 +8,7 @@ use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders;
use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
use Automattic\WooCommerce\Internal\Admin\Settings\Utils;
use Automattic\WooCommerce\Internal\Logging\SafeGlobalFunctionProxy;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
use Throwable;
use WC_HTTPS;
use WC_Payment_Gateway;
@@ -36,6 +37,22 @@ class PaymentGateway {
const PAYMENT_METHOD_CATEGORY_PRIMARY = 'primary';
const PAYMENT_METHOD_CATEGORY_SECONDARY = 'secondary';
+ /**
+ * The LegacyProxy instance.
+ *
+ * @var LegacyProxy
+ */
+ protected LegacyProxy $proxy;
+
+ /**
+ * Constructor.
+ *
+ * @param LegacyProxy $proxy The LegacyProxy instance.
+ */
+ public function __construct( LegacyProxy $proxy ) {
+ $this->proxy = $proxy;
+ }
+
/**
* Extract the payment gateway provider details from the object.
*
@@ -602,10 +619,7 @@ class PaymentGateway {
if ( method_exists( $payment_gateway, 'get_onboarding_not_supported_message' ) &&
is_callable( array( $payment_gateway, 'get_onboarding_not_supported_message' ) ) ) {
- $message = call_user_func_array(
- array( $payment_gateway, 'get_onboarding_not_supported_message' ),
- array( $country_code ),
- );
+ $message = call_user_func( array( $payment_gateway, 'get_onboarding_not_supported_message' ), $country_code, );
if ( is_string( $message ) && ! empty( $message ) ) {
return sanitize_textarea_field( trim( $message ) );
}
@@ -964,10 +978,7 @@ class PaymentGateway {
try {
// Get the "raw" recommended payment methods from the payment gateway.
- $recommended_pms = call_user_func_array(
- array( $payment_gateway, 'get_recommended_payment_methods' ),
- array( $country_code ),
- );
+ $recommended_pms = call_user_func( array( $payment_gateway, 'get_recommended_payment_methods' ), $country_code );
if ( ! is_array( $recommended_pms ) ) {
// Bail if the recommended payment methods are not an array.
return array();
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/WooPayments.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/WooPayments.php
index 8c301ab31d..c5a2620fc1 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/WooPayments.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/WooPayments.php
@@ -18,7 +18,6 @@ use Automattic\WooCommerce\Internal\Logging\SafeGlobalFunctionProxy;
use Throwable;
use WC_Abstract_Order;
use WC_Payment_Gateway;
-use WooCommerce\Admin\Experimental_Abtest;
defined( 'ABSPATH' ) || exit;
@@ -189,7 +188,7 @@ class WooPayments extends PaymentGateway {
// 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' );
+ $plugin_data = $this->proxy->call_static( PluginsHelper::class, 'get_plugin_data', $extension_suggestion['plugin']['file'] . '.php' );
if ( $plugin_data && ! empty( $plugin_data['Version'] ) &&
version_compare( $plugin_data['Version'], PaymentsProviders\WooPayments\WooPaymentsService::EXTENSION_MINIMUM_VERSION, '>=' ) ) {
@@ -246,24 +245,6 @@ class WooPayments extends PaymentGateway {
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.
*
@@ -296,11 +277,13 @@ class WooPayments extends PaymentGateway {
* @return bool True if the payment gateway is in test mode, false otherwise.
*/
public function is_in_test_mode( WC_Payment_Gateway $payment_gateway ): bool {
- if ( class_exists( '\WC_Payments' ) &&
- is_callable( '\WC_Payments::mode' ) ) {
+ if ( $this->proxy->call_function( 'class_exists', 'WC_Payments' ) &&
+ $this->proxy->call_function( 'is_callable', 'WC_Payments::mode' ) ) {
+
+ $woopayments_mode = $this->proxy->call_static( 'WC_Payments', 'mode' );
+ if ( $this->proxy->call_function( 'method_exists', $woopayments_mode, 'is_test' ) &&
+ $this->proxy->call_function( 'is_callable', array( $woopayments_mode, 'is_test' ) ) ) {
- $woopayments_mode = \WC_Payments::mode();
- if ( is_callable( array( $woopayments_mode, 'is_test' ) ) ) {
return $woopayments_mode->is_test();
}
}
@@ -319,11 +302,13 @@ class WooPayments extends PaymentGateway {
* @return bool True if the payment gateway is in dev mode, false otherwise.
*/
public function is_in_dev_mode( WC_Payment_Gateway $payment_gateway ): bool {
- if ( class_exists( '\WC_Payments' ) &&
- is_callable( '\WC_Payments::mode' ) ) {
+ if ( $this->proxy->call_function( 'class_exists', 'WC_Payments' ) &&
+ $this->proxy->call_function( 'is_callable', 'WC_Payments::mode' ) ) {
+
+ $woopayments_mode = $this->proxy->call_static( 'WC_Payments', 'mode' );
+ if ( $this->proxy->call_function( 'method_exists', $woopayments_mode, 'is_dev' ) &&
+ $this->proxy->call_function( 'is_callable', array( $woopayments_mode, 'is_dev' ) ) ) {
- $woopayments_mode = \WC_Payments::mode();
- if ( is_callable( array( $woopayments_mode, 'is_dev' ) ) ) {
return $woopayments_mode->is_dev();
}
}
@@ -405,11 +390,13 @@ class WooPayments extends PaymentGateway {
* @return bool True if the payment gateway is in test mode onboarding, false otherwise.
*/
public function is_in_test_mode_onboarding( WC_Payment_Gateway $payment_gateway ): bool {
- if ( class_exists( '\WC_Payments' ) &&
- is_callable( '\WC_Payments::mode' ) ) {
+ if ( $this->proxy->call_function( 'class_exists', 'WC_Payments' ) &&
+ $this->proxy->call_function( 'is_callable', 'WC_Payments::mode' ) ) {
+
+ $woopayments_mode = $this->proxy->call_static( 'WC_Payments', 'mode' );
+ if ( $this->proxy->call_function( 'method_exists', $woopayments_mode, 'is_test_mode_onboarding' ) &&
+ $this->proxy->call_function( 'is_callable', array( $woopayments_mode, 'is_test_mode_onboarding' ) ) ) {
- $woopayments_mode = \WC_Payments::mode();
- if ( is_callable( array( $woopayments_mode, 'is_test_mode_onboarding' ) ) ) {
return $woopayments_mode->is_test_mode_onboarding();
}
}
@@ -429,22 +416,18 @@ class WooPayments extends PaymentGateway {
* @return string The onboarding URL for the payment gateway.
*/
public function get_onboarding_url( WC_Payment_Gateway $payment_gateway, string $return_url = '' ): string {
- if ( class_exists( '\WC_Payments_Account' ) && is_callable( '\WC_Payments_Account::get_connect_url' ) ) {
- $connect_url = \WC_Payments_Account::get_connect_url();
+ if ( $this->proxy->call_function( 'class_exists', 'WC_Payments_Account' ) &&
+ $this->proxy->call_function( 'is_callable', 'WC_Payments_Account::get_connect_url' ) ) {
+
+ $connect_url = $this->proxy->call_static( 'WC_Payments_Account', 'get_connect_url' );
} else {
$connect_url = parent::get_onboarding_url( $payment_gateway, $return_url );
}
- $query = wp_parse_url( $connect_url, PHP_URL_QUERY );
- // We expect the URL to have a query string. Bail if it doesn't.
- if ( empty( $query ) ) {
- return $connect_url;
- }
-
// Default URL params to set, regardless if they exist.
$params = array(
- 'from' => defined( '\WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS' ) ? \WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS : 'WCADMIN_PAYMENT_SETTINGS',
- 'source' => defined( '\WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE' ) ? \WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE : 'wcadmin-settings-page',
+ 'from' => Constants::is_defined( 'WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS' ) ? (string) Constants::get_constant( 'WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS' ) : 'WCADMIN_PAYMENT_SETTINGS',
+ 'source' => Constants::is_defined( 'WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE' ) ? (string) Constants::get_constant( 'WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE' ) : 'wcadmin-settings-page',
'redirect_to_settings_page' => 'true',
);
@@ -495,30 +478,6 @@ class WooPayments extends PaymentGateway {
$live_onboarding = true;
}
- // We run an experiment to determine the efficiency of test-account-first onboarding vs straight-to-live onboarding.
- // If the experiment is active and the store is in the treatment group, we will force live onboarding.
- // Otherwise, we will do test-account-first onboarding (control group).
- // Stores that are determined by our routing logic that they should do straight-to-live onboarding
- // will not be affected by the experiment.
- if ( ! $live_onboarding ) {
- $transient_key = 'wc_experiment_failure_woocommerce_payment_settings_onboarding_2025_v1';
-
- // Try to get cached result first.
- $cached_result = get_transient( $transient_key );
-
- // If we have a cache entry that indicates an error, don't enforce anything. Just let the routing logic decide.
- if ( 'error' !== $cached_result ) {
- try {
- if ( Experimental_Abtest::in_treatment( 'woocommerce_payment_settings_onboarding_2025_v1' ) ) {
- $live_onboarding = true;
- }
- } catch ( \Exception $e ) {
- // If the experiment fails, set a transient to avoid repeated failures.
- set_transient( $transient_key, 'error', HOUR_IN_SECONDS );
- }
- }
- }
-
// If we are doing live onboarding, we don't need to add more to the URL.
// But for test-drive/sandbox mode, we have work to do.
if ( ! $live_onboarding ) {
@@ -617,9 +576,15 @@ class WooPayments extends PaymentGateway {
* @return bool True if the account is a test account, false otherwise.
*/
private function has_test_account(): bool {
- if ( function_exists( '\wcpay_get_container' ) && class_exists( '\WC_Payments_Account' ) ) {
- $account_service = \wcpay_get_container()->get( \WC_Payments_Account::class );
- if ( ! empty( $account_service ) && is_callable( array( $account_service, 'get_account_status_data' ) ) ) {
+ if ( $this->proxy->call_function( 'function_exists', 'wcpay_get_container' ) &&
+ $this->proxy->call_function( 'class_exists', 'WC_Payments_Account' ) ) {
+
+ $woopayments_container = $this->proxy->call_function( 'wcpay_get_container' );
+ $account_service = $woopayments_container->get( 'WC_Payments_Account' );
+ if ( ! empty( $account_service ) &&
+ $this->proxy->call_function( 'method_exists', $account_service, 'get_account_status_data' ) &&
+ $this->proxy->call_function( 'is_callable', array( $account_service, 'get_account_status_data' ) ) ) {
+
$account_status = $account_service->get_account_status_data();
return ! empty( $account_status['testDrive'] );
@@ -640,9 +605,15 @@ class WooPayments extends PaymentGateway {
* @return bool True if the account is a sandbox account, false otherwise.
*/
private function has_sandbox_account(): bool {
- if ( function_exists( '\wcpay_get_container' ) && class_exists( '\WC_Payments_Account' ) ) {
- $account_service = \wcpay_get_container()->get( \WC_Payments_Account::class );
- if ( ! empty( $account_service ) && is_callable( array( $account_service, 'get_account_status_data' ) ) ) {
+ if ( $this->proxy->call_function( 'function_exists', 'wcpay_get_container' ) &&
+ $this->proxy->call_function( 'class_exists', 'WC_Payments_Account' ) ) {
+
+ $woopayments_container = $this->proxy->call_function( 'wcpay_get_container' );
+ $account_service = $woopayments_container->get( 'WC_Payments_Account' );
+ if ( ! empty( $account_service ) &&
+ $this->proxy->call_function( 'method_exists', $account_service, 'get_account_status_data' ) &&
+ $this->proxy->call_function( 'is_callable', array( $account_service, 'get_account_status_data' ) ) ) {
+
$account_status = $account_service->get_account_status_data();
return empty( $account_status['isLive'] ) && empty( $account_status['testDrive'] );
@@ -661,10 +632,10 @@ class WooPayments extends PaymentGateway {
*/
private function get_supported_country_codes(): ?array {
try {
- if ( class_exists( '\WC_Payments_Utils' ) &&
- is_callable( '\WC_Payments_Utils::supported_countries' ) ) {
+ if ( $this->proxy->call_function( 'class_exists', 'WC_Payments_Utils' ) &&
+ $this->proxy->call_function( 'is_callable', 'WC_Payments_Utils::supported_countries' ) ) {
- $supported_country_codes = \WC_Payments_Utils::supported_countries();
+ $supported_country_codes = $this->proxy->call_static( 'WC_Payments_Utils', 'supported_countries' );
if ( is_array( $supported_country_codes ) ) {
return array_unique( array_map( 'strtoupper', array_keys( $supported_country_codes ) ) );
}
@@ -682,4 +653,41 @@ class WooPayments extends PaymentGateway {
return null;
}
+
+ /**
+ * 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 {
+ try {
+ $wpcom_connection_manager = $this->proxy->get_instance_of( WPCOM_Connection_Manager::class, 'woocommerce' );
+ } catch ( \Throwable $e ) {
+ // Log so we can investigate.
+ SafeGlobalFunctionProxy::wc_get_logger()->error(
+ 'Failed to get the WPCOM/Jetpack Connection Manager instance: ' . $e->getMessage(),
+ array(
+ 'source' => 'settings-payments',
+ )
+ );
+
+ // Assume no connection.
+ return array(
+ 'wpcom_has_working_connection' => false,
+ 'wpcom_is_store_connected' => false,
+ 'wpcom_has_connected_owner' => false,
+ 'wpcom_is_connection_owner' => false,
+ );
+ }
+
+ $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(),
+ );
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/WCPaymentsUtils.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/WCPaymentsUtils.php
deleted file mode 100644
index 917d6d0e1c..0000000000
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/WCPaymentsUtils.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-/**
- * Mock WC_Payments_Utils class for testing.
- *
- * This mock is used in tests when the real WC_Payments_Utils class is not available.
- *
- * @package WooCommerce\Tests\Internal\Admin\Settings\Mocks
- */
-
-declare( strict_types=1 );
-
-if ( ! class_exists( 'WC_Payments_Utils' ) ) {
- /**
- * Mock WC_Payments_Utils class.
- *
- * phpcs:disable Squiz.Classes.ClassFileName.NoMatch
- * phpcs:disable Suin.Classes.PSR4.IncorrectClassName
- * phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
- */
- class WC_Payments_Utils {
- /**
- * Get the list of supported countries for WooPayments.
- *
- * @return array Array of country codes and names.
- */
- public static function supported_countries() {
- // This is just a subset of countries that WooPayments supports.
- // But it should cover our testing needs.
- return array(
- 'us' => 'United States',
- 'gb' => 'United Kingdom',
- 'de' => 'Germany',
- );
- }
- }
-}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/PaymentGatewayTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/PaymentGatewayTest.php
index 663fcfa15c..e48392c686 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/PaymentGatewayTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/PaymentGatewayTest.php
@@ -7,7 +7,11 @@ use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\PaymentGateway;
use Automattic\WooCommerce\Internal\Admin\Suggestions\PaymentsExtensionSuggestions;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\TestingContainer;
use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use PHPUnit\Framework\MockObject\MockObject;
use stdClass;
use WC_Unit_Test_Case;
@@ -18,6 +22,11 @@ use WC_Unit_Test_Case;
*/
class PaymentGatewayTest extends WC_Unit_Test_Case {
+ /**
+ * @var MockableLegacyProxy|MockObject
+ */
+ protected $mockable_proxy;
+
/**
* @var PaymentGateway
*/
@@ -36,7 +45,16 @@ class PaymentGatewayTest extends WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
- $this->sut = new PaymentGateway();
+ /**
+ * TestingContainer instance.
+ *
+ * @var TestingContainer $container
+ */
+ $container = wc_get_container();
+
+ $this->mockable_proxy = $container->get( LegacyProxy::class );
+
+ $this->sut = new PaymentGateway( $this->mockable_proxy );
}
/**
@@ -524,6 +542,66 @@ class PaymentGatewayTest extends WC_Unit_Test_Case {
$this->assertFalse( $this->sut->needs_setup( $fake_gateway ) );
}
+ /**
+ * Test needs_setup fallback logic when method returns false but account is not connected.
+ */
+ public function test_needs_setup_fallback_when_method_returns_false_and_not_connected() {
+ // Arrange - Create a mock gateway with needs_setup method returning false.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'needs_setup' ) )
+ ->addMethods( array( 'is_account_connected' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ // Expect needs_setup method to return false.
+ $gateway->expects( $this->once() )
+ ->method( 'needs_setup' )
+ ->willReturn( false );
+
+ // Expect is_account_connected to be called and return false.
+ $gateway->expects( $this->once() )
+ ->method( 'is_account_connected' )
+ ->willReturn( false );
+
+ // Act.
+ $result = $this->sut->needs_setup( $gateway );
+
+ // Assert - Should return true because account is not connected.
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * Test needs_setup fallback logic when method returns false and account is connected.
+ */
+ public function test_needs_setup_fallback_when_method_returns_false_and_is_connected() {
+ // Arrange - Create a mock gateway with needs_setup method returning false.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'needs_setup' ) )
+ ->addMethods( array( 'is_account_connected' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ // Expect needs_setup method to return false.
+ $gateway->expects( $this->once() )
+ ->method( 'needs_setup' )
+ ->willReturn( false );
+
+ // Expect is_account_connected to be called and return true.
+ $gateway->expects( $this->once() )
+ ->method( 'is_account_connected' )
+ ->willReturn( true );
+
+ // Act.
+ $result = $this->sut->needs_setup( $gateway );
+
+ // Assert - Should return false because account is connected.
+ $this->assertFalse( $result );
+ }
+
/**
* Test is_in_test_mode.
*/
@@ -539,6 +617,129 @@ class PaymentGatewayTest extends WC_Unit_Test_Case {
$this->assertFalse( $this->sut->is_in_test_mode( $fake_gateway ) );
}
+ /**
+ * Test is_in_test_mode with testmode property.
+ * Use a mock gateway without is_test_mode() method to test property fallback.
+ */
+ public function test_is_in_test_mode_with_testmode_property() {
+ // Arrange - Create a mock gateway without is_test_mode() or is_in_test_mode() methods.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ // Test with testmode property set to true.
+ $gateway->testmode = true;
+ $this->assertTrue( $this->sut->is_in_test_mode( $gateway ) );
+
+ // Test with testmode property set to false.
+ $gateway->testmode = false;
+ $this->assertFalse( $this->sut->is_in_test_mode( $gateway ) );
+
+ // Test with string values.
+ $gateway->testmode = 'yes';
+ $this->assertTrue( $this->sut->is_in_test_mode( $gateway ) );
+
+ $gateway->testmode = 'no';
+ $this->assertFalse( $this->sut->is_in_test_mode( $gateway ) );
+ }
+
+ /**
+ * Test is_in_test_mode with mode option fallback.
+ * Use a mock gateway without methods or properties to test get_option fallback.
+ */
+ public function test_is_in_test_mode_with_mode_option() {
+ // Test mode option with 'test' value.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'get_option' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ // Expect get_option to be called for 'test_mode', 'testmode', and 'mode'.
+ $gateway->expects( $this->exactly( 3 ) )
+ ->method( 'get_option' )
+ ->willReturnCallback(
+ function ( $key, $default_value ) {
+ unset( $default_value ); // Avoid parameter not used PHPCS errors.
+ if ( 'mode' === $key ) {
+ return 'test';
+ }
+ return 'not_found';
+ }
+ );
+
+ $this->assertTrue( $this->sut->is_in_test_mode( $gateway ) );
+
+ // Test with 'sandbox' value.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'get_option' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ $gateway->expects( $this->exactly( 3 ) )
+ ->method( 'get_option' )
+ ->willReturnCallback(
+ function ( $key, $default_value ) {
+ unset( $default_value ); // Avoid parameter not used PHPCS errors.
+ if ( 'mode' === $key ) {
+ return 'sandbox';
+ }
+ return 'not_found';
+ }
+ );
+
+ $this->assertTrue( $this->sut->is_in_test_mode( $gateway ) );
+
+ // Test with 'live' value.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'get_option' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ $gateway->expects( $this->exactly( 3 ) )
+ ->method( 'get_option' )
+ ->willReturnCallback(
+ function ( $key, $default_value ) {
+ unset( $default_value ); // Avoid parameter not used PHPCS errors.
+ if ( 'mode' === $key ) {
+ return 'live';
+ }
+ return 'not_found';
+ }
+ );
+
+ $this->assertFalse( $this->sut->is_in_test_mode( $gateway ) );
+
+ // Test with 'production' value.
+ $gateway = $this->getMockBuilder( 'WC_Payment_Gateway' )
+ ->disableOriginalConstructor()
+ ->onlyMethods( array( 'get_option' ) )
+ ->getMock();
+
+ $gateway->id = 'test_gateway';
+
+ $gateway->expects( $this->exactly( 3 ) )
+ ->method( 'get_option' )
+ ->willReturnCallback(
+ function ( $key, $default_value ) {
+ unset( $default_value ); // Avoid parameter not used PHPCS errors.
+ if ( 'mode' === $key ) {
+ return 'production';
+ }
+ return 'not_found';
+ }
+ );
+
+ $this->assertFalse( $this->sut->is_in_test_mode( $gateway ) );
+ }
+
/**
* Test is_in_dev_mode.
*/
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WCCoreTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WCCoreTest.php
index b3b52527be..1cc5c914a6 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WCCoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WCCoreTest.php
@@ -4,7 +4,11 @@ declare( strict_types=1 );
namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentsProviders;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WCCore;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\TestingContainer;
use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use PHPUnit\Framework\MockObject\MockObject;
use WC_Unit_Test_Case;
use WC_Gateway_BACS;
use WC_Gateway_Cheque;
@@ -18,6 +22,11 @@ use WC_Gateway_Paypal;
*/
class WCCoreTest extends WC_Unit_Test_Case {
+ /**
+ * @var MockableLegacyProxy|MockObject
+ */
+ protected $mockable_proxy;
+
/**
* @var WCCore
*/
@@ -29,7 +38,16 @@ class WCCoreTest extends WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
- $this->sut = new WCCore();
+ /**
+ * TestingContainer instance.
+ *
+ * @var TestingContainer $container
+ */
+ $container = wc_get_container();
+
+ $this->mockable_proxy = $container->get( LegacyProxy::class );
+
+ $this->sut = new WCCore( $this->mockable_proxy );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPaymentsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPaymentsTest.php
index c1546cae48..e661d032cf 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPaymentsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPaymentsTest.php
@@ -3,16 +3,19 @@ declare( strict_types=1 );
namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentsProviders;
+use Automattic\Jetpack\Connection\Manager as WPCOM_Connection_Manager;
use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Admin\PluginsHelper;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\PaymentGateway;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WooPayments;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WooPayments\WooPaymentsRestController;
use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WooPayments\WooPaymentsService;
-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\Testing\Tools\TestingContainer;
use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use PHPUnit\Framework\MockObject\MockObject;
use WC_Unit_Test_Case;
/**
@@ -23,14 +26,39 @@ use WC_Unit_Test_Case;
class WooPaymentsTest extends WC_Unit_Test_Case {
/**
- * @var WooPaymentsRestController
+ * @var WooPaymentsRestController|MockObject
*/
protected $mock_rest_controller;
+ /**
+ * @var MockableLegacyProxy|MockObject
+ */
+ protected $mockable_proxy;
+
+ /**
+ * @var WPCOM_Connection_Manager|MockObject
+ */
+ protected $mock_wpcom_connection_manager;
+
+ /**
+ * @var object&MockObject
+ */
+ protected $mock_woopayments_container;
+
+ /**
+ * @var object&MockObject
+ */
+ protected $mock_woopayments_account_service;
+
+ /**
+ * @var object&MockObject
+ */
+ protected $mock_woopayments_mode;
+
/**
* @var WooPayments
*/
- protected $sut;
+ protected WooPayments $sut;
/**
* Set up test.
@@ -38,7 +66,33 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
- $this->sut = new WooPayments();
+ $this->setup_woopayments_container_mock();
+ $this->setup_woopayments_mode_mock();
+ $this->setup_wpcom_connection_mock();
+ $this->setup_woopayments_account_service_mock();
+ // Mock the WC WooPayments REST controller.
+ $this->setup_woopayments_reset_controller_mock();
+ // Finally, set up the mockable proxy.
+ $this->setup_legacy_proxy_mocks();
+
+ $this->sut = new WooPayments( $this->mockable_proxy );
+ }
+
+ /**
+ * Tear down.
+ */
+ public function tearDown(): void {
+ $this->mockable_proxy->reset();
+
+ /**
+ * TestingContainer instance.
+ *
+ * @var TestingContainer $container
+ */
+ $container = wc_get_container();
+ $container->reset_all_resolved();
+
+ parent::tearDown();
}
/**
@@ -111,135 +165,126 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
),
);
+ // Arrange the WooPayments mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( true );
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( true );
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( true );
+
+ // Arrange the WPCOM connection as fully working.
+ $this->mock_wpcom_connection();
+
// Arrange the version constant to meet the minimum requirements for the native in-context onboarding.
Constants::set_constant( 'WCPAY_VERSION_NUMBER', WooPaymentsService::EXTENSION_MINIMUM_VERSION );
- // Mock the WooPaymentsRestController to provide REST URL paths.
- $mock_rest_controller = $this->createMock( WooPaymentsRestController::class );
- $mock_rest_controller
- ->method( 'get_rest_url_path' )
- ->willReturnCallback(
- function ( $relative_path = '' ) {
- $path = '/some/rest/for/woopayments';
- if ( ! empty( $relative_path ) ) {
- $path .= '/' . ltrim( $relative_path, '/' );
- }
- return $path;
- }
- );
-
/**
* TestingContainer instance.
*
* @var TestingContainer $container
*/
$container = wc_get_container();
- $container->replace( WooPaymentsRestController::class, $mock_rest_controller );
+ $container->replace( WooPaymentsRestController::class, $this->mock_rest_controller );
try {
// 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' ),
- 'links' => array(),
- '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§ion=bogus_settings&from=' . Payments::FROM_PAYMENTS_SETTINGS,
- ),
- ),
- ),
- 'plugin' => array(
- '_type' => PaymentsProviders::EXTENSION_TYPE_WPORG,
- 'slug' => 'woocommerce-payments',
- 'file' => 'woocommerce-payments/woocommerce-payments',
- 'status' => PaymentsProviders::EXTENSION_ACTIVE,
- ),
- 'onboarding' => array(
- 'type' => PaymentGateway::ONBOARDING_TYPE_NATIVE_IN_CONTEXT,
- 'state' => array(
- 'supported' => true,
- 'started' => true,
- 'completed' => true,
- 'test_mode' => true,
- 'test_drive_account' => false,
- 'wpcom_has_working_connection' => false,
- 'wpcom_is_store_connected' => false,
- 'wpcom_has_connected_owner' => false,
- 'wpcom_is_connection_owner' => false,
- ),
- 'messages' => array(
- 'not_supported' => null,
- ),
- '_links' => array(
- 'onboard' => array(
- 'href' => Utils::wc_payments_settings_url( '/woopayments/onboarding', array( 'from' => Payments::FROM_PAYMENTS_SETTINGS ) ),
- ),
- 'reset' => array(
- 'href' => rest_url( '/some/rest/for/woopayments/onboarding/reset' ),
- ),
- ),
- '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
- );
+ // Assert - Use targeted assertions for resilient testing.
+ // Basic gateway details.
+ $this->assertSame( 'woocommerce_payments', $gateway_details['id'] );
+ $this->assertSame( 999, $gateway_details['_order'] );
+ $this->assertSame( 'WooPayments has a very long title that should be truncated after some length', $gateway_details['title'] );
+ $this->assertStringStartsWith( 'Lorem ipsum dolor sit amet', $gateway_details['description'] );
+ $this->assertSame( 'https://example.com/icon.png', $gateway_details['icon'] );
+ $this->assertSame( array( 'products', 'something', 'bogus' ), $gateway_details['supports'] );
+ $this->assertSame( array(), $gateway_details['links'] );
+
+ // State.
+ $this->assertArrayHasKey( 'state', $gateway_details );
+ $this->assertTrue( $gateway_details['state']['enabled'] );
+ $this->assertTrue( $gateway_details['state']['account_connected'] );
+ $this->assertTrue( $gateway_details['state']['needs_setup'] );
+ $this->assertTrue( $gateway_details['state']['test_mode'] );
+ $this->assertTrue( $gateway_details['state']['dev_mode'] );
+
+ // Management.
+ $this->assertArrayHasKey( 'management', $gateway_details );
+ $this->assertArrayHasKey( '_links', $gateway_details['management'] );
+ $this->assertArrayHasKey( 'settings', $gateway_details['management']['_links'] );
+ $this->assertStringContainsString( 'admin.php?page=wc-settings', $gateway_details['management']['_links']['settings']['href'] );
+
+ // Plugin.
+ $this->assertArrayHasKey( 'plugin', $gateway_details );
+ $this->assertSame( PaymentsProviders::EXTENSION_TYPE_WPORG, $gateway_details['plugin']['_type'] );
+ $this->assertSame( 'woocommerce-payments', $gateway_details['plugin']['slug'] );
+ $this->assertSame( 'woocommerce-payments/woocommerce-payments', $gateway_details['plugin']['file'] );
+ $this->assertSame( PaymentsProviders::EXTENSION_ACTIVE, $gateway_details['plugin']['status'] );
+
+ // Onboarding - Type.
+ $this->assertArrayHasKey( 'onboarding', $gateway_details );
+ $this->assertSame( PaymentGateway::ONBOARDING_TYPE_NATIVE_IN_CONTEXT, $gateway_details['onboarding']['type'] );
+
+ // Onboarding - State.
+ $this->assertArrayHasKey( 'state', $gateway_details['onboarding'] );
+ $this->assertTrue( $gateway_details['onboarding']['state']['supported'] );
+ $this->assertTrue( $gateway_details['onboarding']['state']['started'] );
+ $this->assertTrue( $gateway_details['onboarding']['state']['completed'] );
+ $this->assertTrue( $gateway_details['onboarding']['state']['test_mode'] );
+ $this->assertFalse( $gateway_details['onboarding']['state']['test_drive_account'] );
+ $this->assertTrue( $gateway_details['onboarding']['state']['wpcom_has_working_connection'] );
+
+ // Onboarding - Messages.
+ $this->assertArrayHasKey( 'messages', $gateway_details['onboarding'] );
+ $this->assertArrayHasKey( 'not_supported', $gateway_details['onboarding']['messages'] );
+ $this->assertNull( $gateway_details['onboarding']['messages']['not_supported'] );
+
+ // Onboarding - Links.
+ $this->assertArrayHasKey( '_links', $gateway_details['onboarding'] );
+ $this->assertArrayHasKey( 'onboard', $gateway_details['onboarding']['_links'] );
+ $this->assertStringContainsString( '/woopayments/onboarding', $gateway_details['onboarding']['_links']['onboard']['href'] );
+ $this->assertArrayHasKey( 'reset', $gateway_details['onboarding']['_links'] );
+ $this->assertStringContainsString( '/onboarding/reset', $gateway_details['onboarding']['_links']['reset']['href'] );
+
+ // Onboarding - Recommended payment methods.
+ $this->assertArrayHasKey( 'recommended_payment_methods', $gateway_details['onboarding'] );
+ $recommended_pms = $gateway_details['onboarding']['recommended_payment_methods'];
+ $this->assertCount( 4, $recommended_pms );
+
+ // Check first payment method (woopay) - ordered first, disabled, invalid icon removed.
+ $this->assertSame( 'woopay', $recommended_pms[0]['id'] );
+ $this->assertSame( 0, $recommended_pms[0]['_order'] );
+ $this->assertFalse( $recommended_pms[0]['enabled'] );
+ $this->assertFalse( $recommended_pms[0]['required'] );
+ $this->assertSame( 'WooPay', $recommended_pms[0]['title'] );
+ $this->assertSame( '', $recommended_pms[0]['icon'] ); // Invalid URL removed.
+ $this->assertSame( PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY, $recommended_pms[0]['category'] );
+
+ // Check second payment method (card) - required, HTML tags stripped but content preserved.
+ $this->assertSame( 'card', $recommended_pms[1]['id'] );
+ $this->assertSame( 1, $recommended_pms[1]['_order'] );
+ $this->assertTrue( $recommended_pms[1]['enabled'] );
+ $this->assertTrue( $recommended_pms[1]['required'] );
+ $this->assertSame( 'Credit/debit card (required)', $recommended_pms[1]['title'] );
+ $this->assertStringContainsString( 'Accepts', $recommended_pms[1]['description'] );
+ $this->assertStringContainsString( 'debit cards', $recommended_pms[1]['description'] );
+ $this->assertSame( 'https://example.com/card-icon.png', $recommended_pms[1]['icon'] );
+
+ // Check third payment method (basic2) - uses priority, category normalized.
+ $this->assertSame( 'basic2', $recommended_pms[2]['id'] );
+ $this->assertSame( 2, $recommended_pms[2]['_order'] );
+ $this->assertFalse( $recommended_pms[2]['enabled'] );
+ $this->assertSame( PaymentGateway::PAYMENT_METHOD_CATEGORY_PRIMARY, $recommended_pms[2]['category'] ); // 'unknown' normalized to primary.
+
+ // Check fourth payment method (basic) - no order, placed last, secondary category.
+ $this->assertSame( 'basic', $recommended_pms[3]['id'] );
+ $this->assertSame( 3, $recommended_pms[3]['_order'] );
+ $this->assertTrue( $recommended_pms[3]['enabled'] );
+ $this->assertSame( PaymentGateway::PAYMENT_METHOD_CATEGORY_SECONDARY, $recommended_pms[3]['category'] );
} finally {
// Clean up.
Constants::clear_constants();
@@ -265,23 +310,23 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
),
);
+ // Arrange the WooPayments mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( true );
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( true );
+
+ // Arrange the WPCOM connection as fully working.
+ $this->mock_wpcom_connection();
+
// Arrange the version constant to meet the minimum requirements.
Constants::set_constant( 'WCPAY_VERSION_NUMBER', WooPaymentsService::EXTENSION_MINIMUM_VERSION );
- // Mock the WooPaymentsRestController to provide REST URL paths.
- $mock_rest_controller = $this->createMock( WooPaymentsRestController::class );
- $mock_rest_controller
- ->method( 'get_rest_url_path' )
- ->willReturnCallback(
- function ( $relative_path = '' ) {
- $path = '/some/rest/for/woopayments';
- if ( ! empty( $relative_path ) ) {
- $path .= '/' . ltrim( $relative_path, '/' );
- }
- return $path;
- }
- );
-
// Mock the WooPaymentsService to return onboarding details.
$mock_service = $this->createMock( WooPaymentsService::class );
$mock_service
@@ -317,7 +362,7 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
* @var TestingContainer $container
*/
$container = wc_get_container();
- $container->replace( WooPaymentsRestController::class, $mock_rest_controller );
+ $container->replace( WooPaymentsRestController::class, $this->mock_rest_controller );
$container->replace( WooPaymentsService::class, $mock_service );
try {
@@ -363,23 +408,23 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
),
);
+ // Arrange the WooPayments mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( false );
+
+ // Arrange the WPCOM connection as fully working.
+ $this->mock_wpcom_connection();
+
// Arrange the version constant.
Constants::set_constant( 'WCPAY_VERSION_NUMBER', WooPaymentsService::EXTENSION_MINIMUM_VERSION );
- // Mock the WooPaymentsRestController to provide REST URL paths.
- $mock_rest_controller = $this->createMock( WooPaymentsRestController::class );
- $mock_rest_controller
- ->method( 'get_rest_url_path' )
- ->willReturnCallback(
- function ( $relative_path = '' ) {
- $path = '/some/rest/for/woopayments';
- if ( ! empty( $relative_path ) ) {
- $path .= '/' . ltrim( $relative_path, '/' );
- }
- return $path;
- }
- );
-
// Mock the service to throw an exception.
$mock_service = $this->createMock( WooPaymentsService::class );
$mock_service
@@ -392,7 +437,7 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
* @var TestingContainer $container
*/
$container = wc_get_container();
- $container->replace( WooPaymentsRestController::class, $mock_rest_controller );
+ $container->replace( WooPaymentsRestController::class, $this->mock_rest_controller );
$container->replace( WooPaymentsService::class, $mock_service );
try {
@@ -418,11 +463,6 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
// Arrange.
$fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
- // Load the mock WC_Payments_Utils if the real class doesn't exist.
- if ( ! class_exists( '\WC_Payments_Utils' ) ) {
- require_once __DIR__ . '/../Mocks/WCPaymentsUtils.php';
- }
-
// Act.
$is_supported = $this->sut->is_onboarding_supported( $fake_gateway, 'US' );
@@ -446,11 +486,6 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
)
);
- // Load the mock WC_Payments_Utils if the real class doesn't exist.
- if ( ! class_exists( '\WC_Payments_Utils' ) ) {
- require_once __DIR__ . '/../Mocks/WCPaymentsUtils.php';
- }
-
// Act - testing with a country definitely not in the supported list.
$is_supported = $this->sut->is_onboarding_supported( $fake_gateway, 'XX' );
@@ -541,6 +576,17 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
),
);
+ // Arrange the WooPayments mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( false );
+
// Arrange the version constant.
Constants::set_constant( 'WCPAY_VERSION_NUMBER', WooPaymentsService::EXTENSION_MINIMUM_VERSION );
@@ -569,9 +615,7 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
'woocommerce_payments',
array( 'onboarding_supported' => null )
);
- if ( ! class_exists( '\WC_Payments_Utils' ) ) {
- require_once __DIR__ . '/../Mocks/WCPaymentsUtils.php';
- }
+
$this->assertTrue( $this->sut->is_onboarding_supported( $fake_gateway, 'GB' ) );
}
@@ -592,6 +636,17 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
)
);
+ // Arrange the WooPayments mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( false );
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( false );
+
// Arrange the version constant.
Constants::set_constant( 'WCPAY_VERSION_NUMBER', WooPaymentsService::EXTENSION_MINIMUM_VERSION );
@@ -613,4 +668,881 @@ class WooPaymentsTest extends WC_Unit_Test_Case {
Constants::clear_constants();
}
}
+
+ /**
+ * Test enhance_extension_suggestion with compatible installed version.
+ */
+ public function test_enhance_extension_suggestion_with_compatible_installed_version() {
+ // Arrange - Mock PluginsHelper to return compatible version (>= 9.3.0).
+ $this->mockable_proxy->register_static_mocks(
+ array(
+ PluginsHelper::class => array(
+ 'get_plugin_data' => function ( $plugin_file ) {
+ if ( 'woocommerce-payments/woocommerce-payments.php' === $plugin_file ) {
+ return array(
+ 'Version' => '10.0.0', // Compatible version >= 9.3.0.
+ );
+ }
+ return false;
+ },
+ ),
+ )
+ );
+
+ // Arrange - Extension suggestion for installed WooPayments with compatible version.
+ $extension_suggestion = array(
+ 'id' => 'woocommerce-payments',
+ 'plugin' => array(
+ 'file' => 'woocommerce-payments/woocommerce-payments',
+ 'status' => PaymentsProviders::EXTENSION_ACTIVE,
+ ),
+ 'onboarding' => array(),
+ );
+
+ // Arrange the WPCOM connection as fully working.
+ $this->mock_wpcom_connection();
+
+ // Act.
+ $enhanced = $this->sut->enhance_extension_suggestion( $extension_suggestion );
+
+ // Assert - Should set native in-context onboarding type.
+ $this->assertArrayHasKey( 'onboarding', $enhanced );
+ $this->assertArrayHasKey( 'type', $enhanced['onboarding'] );
+ $this->assertSame( PaymentGateway::ONBOARDING_TYPE_NATIVE_IN_CONTEXT, $enhanced['onboarding']['type'] );
+
+ // Assert - Should include WPCOM connection state.
+ $this->assertArrayHasKey( 'state', $enhanced['onboarding'] );
+ $this->assertArrayHasKey( 'wpcom_has_working_connection', $enhanced['onboarding']['state'] );
+ $this->assertArrayHasKey( 'wpcom_is_store_connected', $enhanced['onboarding']['state'] );
+
+ // Assert - Should not include preload link when WPCOM is connected.
+ $this->assertArrayNotHasKey( 'preload', $enhanced['onboarding']['_links'] ?? array() );
+ }
+
+ /**
+ * Test enhance_extension_suggestion with incompatible installed version.
+ */
+ public function test_enhance_extension_suggestion_with_incompatible_installed_version() {
+ // Arrange - Mock PluginsHelper to return old version.
+ $this->mockable_proxy->register_static_mocks(
+ array(
+ PluginsHelper::class => array(
+ 'get_plugin_data' => function ( $plugin_file ) {
+ if ( 'woocommerce-payments/woocommerce-payments.php' === $plugin_file ) {
+ return array(
+ 'Version' => '3.0.0', // Below minimum.
+ );
+ }
+ return false;
+ },
+ ),
+ )
+ );
+
+ $extension_suggestion = array(
+ 'id' => 'woocommerce-payments',
+ 'plugin' => array(
+ 'file' => 'woocommerce-payments/woocommerce-payments',
+ 'status' => PaymentsProviders::EXTENSION_ACTIVE,
+ ),
+ 'onboarding' => array(),
+ );
+
+ // Act.
+ $enhanced = $this->sut->enhance_extension_suggestion( $extension_suggestion );
+
+ // Assert - Should fall back to external onboarding type (from parent).
+ $this->assertArrayHasKey( 'onboarding', $enhanced );
+ $this->assertArrayHasKey( 'type', $enhanced['onboarding'] );
+ $this->assertSame( PaymentGateway::ONBOARDING_TYPE_EXTERNAL, $enhanced['onboarding']['type'] );
+ }
+
+ /**
+ * Test enhance_extension_suggestion when extension not installed.
+ */
+ public function test_enhance_extension_suggestion_when_not_installed() {
+ // Arrange - Extension suggestion for not-yet-installed WooPayments.
+ $extension_suggestion = array(
+ 'id' => 'woocommerce-payments',
+ 'plugin' => array(
+ 'file' => '',
+ 'status' => PaymentsProviders::EXTENSION_NOT_INSTALLED,
+ ),
+ 'onboarding' => array(),
+ );
+
+ // Act.
+ $enhanced = $this->sut->enhance_extension_suggestion( $extension_suggestion );
+
+ // Assert - Should assume latest version and set native in-context.
+ $this->assertArrayHasKey( 'onboarding', $enhanced );
+ $this->assertArrayHasKey( 'type', $enhanced['onboarding'] );
+ $this->assertSame( PaymentGateway::ONBOARDING_TYPE_NATIVE_IN_CONTEXT, $enhanced['onboarding']['type'] );
+ }
+
+ /**
+ * Test enhance_extension_suggestion includes preload link without WPCOM connection.
+ */
+ public function test_enhance_extension_suggestion_includes_preload_link_without_wpcom() {
+ // Arrange - Mock WPCOM connection as not working.
+ $this->mock_wpcom_connection_manager
+ ->method( 'is_connected' )
+ ->willReturn( false );
+ $this->mock_wpcom_connection_manager
+ ->method( 'has_connected_owner' )
+ ->willReturn( false );
+
+ $extension_suggestion = array(
+ 'id' => 'woocommerce-payments',
+ 'plugin' => array(
+ 'file' => '',
+ 'status' => PaymentsProviders::EXTENSION_NOT_INSTALLED,
+ ),
+ 'onboarding' => array(),
+ );
+
+ // Arrange the WPCOM connection as not working.
+ $this->mock_wpcom_connection( false, false, false );
+
+ /**
+ * TestingContainer instance.
+ *
+ * @var TestingContainer $container
+ */
+ $container = wc_get_container();
+ $container->replace( WooPaymentsRestController::class, $this->mock_rest_controller );
+
+ try {
+ // Act.
+ $enhanced = $this->sut->enhance_extension_suggestion( $extension_suggestion );
+
+ // Assert - Should include preload link when WPCOM not connected.
+ $this->assertArrayHasKey( 'onboarding', $enhanced );
+ $this->assertArrayHasKey( '_links', $enhanced['onboarding'] );
+ $this->assertArrayHasKey( 'preload', $enhanced['onboarding']['_links'] );
+ $this->assertStringContainsString( '/onboarding/preload', $enhanced['onboarding']['_links']['preload']['href'] );
+
+ // Assert - WPCOM connection state should show not connected.
+ $this->assertArrayHasKey( 'state', $enhanced['onboarding'] );
+ $this->assertFalse( $enhanced['onboarding']['state']['wpcom_has_working_connection'] );
+ } finally {
+ $container->reset_replacement( WooPaymentsRestController::class );
+ }
+ }
+
+ /**
+ * Test needs_setup returns true when account is not connected.
+ */
+ public function test_needs_setup_when_account_not_connected() {
+ // Arrange - Gateway without account connection.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => false,
+ )
+ );
+
+ // Act.
+ $needs_setup = $this->sut->needs_setup( $fake_gateway );
+
+ // Assert - Should need setup when no account connected.
+ $this->assertTrue( $needs_setup );
+ }
+
+ /**
+ * Test needs_setup returns false when test-drive account exists.
+ */
+ public function test_needs_setup_with_test_drive_account() {
+ // Arrange - Gateway with connected account.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => true,
+ )
+ );
+
+ // Arrange - Mock test-drive account status.
+ $this->mock_woopayments_account_service
+ ->method( 'get_account_status_data' )
+ ->willReturn(
+ array(
+ 'testDrive' => true,
+ 'isLive' => false,
+ )
+ );
+
+ // Act.
+ $needs_setup = $this->sut->needs_setup( $fake_gateway );
+
+ // Assert - Test-drive accounts don't need setup.
+ $this->assertFalse( $needs_setup );
+ }
+
+ /**
+ * Test needs_setup delegates to parent when account connected and not test-drive.
+ */
+ public function test_needs_setup_delegates_to_parent_when_normal_account() {
+ // Arrange - Gateway with connected account that needs setup (per parent logic).
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => true,
+ 'needs_setup' => true,
+ )
+ );
+
+ // Arrange - Mock normal account (not test-drive).
+ $this->mock_woopayments_account_service
+ ->method( 'get_account_status_data' )
+ ->willReturn(
+ array(
+ 'testDrive' => false,
+ 'isLive' => true,
+ )
+ );
+
+ // Act.
+ $needs_setup = $this->sut->needs_setup( $fake_gateway );
+
+ // Assert - Should delegate to parent and return its value.
+ $this->assertTrue( $needs_setup );
+ }
+
+ /**
+ * Test needs_setup delegates to parent and returns false when setup complete.
+ */
+ public function test_needs_setup_returns_false_when_setup_complete() {
+ // Arrange - Gateway fully configured.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => true,
+ 'needs_setup' => false,
+ )
+ );
+
+ // Arrange - Mock normal account.
+ $this->mock_woopayments_account_service
+ ->method( 'get_account_status_data' )
+ ->willReturn(
+ array(
+ 'testDrive' => false,
+ 'isLive' => true,
+ )
+ );
+
+ // Act.
+ $needs_setup = $this->sut->needs_setup( $fake_gateway );
+
+ // Assert - Should return false when setup complete.
+ $this->assertFalse( $needs_setup );
+ }
+
+ /**
+ * Test is_in_test_mode returns true when WC_Payments mode reports test mode.
+ */
+ public function test_is_in_test_mode_when_woopayments_reports_test() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return test mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( true );
+
+ // Act.
+ $is_test_mode = $this->sut->is_in_test_mode( $fake_gateway );
+
+ // Assert.
+ $this->assertTrue( $is_test_mode );
+ }
+
+ /**
+ * Test is_in_test_mode returns false when WC_Payments mode reports live mode.
+ */
+ public function test_is_in_test_mode_when_woopayments_reports_live() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return live mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_test' )
+ ->willReturn( false );
+
+ // Act.
+ $is_test_mode = $this->sut->is_in_test_mode( $fake_gateway );
+
+ // Assert.
+ $this->assertFalse( $is_test_mode );
+ }
+
+ /**
+ * Test is_in_test_mode delegates to parent when WC_Payments unavailable.
+ */
+ public function test_is_in_test_mode_delegates_to_parent_when_woopayments_unavailable() {
+ // Arrange - Gateway with test mode flag.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'test_mode' => true,
+ )
+ );
+
+ // Arrange - Mock WC_Payments as not available.
+ $this->mockable_proxy->register_function_mocks(
+ array(
+ 'class_exists' => function ( $class_name ) {
+ if ( 'WC_Payments' === $class_name ) {
+ return false;
+ }
+ return false;
+ },
+ )
+ );
+
+ // Act.
+ $is_test_mode = $this->sut->is_in_test_mode( $fake_gateway );
+
+ // Assert - Should delegate to parent when WC_Payments unavailable.
+ $this->assertTrue( $is_test_mode );
+ }
+
+ /**
+ * Test is_in_dev_mode returns true when WC_Payments mode reports dev mode.
+ */
+ public function test_is_in_dev_mode_when_woopayments_reports_dev() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return dev mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( true );
+
+ // Act.
+ $is_dev_mode = $this->sut->is_in_dev_mode( $fake_gateway );
+
+ // Assert.
+ $this->assertTrue( $is_dev_mode );
+ }
+
+ /**
+ * Test is_in_dev_mode returns false when WC_Payments mode reports not dev mode.
+ */
+ public function test_is_in_dev_mode_when_woopayments_reports_not_dev() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return not dev mode.
+ $this->mock_woopayments_mode
+ ->method( 'is_dev' )
+ ->willReturn( false );
+
+ // Act.
+ $is_dev_mode = $this->sut->is_in_dev_mode( $fake_gateway );
+
+ // Assert.
+ $this->assertFalse( $is_dev_mode );
+ }
+
+ /**
+ * Test is_in_dev_mode delegates to parent when WC_Payments unavailable.
+ */
+ public function test_is_in_dev_mode_delegates_to_parent_when_woopayments_unavailable() {
+ // Arrange - Gateway with dev mode flag.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'dev_mode' => true,
+ )
+ );
+
+ // Arrange - Mock WC_Payments as not available.
+ $this->mockable_proxy->register_function_mocks(
+ array(
+ 'class_exists' => function ( $class_name ) {
+ if ( 'WC_Payments' === $class_name ) {
+ return false;
+ }
+ return false;
+ },
+ )
+ );
+
+ // Act.
+ $is_dev_mode = $this->sut->is_in_dev_mode( $fake_gateway );
+
+ // Assert - Should delegate to parent when WC_Payments unavailable.
+ $this->assertTrue( $is_dev_mode );
+ }
+
+ /**
+ * Test is_in_test_mode_onboarding returns true when WC_Payments mode reports test mode onboarding.
+ */
+ public function test_is_in_test_mode_onboarding_when_woopayments_reports_test_mode() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return test mode onboarding.
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( true );
+
+ // Act.
+ $is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $fake_gateway );
+
+ // Assert.
+ $this->assertTrue( $is_test_mode_onboarding );
+ }
+
+ /**
+ * Test is_in_test_mode_onboarding returns false when WC_Payments mode reports not test mode onboarding.
+ */
+ public function test_is_in_test_mode_onboarding_when_woopayments_reports_not_test_mode() {
+ // Arrange.
+ $fake_gateway = new FakePaymentGateway( 'woocommerce_payments', array() );
+
+ // Arrange - Mock WooPayments mode to return not test mode onboarding.
+ $this->mock_woopayments_mode
+ ->method( 'is_test_mode_onboarding' )
+ ->willReturn( false );
+
+ // Act.
+ $is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $fake_gateway );
+
+ // Assert.
+ $this->assertFalse( $is_test_mode_onboarding );
+ }
+
+ /**
+ * Test is_in_test_mode_onboarding delegates to parent when WC_Payments unavailable.
+ */
+ public function test_is_in_test_mode_onboarding_delegates_to_parent_when_woopayments_unavailable() {
+ // Arrange - Gateway with test mode onboarding flag.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'test_mode_onboarding' => true,
+ )
+ );
+
+ // Arrange - Mock WC_Payments as not available.
+ $this->mockable_proxy->register_function_mocks(
+ array(
+ 'class_exists' => function ( $class_name ) {
+ if ( 'WC_Payments' === $class_name ) {
+ return false;
+ }
+ return false;
+ },
+ )
+ );
+
+ // Act.
+ $is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $fake_gateway );
+
+ // Assert - Should delegate to parent when WC_Payments unavailable.
+ $this->assertTrue( $is_test_mode_onboarding );
+ }
+
+ /**
+ * Test get_onboarding_url with connected account returns URL with base params only.
+ */
+ public function test_get_onboarding_url_with_connected_account() {
+ // Arrange - Gateway with connected account.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => true,
+ )
+ );
+
+ // Act.
+ $onboarding_url = $this->sut->get_onboarding_url( $fake_gateway );
+
+ // Assert - Should contain base URL and standard params but NO test_drive params.
+ $this->assertStringContainsString( 'https://example.com/wp-admin/woopayments/connect-url', $onboarding_url );
+ $this->assertStringContainsString( 'from=', $onboarding_url );
+ $this->assertStringContainsString( 'source=', $onboarding_url );
+ $this->assertStringContainsString( 'redirect_to_settings_page=true', $onboarding_url );
+ // Should NOT include test_drive params for connected accounts.
+ $this->assertStringNotContainsString( 'test_drive=true', $onboarding_url );
+ $this->assertStringNotContainsString( 'auto_start_test_drive_onboarding=true', $onboarding_url );
+ }
+
+ /**
+ * Test get_onboarding_url for new store in coming soon mode with already selling profile.
+ */
+ public function test_get_onboarding_url_coming_soon_mode_already_selling_online() {
+ // Arrange - Gateway without connected account.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => false,
+ )
+ );
+
+ // Arrange - Store in coming soon mode.
+ update_option( 'woocommerce_coming_soon', 'yes' );
+
+ // Arrange - Onboarding profile: already selling online.
+ update_option(
+ 'woocommerce_onboarding_profile',
+ array(
+ 'business_choice' => 'im_already_selling',
+ 'selling_online_answer' => 'yes_im_selling_online',
+ )
+ );
+
+ try {
+ // Act.
+ $onboarding_url = $this->sut->get_onboarding_url( $fake_gateway );
+
+ // Assert - Should do LIVE onboarding (no test_drive params).
+ $this->assertStringContainsString( 'https://example.com/wp-admin/woopayments/connect-url', $onboarding_url );
+ $this->assertStringNotContainsString( 'test_drive=true', $onboarding_url );
+ $this->assertStringNotContainsString( 'auto_start_test_drive_onboarding=true', $onboarding_url );
+ } finally {
+ // Clean up.
+ delete_option( 'woocommerce_coming_soon' );
+ delete_option( 'woocommerce_onboarding_profile' );
+ }
+ }
+
+ /**
+ * Test get_onboarding_url for new store in coming soon mode with both online and offline.
+ */
+ public function test_get_onboarding_url_coming_soon_mode_selling_both() {
+ // Arrange - Gateway without connected account.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => false,
+ )
+ );
+
+ // Arrange - Store in coming soon mode.
+ update_option( 'woocommerce_coming_soon', 'yes' );
+
+ // Arrange - Onboarding profile: selling both online and offline.
+ update_option(
+ 'woocommerce_onboarding_profile',
+ array(
+ 'business_choice' => 'im_already_selling',
+ 'selling_online_answer' => 'im_selling_both_online_and_offline',
+ )
+ );
+
+ try {
+ // Act.
+ $onboarding_url = $this->sut->get_onboarding_url( $fake_gateway );
+
+ // Assert - Should do LIVE onboarding (no test_drive params).
+ $this->assertStringContainsString( 'https://example.com/wp-admin/woopayments/connect-url', $onboarding_url );
+ $this->assertStringNotContainsString( 'test_drive=true', $onboarding_url );
+ $this->assertStringNotContainsString( 'auto_start_test_drive_onboarding=true', $onboarding_url );
+ } finally {
+ // Clean up.
+ delete_option( 'woocommerce_coming_soon' );
+ delete_option( 'woocommerce_onboarding_profile' );
+ }
+ }
+
+ /**
+ * Test get_onboarding_url for new store in coming soon mode with other profile.
+ */
+ public function test_get_onboarding_url_coming_soon_mode_other_profile() {
+ // Arrange - Gateway without connected account.
+ $fake_gateway = new FakePaymentGateway(
+ 'woocommerce_payments',
+ array(
+ 'enabled' => true,
+ 'account_connected' => false,
+ )
+ );
+
+ // Arrange - Store in coming soon mode.
+ update_option( 'woocommerce_coming_soon', 'yes' );
+
+ // Arrange - Onboarding profile: not already selling.
+ update_option(
+ 'woocommerce_onboarding_profile',
+ array(
+ 'business_choice' => 'im_just_starting',
+ )
+ );
+
+ try {
+ // Act.
+ $onboarding_url = $this->sut->get_onboarding_url( $fake_gateway );
+
+ // Assert - Should do TEST-DRIVE onboarding (includes test_drive params).
+ $this->assertStringContainsString( 'https://example.com/wp-admin/woopayments/connect-url', $onboarding_url );
+ $this->assertStringContainsString( 'test_drive=true', $onboarding_url );
+ $this->assertStringContainsString( 'auto_start_test_drive_onboarding=true', $onboarding_url );
+ } finally {
+ // Clean up.
+ delete_option( 'woocommerce_coming_soon' );
+ delete_option( 'woocommerce_onboarding_profile' );
+ }
+ }
+
+ /**
+ * Setup WPCOM connection manager mock.
+ *
+ * Creates a mock of the WPCOM connection manager and configures it to
+ * simulate a connected Jetpack site with a connected owner.
+ */
+ private function setup_wpcom_connection_mock(): void {
+ $this->mock_wpcom_connection_manager = $this->getMockBuilder( WPCOM_Connection_Manager::class )
+ ->onlyMethods(
+ array(
+ 'is_connected',
+ 'has_connected_owner',
+ 'is_connection_owner',
+ )
+ )
+ ->getMock();
+ }
+
+ /**
+ * Configure the WPCOM connection manager mock.
+ *
+ * @param bool $is_connected Whether the site is connected to WPCOM.
+ * @param bool $has_connected_owner Whether the connected owner exists.
+ * @param bool $is_connection_owner Whether the current user is the connection owner.
+ */
+ private function mock_wpcom_connection( bool $is_connected = true, bool $has_connected_owner = true, bool $is_connection_owner = true ): void {
+ $this->mock_wpcom_connection_manager
+ ->method( 'is_connected' )
+ ->willReturn( $is_connected );
+ $this->mock_wpcom_connection_manager
+ ->method( 'has_connected_owner' )
+ ->willReturn( $has_connected_owner );
+ $this->mock_wpcom_connection_manager
+ ->method( 'is_connection_owner' )
+ ->willReturn( $is_connection_owner );
+ }
+
+ /**
+ * Setup WooPayments DI container mock.
+ *
+ * Creates a mock WooPayments DI container with standard methods for testing.
+ */
+ private function setup_woopayments_container_mock(): void {
+ $this->mock_woopayments_container = $this->getMockBuilder( \stdClass::class )
+ ->addMethods( array( 'get' ) )
+ ->getMock();
+ }
+
+ /**
+ * Setup WooPayments WooPaymentsRestController mock.
+ */
+ private function setup_woopayments_reset_controller_mock(): void {
+ // Mock the WooPaymentsRestController to provide REST URL paths.
+ $this->mock_rest_controller = $this->createMock( WooPaymentsRestController::class );
+ $this->mock_rest_controller
+ ->method( 'get_rest_url_path' )
+ ->willReturnCallback(
+ function ( $relative_path = '' ) {
+ $path = '/some/rest/for/woopayments';
+ if ( ! empty( $relative_path ) ) {
+ $path .= '/' . ltrim( $relative_path, '/' );
+ }
+ return $path;
+ }
+ );
+ }
+
+ /**
+ * Setup WooPayments account service mock.
+ *
+ * Creates a mock account service with standard methods for testing.
+ */
+ private function setup_woopayments_account_service_mock(): void {
+ $this->mock_woopayments_account_service = $this->getMockBuilder( \stdClass::class )
+ ->addMethods( array( 'is_stripe_account_valid', 'get_account_status_data' ) )
+ ->getMock();
+ }
+
+ /**
+ * Setup WooPayments mode mock.
+ *
+ * Creates a mock WooPayments mode with standard methods for testing.
+ */
+ private function setup_woopayments_mode_mock(): void {
+ $this->mock_woopayments_mode = $this->getMockBuilder( \stdClass::class )
+ ->addMethods( array( 'is_test', 'is_test_mode_onboarding', 'is_dev' ) )
+ ->getMock();
+ }
+
+ /**
+ * Setup legacy proxy mocks.
+ *
+ * Configures the mockable legacy proxy with class, static, and function mocks
+ * needed for testing the payments functionality.
+ */
+ private function setup_legacy_proxy_mocks(): void {
+ /**
+ * TestingContainer instance.
+ *
+ * @var TestingContainer $container
+ */
+ $container = wc_get_container();
+
+ $this->mockable_proxy = $container->get( LegacyProxy::class );
+ $this->mockable_proxy->register_class_mocks(
+ array(
+ WPCOM_Connection_Manager::class => $this->mock_wpcom_connection_manager,
+ WooPaymentsRestController::class => $this->mock_rest_controller,
+ )
+ );
+
+ // We have no way of knowing if the container has already resolved the mocked classes,
+ // so we need to reset all resolved instances.
+ $container->reset_all_resolved();
+
+ $this->mockable_proxy->register_static_mocks(
+ array(
+ 'WC_Payments_Utils' => array(
+ 'supported_countries' => function () {
+ return $this->get_woopayments_supported_countries();
+ },
+ ),
+ 'WC_Payments' => array(
+ 'get_account_service' => function () {
+ return $this->mock_woopayments_account_service;
+ },
+ 'mode' => function () {
+ return $this->mock_woopayments_mode;
+ },
+ ),
+ 'WC_Payments_Account' => array(
+ 'get_connect_url' => function () {
+ return 'https://example.com/wp-admin/woopayments/connect-url?existing=param';
+ },
+ ),
+ PluginsHelper::class => array(
+ 'get_plugin_data' => function ( $plugin_file ) {
+ if ( 'woocommerce-payments/woocommerce-payments.php' === $plugin_file ) {
+ return array(
+ 'Version' => '4.0.0',
+ );
+ }
+
+ return false;
+ },
+ ),
+ )
+ );
+
+ $this->mockable_proxy->register_function_mocks(
+ array(
+ 'class_exists' => function ( $class_name ) {
+ if ( in_array( $class_name, array( 'WC_Payments', 'WC_Payments_Account', 'WC_Payments_Utils' ), true ) ) {
+ return true;
+ }
+
+ // For other classes, delegate to PHP's native class_exists()
+ // so tests mirror runtime reality.
+ return \class_exists( $class_name );
+ },
+ 'method_exists' => function ( $object_or_class, $method_name ) {
+ if ( is_object( $object_or_class ) && $object_or_class === $this->mock_woopayments_mode ) {
+ if ( in_array( $method_name, array( 'is_test', 'is_test_mode_onboarding', 'is_dev' ), true ) ) {
+ return true;
+ }
+ }
+
+ if ( is_object( $object_or_class ) && $object_or_class === $this->mock_woopayments_account_service ) {
+ if ( in_array( $method_name, array( 'get_account_status_data' ), true ) ) {
+ return true;
+ }
+ }
+ if ( is_string( $object_or_class ) ) {
+ if ( 'WC_Payments' === $object_or_class ) {
+ if ( in_array( $method_name, array( 'mode' ), true ) ) {
+ return true;
+ }
+ }
+
+ if ( 'WC_Payments_Account' === $object_or_class ) {
+ if ( in_array( $method_name, array( 'get_connect_url' ), true ) ) {
+ return true;
+ }
+ }
+
+ if ( 'WC_Payments_Utils' === $object_or_class ) {
+ if ( in_array( $method_name, array( 'supported_countries' ), true ) ) {
+ return true;
+ }
+ }
+ }
+ // For other methods, delegate to PHP's native method_exists()
+ // so tests mirror runtime reality.
+ return \method_exists( $object_or_class, $method_name );
+ },
+ 'is_callable' => function () {
+ if ( func_num_args() > 0 ) {
+ $callable = func_get_arg( 0 );
+ if ( is_array( $callable ) && count( $callable ) === 2 ) {
+ $object_or_class = $callable[0];
+ $method_name = $callable[1];
+ if ( $object_or_class === $this->mock_woopayments_mode &&
+ in_array( $method_name, array( 'is_test', 'is_test_mode_onboarding', 'is_dev' ), true ) ) {
+ return true;
+ }
+
+ if ( $object_or_class === $this->mock_woopayments_account_service &&
+ in_array( $method_name, array( 'get_account_status_data' ), true ) ) {
+ return true;
+ }
+ } elseif ( is_string( $callable ) ) {
+ if ( in_array(
+ $callable,
+ array( 'WC_Payments::mode', 'WC_Payments_Account::get_connect_url', 'WC_Payments_Utils::supported_countries' ),
+ true
+ )
+ ) {
+
+ return true;
+ }
+ }
+
+ // For other callables, delegate to PHP's native is_callable()
+ // so tests mirror runtime reality.
+ return \is_callable( $callable );
+ }
+
+ // No arguments provided.
+ return false;
+ },
+ 'wcpay_get_container' => function () {
+ return $this->mock_woopayments_container;
+ },
+ ),
+ );
+ }
+
+ /**
+ * Get the list of supported countries for WooPayments.
+ *
+ * @return array Array of country codes and names.
+ */
+ private function get_woopayments_supported_countries(): array {
+ // This is just a subset of countries that WooPayments supports.
+ // But it should cover our testing needs.
+ return array(
+ 'us' => 'United States',
+ 'gb' => 'United Kingdom',
+ 'de' => 'Germany',
+ );
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
index 63c0309682..0c8d0d52e9 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
@@ -410,6 +410,190 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
);
}
+ /**
+ * Test getting payment gateway provider instance returns specific provider.
+ */
+ public function test_get_payment_gateway_provider_instance_returns_specific_provider() {
+ // Arrange - woocommerce_payments is mapped to WooPayments provider.
+ $gateway_id = 'woocommerce_payments';
+
+ // Act.
+ $provider = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+
+ // Assert.
+ $this->assertInstanceOf(
+ PaymentsProviders\WooPayments::class,
+ $provider,
+ 'Should return specific WooPayments provider instance'
+ );
+ }
+
+ /**
+ * Test getting payment gateway provider instance returns specific provider with wildcard match.
+ */
+ public function test_get_payment_gateway_provider_instance_returns_specific_provider_with_wildcard() {
+ // Arrange - stripe_* pattern matches stripe_ideal, and should return Stripe provider.
+ $gateway_id = 'stripe_ideal';
+
+ // Act.
+ $provider = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+
+ // Assert.
+ $this->assertInstanceOf(
+ PaymentsProviders\Stripe::class,
+ $provider,
+ 'Should return Stripe provider for wildcard match'
+ );
+ }
+
+ /**
+ * Test getting payment gateway provider instance returns generic provider when no mapping exists.
+ */
+ public function test_get_payment_gateway_provider_instance_returns_generic_provider_when_no_mapping() {
+ // Arrange - Use a gateway ID that has no mapping.
+ $gateway_id = 'unknown_gateway_id';
+
+ // Act.
+ $provider = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+
+ // Assert - Verify it's the generic provider, not a specific subclass.
+ $this->assertSame(
+ PaymentGateway::class,
+ get_class( $provider ),
+ 'Should return generic PaymentGateway instance when no mapping found'
+ );
+ }
+
+ /**
+ * Test getting payment gateway provider instance returns cached instance.
+ */
+ public function test_get_payment_gateway_provider_instance_returns_cached_instance() {
+ // Arrange.
+ $gateway_id = 'woocommerce_payments';
+
+ // Act - Get provider instance twice.
+ $provider1 = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+ $provider2 = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+
+ // Assert - Should return the same instance (cached).
+ $this->assertSame(
+ $provider1,
+ $provider2,
+ 'Should return same cached instance for same gateway ID'
+ );
+ }
+
+ /**
+ * Test getting payment gateway provider instance returns generic provider for invalid provider class.
+ */
+ public function test_get_payment_gateway_provider_instance_returns_generic_provider_for_invalid_provider_class() {
+ // Arrange - Use reflection to inject an invalid provider class mapping.
+ $reflection = new \ReflectionClass( $this->sut );
+ $property = $reflection->getProperty( 'payment_gateways_providers_class_map' );
+ $property->setAccessible( true );
+
+ // Get current map and add invalid mapping.
+ $current_map = $property->getValue( $this->sut );
+ $current_map['invalid_gateway'] = \stdClass::class; // stdClass does not extend PaymentGateway.
+ $property->setValue( $this->sut, $current_map );
+
+ // Expect the wc_doing_it_wrong notice.
+ $this->setExpectedIncorrectUsage( PaymentsProviders::class . '::get_payment_gateway_provider_instance' );
+
+ // Act.
+ $provider = $this->sut->get_payment_gateway_provider_instance( 'invalid_gateway' );
+
+ // Assert - Verify it's the generic provider, not a specific subclass.
+ $this->assertSame(
+ PaymentGateway::class,
+ get_class( $provider ),
+ 'Should return generic PaymentGateway instance for invalid provider class'
+ );
+ }
+
+ /**
+ * Test getting payment extension suggestion provider instance returns specific provider.
+ */
+ public function test_get_payment_extension_suggestion_provider_instance_returns_specific_provider() {
+ // Arrange - woopayments PES ID is mapped to WooPayments provider.
+ $pes_id = ExtensionSuggestions::WOOPAYMENTS;
+
+ // Act.
+ $provider = $this->sut->get_payment_extension_suggestion_provider_instance( $pes_id );
+
+ // Assert.
+ $this->assertInstanceOf(
+ PaymentsProviders\WooPayments::class,
+ $provider,
+ 'Should return specific WooPayments provider instance'
+ );
+ }
+
+ /**
+ * Test getting payment extension suggestion provider instance returns generic provider when no mapping exists.
+ */
+ public function test_get_payment_extension_suggestion_provider_instance_returns_generic_provider_when_no_mapping() {
+ // Arrange - Use a PES ID that has no mapping.
+ $pes_id = 'unknown_pes_id';
+
+ // Act.
+ $provider = $this->sut->get_payment_extension_suggestion_provider_instance( $pes_id );
+
+ // Assert - Verify it's the generic provider, not a specific subclass.
+ $this->assertSame(
+ PaymentGateway::class,
+ get_class( $provider ),
+ 'Should return generic PaymentGateway instance when no mapping found'
+ );
+ }
+
+ /**
+ * Test getting payment extension suggestion provider instance returns cached instance.
+ */
+ public function test_get_payment_extension_suggestion_provider_instance_returns_cached_instance() {
+ // Arrange.
+ $pes_id = ExtensionSuggestions::WOOPAYMENTS;
+
+ // Act - Get provider instance twice.
+ $provider1 = $this->sut->get_payment_extension_suggestion_provider_instance( $pes_id );
+ $provider2 = $this->sut->get_payment_extension_suggestion_provider_instance( $pes_id );
+
+ // Assert - Should return the same instance (cached).
+ $this->assertSame(
+ $provider1,
+ $provider2,
+ 'Should return same cached instance for same PES ID'
+ );
+ }
+
+ /**
+ * Test getting payment extension suggestion provider instance returns generic provider for invalid provider class.
+ */
+ public function test_get_payment_extension_suggestion_provider_instance_returns_generic_provider_for_invalid_provider_class() {
+ // Arrange - Use reflection to inject an invalid provider class mapping.
+ $reflection = new \ReflectionClass( $this->sut );
+ $property = $reflection->getProperty( 'payment_extension_suggestions_providers_class_map' );
+ $property->setAccessible( true );
+
+ // Get current map and add invalid mapping.
+ $current_map = $property->getValue( $this->sut );
+ $current_map['invalid_pes'] = \stdClass::class; // stdClass does not extend PaymentGateway.
+ $property->setValue( $this->sut, $current_map );
+
+ // Expect the wc_doing_it_wrong notice.
+ $this->setExpectedIncorrectUsage( PaymentsProviders::class . '::get_payment_extension_suggestion_provider_instance' );
+
+ // Act.
+ $provider = $this->sut->get_payment_extension_suggestion_provider_instance( 'invalid_pes' );
+
+ // Assert - Verify it's the generic provider, not a specific subclass.
+ $this->assertSame(
+ PaymentGateway::class,
+ get_class( $provider ),
+ 'Should return generic PaymentGateway instance for invalid provider class'
+ );
+ }
+
/**
* Test getting payment gateway base details.
*/