diff --git a/changelog.txt b/changelog.txt index fc778f8fec..806df825f3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -17,6 +17,7 @@ * Fix - Klarna not processing recurring payments * Fix - Fix Express Checkout error with free trial subscription on blocks cart/checkout * Fix - Prevent retrying requests that errored out due to declined payment methods +* Fix - Detect WooCommerce Subscriptions staging sites when checking if payments can be detached = 10.0.1 - 2025-10-15 = * Fix - Remove persistent reconnection notices diff --git a/includes/class-wc-stripe-api.php b/includes/class-wc-stripe-api.php index 5e36d7a445..784b4b640f 100644 --- a/includes/class-wc-stripe-api.php +++ b/includes/class-wc-stripe-api.php @@ -576,21 +576,49 @@ public static function should_detach_payment_method_from_customer() { return true; } - // Return true for the delete user request from the admin dashboard or WP-CLI when the site is a production site - // and return false when the site is a staging/local/development site. - // This is to avoid detaching the payment method from the live production site. - // Requests coming from the customer account page i.e delete payment method, are not affected by this and returns true. - if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { - if ( 'production' === wp_get_environment_type() ) { - return true; - } else { - return false; - } + // Requests coming from the customer account page i.e delete payment method, should always be allowed, and should return true. + $is_admin_request = is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ); + if ( ! $is_admin_request ) { + return true; + } + + // If we are not in a production site, we should not detach the payment method, + // as we don't want to detach the payment method from the live production site. + $is_staging_site = self::is_woocommerce_subscriptions_staging_mode() || 'production' !== wp_get_environment_type(); + if ( $is_staging_site ) { + return false; } + // Otherwise, we are in a production site, and we should detach the payment method. return true; } + /** + * Checks if the site has WooCommerce Subscriptions staging mode enabled. + * + * @return bool True if the site has WooCommerce Subscriptions active and staging mode enabled, false otherwise. + */ + private static function is_woocommerce_subscriptions_staging_mode() { + if ( ! class_exists( 'WC_Subscriptions' ) ) { + return false; + } + + // Check if WooCommerce Subscriptions >= 4.0.0 is active (uses WCS_Staging class) + if ( class_exists( 'WCS_Staging' ) && method_exists( 'WCS_Staging', 'is_duplicate_site' ) ) { + return WCS_Staging::is_duplicate_site(); + } + + // Check if WooCommerce Subscriptions < 4.0.0 is active + // and if it is, check if the site is in staging mode via is_duplicate_site(). + if ( version_compare( WC_Subscriptions::$version, '4.0.0', '<' ) + && method_exists( 'WC_Subscriptions', 'is_duplicate_site' ) + ) { + return WC_Subscriptions::is_duplicate_site(); + } + + return false; + } + /** * Get the payment method configuration. * diff --git a/readme.txt b/readme.txt index b5851d5c09..3c9a0f8455 100644 --- a/readme.txt +++ b/readme.txt @@ -127,5 +127,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o * Fix - Klarna not processing recurring payments * Fix - Fix Express Checkout error with free trial subscription on blocks cart/checkout * Fix - Prevent retrying requests that errored out due to declined payment methods +* Fix - Detect WooCommerce Subscriptions staging sites when checking if payments can be detached [See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt). diff --git a/tests/phpunit/Helpers/WCS_Staging.php b/tests/phpunit/Helpers/WCS_Staging.php new file mode 100644 index 0000000000..ab63ed81ee --- /dev/null +++ b/tests/phpunit/Helpers/WCS_Staging.php @@ -0,0 +1,34 @@ + json_encode( [ 'error' => 'invalid_api_key' ] ), ]; } + + public function provide_test_should_detach_payment_method_from_customer(): array { + return [ + 'test mode from non-admin context should detach' => [ + 'expected_return' => true, + 'is_test_mode' => true, + 'is_admin_request' => false, + 'is_wc_sub_staging_site' => false, + ], + 'live mode from non-admin context should detach' => [ + 'expected_return' => true, + 'is_test_mode' => false, + 'is_admin_request' => false, + 'is_wc_sub_staging_site' => false, + ], + 'test mode from admin context should detach' => [ + 'expected_return' => true, + 'is_test_mode' => true, + 'is_admin_request' => true, + 'is_wc_sub_staging_site' => false, + ], + 'live mode from admin context with no subscription staging site should detach' => [ + 'expected_return' => true, + 'is_test_mode' => false, + 'is_admin_request' => true, + 'is_wc_sub_staging_site' => false, + ], + 'live mode from admin context with subscription staging site should not detach' => [ + 'expected_return' => false, + 'is_test_mode' => false, + 'is_admin_request' => true, + 'is_wc_sub_staging_site' => true, + ], + // Ideally, we would test multiple environment types, but wp_get_environment_type() uses a + // static variable that can't be modified between tests. + ]; + } + + /** + * @dataProvider provide_test_should_detach_payment_method_from_customer + */ + public function test_should_detach_payment_method_from_customer( bool $expected_return, bool $is_test_mode, bool $is_admin_request, bool $is_wc_sub_staging_site = false ) { + $initial_test_mode = \WC_Stripe_Mode::is_test(); + + $stripe_settings = \WC_Stripe_Helper::get_stripe_settings(); + $stripe_settings['testmode'] = $is_test_mode ? 'yes' : 'no'; + \WC_Stripe_Helper::update_main_stripe_settings( $stripe_settings ); + + $initial_current_screen = null; + $reset_current_screen = false; + + if ( $is_admin_request ) { + $initial_current_screen = $GLOBALS['current_screen'] ?? null; + $reset_current_screen = true; + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $GLOBALS['current_screen'] = \WP_Screen::get( 'post.php' ); + } + + require_once __DIR__ . '/Helpers/WCS_Staging.php'; + \WCS_Staging::set_is_duplicate_site( $is_wc_sub_staging_site ); + + $result = \WC_Stripe_API::should_detach_payment_method_from_customer(); + + // Reset the environment before running any assertions. + if ( $reset_current_screen ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $GLOBALS['current_screen'] = $initial_current_screen; + } + + if ( $initial_test_mode !== $is_test_mode ) { + $stripe_settings = \WC_Stripe_Helper::get_stripe_settings(); + $stripe_settings['testmode'] = $initial_test_mode ? 'yes' : 'no'; + \WC_Stripe_Helper::update_main_stripe_settings( $stripe_settings ); + } + + \WCS_Staging::set_is_duplicate_site( false ); + + $this->assertEquals( $expected_return, $result ); + } }