From a19aafbcbdb63e10921c7355b1858fd047969290 Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:22:34 +0300 Subject: [PATCH] Remove explicit prices where a single currency is active (#2673) --- ...s-wc-payments-explicit-price-formatter.php | 76 ++++++ includes/multi-currency/MultiCurrency.php | 22 +- ...s-wc-payments-explicit-price-formatter.php | 222 +++++++++++++++++- 3 files changed, 315 insertions(+), 5 deletions(-) diff --git a/includes/class-wc-payments-explicit-price-formatter.php b/includes/class-wc-payments-explicit-price-formatter.php index 6d415a9771b..7aabeaf19c8 100644 --- a/includes/class-wc-payments-explicit-price-formatter.php +++ b/includes/class-wc-payments-explicit-price-formatter.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments */ +use WCPay\MultiCurrency\MultiCurrency; + if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } @@ -13,6 +15,14 @@ * Class for displaying the explicit prices on total amounts. */ class WC_Payments_Explicit_Price_Formatter { + + /** + * The multi currency instance for checking the number of enabled currencies + * + * @var MultiCurrency + */ + private static $multi_currency_instance = null; + /** * Inits the formatter, registering the necessary hooks. */ @@ -23,6 +33,66 @@ public static function init() { add_action( 'woocommerce_admin_order_totals_after_total', [ __CLASS__, 'unregister_formatted_woocommerce_price_filter' ] ); } + /** + * Overrides the MultiCurrency instance that the class uses. + * Mostly for testing purposes. + * + * @param MultiCurrency $multi_currency MultCurrency class. + * + * @return void + */ + public static function set_multi_currency_instance( MultiCurrency $multi_currency ) { + self::$multi_currency_instance = $multi_currency; + } + + /** + * Checks if the method should output explicit price on frontend + * + * @return bool Whether if it should return explicit price or not + */ + private static function should_output_explicit_price() { + // As is_admin() returns false for REST requests, we need to skip those checks for REST requests for backend too. + $is_backend_request = ( + // Current URL is an admın URL. + ( 0 === strpos( strtolower( wp_get_referer() ), strtolower( admin_url() ) ) ) + // The current request is a REST request. + && WC()->is_rest_api_request() + ); + + // Only apply this for frontend. + if ( ! is_admin() && ! defined( 'DOING_CRON' ) && ! $is_backend_request ) { + // If customer multi currency is disabled, don't use explicit currencies on frontend. + // Because it'll have only the store currency active, same as count == 1. + if ( ! WC_Payments_Features::is_customer_multi_currency_enabled() ) { + return false; + } + + // If the MultiCurrency instance hasn't been defined yet, fetch the instance. + if ( null === self::$multi_currency_instance ) { + self::$multi_currency_instance = WC_Payments_Multi_Currency(); + } + + // If the instance isn't initalized yet, skip the checks. + if ( false === self::$multi_currency_instance->is_initialized() ) { + return false; + } + + $enabled_currencies = self::$multi_currency_instance->get_enabled_currencies(); + + // If there isn't any enabled currency, skip it. + if ( empty( $enabled_currencies ) ) { + return false; + } + + // Don't attach explicit price filters on frontend with a single currency setup. + if ( is_array( $enabled_currencies ) && 1 === count( $enabled_currencies ) ) { + return false; + } + } + + return true; + } + /** * Registers the get_explicit_price filter for the order details screen. * @@ -54,6 +124,9 @@ public static function unregister_formatted_woocommerce_price_filter() { * @return string */ public static function get_explicit_price( string $price, WC_Order $order = null ) { + if ( false === static::should_output_explicit_price() ) { + return $price; + } if ( null === $order ) { $currency_code = get_woocommerce_currency(); } else { @@ -75,6 +148,9 @@ public static function get_explicit_price( string $price, WC_Order $order = null * @return array The modified arguments */ public static function get_explicit_price_args( $args ) { + if ( false === static::should_output_explicit_price() ) { + return $args; + } if ( false === strpos( $args['price_format'], $args['currency'] ) ) { $args['price_format'] = sprintf( '%s %s', $args['price_format'], $args['currency'] ); } diff --git a/includes/multi-currency/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php index e8f6e6ec924..f06f5d35626 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -40,6 +40,13 @@ class MultiCurrency { */ protected static $instance = null; + /** + * Static flag to show if the currencies initialization has been completed + * + * @var bool + */ + protected static $is_initialized = false; + /** * Compatibility instance. * @@ -108,7 +115,7 @@ class MultiCurrency { * * @var array */ - protected $available_currencies; + protected $available_currencies = []; /** * The default currency. @@ -122,7 +129,7 @@ class MultiCurrency { * * @var array */ - protected $enabled_currencies; + protected $enabled_currencies = []; /** * Client for making requests to the WooCommerce Payments API @@ -240,6 +247,8 @@ public function init() { if ( is_admin() ) { add_action( 'admin_init', [ __CLASS__, 'add_woo_admin_notes' ] ); } + + static::$is_initialized = true; } /** @@ -1021,4 +1030,13 @@ private function register_admin_scripts() { \WC_Payments::get_file_version( 'dist/multi-currency.css' ) ); } + + /** + * Returns if the currency initialization are completed + * + * @return bool If the initializations have been completedÎ + */ + public static function is_initialized() : bool { + return static::$is_initialized; + } } diff --git a/tests/unit/test-class-wc-payments-explicit-price-formatter.php b/tests/unit/test-class-wc-payments-explicit-price-formatter.php index dbfebae0e8f..d04bc9f7205 100644 --- a/tests/unit/test-class-wc-payments-explicit-price-formatter.php +++ b/tests/unit/test-class-wc-payments-explicit-price-formatter.php @@ -5,22 +5,238 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\MultiCurrency\MultiCurrency; + /** * WC_Payments_Explicit_Price_Formatter unit tests. */ class WC_Payments_Explicit_Price_Formatter_Test extends WP_UnitTestCase { - public function test_get_explicit_price_with_order_currency() { + + const LOGGED_IN_USER_ID = 1; + const ENABLED_CURRENCIES_OPTION = 'wcpay_multi_currency_enabled_currencies'; + const CACHED_CURRENCIES_OPTION = 'wcpay_multi_currency_cached_currencies'; + const CURRENCY_RETRIEVAL_ERROR_OPTION = 'wcpay_multi_currency_retrieval_error'; + + /** + * Mock enabled currencies. + * + * @var array + */ + private $mock_enabled_currencies = [ 'USD', 'CAD', 'GBP', 'BIF' ]; + + /** + * @var int + */ + private $timestamp_for_testing; + + /** + * Mock available currencies with their rates. + * + * @var array + */ + private $mock_available_currencies = [ + 'USD' => 1, + 'CAD' => 1.206823, + 'GBP' => 0.708099, + 'EUR' => 0.826381, + 'CDF' => 2000, + 'BIF' => 1974, // Zero decimal currency. + 'CLP' => 706.8, // Zero decimal currency. + ]; + + /** + * Mock cached currencies return array + * + * @var array + */ + private $mock_cached_currencies; + + /** + * MultiCurrency instance. + * + * @var MultiCurrency + */ + private $multi_currency; + + /** + * Mock of the API client. + * + * @var WC_Payments_API_Client + */ + private $mock_api_client; + + /** + * Mock of the WC_Payments_Account. + * + * @var WC_Payments_Account + */ + private $mock_account; + + /** + * Mock of the WC_Payments_Localization_Service. + * + * @var WC_Payments_Localization_Service + */ + private $mock_localization_service; + + public function setUp() { + parent::setUp(); + + $this->mock_currency_settings( + 'GBP', + [ + 'price_charm' => '-0.1', + 'price_rounding' => '0.50', + ] + ); + + $this->timestamp_for_testing = strtotime( 'today midnight' ); + + $this->mock_cached_currencies = [ + 'currencies' => $this->mock_available_currencies, + 'updated' => $this->timestamp_for_testing, + 'expires' => $this->timestamp_for_testing + DAY_IN_SECONDS, + ]; + + update_option( self::CACHED_CURRENCIES_OPTION, $this->mock_cached_currencies ); + update_option( self::ENABLED_CURRENCIES_OPTION, $this->mock_enabled_currencies ); + + $this->init_multi_currency(); + } + + public function tearDown() { + set_current_screen( 'front' ); + WC()->session->__unset( MultiCurrency::CURRENCY_SESSION_KEY ); + remove_all_filters( 'wcpay_multi_currency_apply_charm_only_to_products' ); + remove_all_filters( 'wcpay_multi_currency_available_currencies' ); + remove_all_filters( 'woocommerce_currency' ); + remove_all_filters( 'stylesheet' ); + + delete_user_meta( self::LOGGED_IN_USER_ID, MultiCurrency::CURRENCY_META_KEY ); + wp_set_current_user( 0 ); + + $this->remove_currency_settings_mock( 'GBP', [ 'price_charm', 'price_rounding', 'manual_rate', 'exchange_rate' ] ); + delete_option( self::CACHED_CURRENCIES_OPTION ); + delete_option( self::ENABLED_CURRENCIES_OPTION ); + update_option( 'wcpay_multi_currency_enable_auto_currency', 'no' ); + + WC_Payments_Explicit_Price_Formatter::set_multi_currency_instance( WC_Payments_Multi_Currency() ); + + parent::tearDown(); + } + + public function test_get_explicit_price_with_order_currency_on_backend_with_one_enabled_currency() { + set_current_screen( 'edit-page' ); + $this->prepare_one_enabled_currency(); + $order = $this->createMock( WC_Order::class ); + $order->method( 'get_currency' )->willReturn( 'BRL' ); + + $this->assertSame( 'R$ 5,90 BRL', WC_Payments_Explicit_Price_Formatter::get_explicit_price( 'R$ 5,90', $order ) ); + } + + public function test_get_explicit_price_with_store_currency_on_backend_with_one_enabled_currency() { + set_current_screen( 'edit-page' ); + $this->prepare_one_enabled_currency(); + $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30' ) ); + } + + public function test_get_explicit_price_skips_already_explicit_prices_on_backend_with_one_enabled_currency() { + set_current_screen( 'edit-page' ); + $this->prepare_one_enabled_currency(); + $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30 USD' ) ); + } + + public function test_get_explicit_price_with_order_currency_on_backend_with_multiple_enabled_currencies() { + set_current_screen( 'edit-page' ); + $order = $this->createMock( WC_Order::class ); $order->method( 'get_currency' )->willReturn( 'BRL' ); $this->assertSame( 'R$ 5,90 BRL', WC_Payments_Explicit_Price_Formatter::get_explicit_price( 'R$ 5,90', $order ) ); } - public function test_get_explicit_price_with_store_currency() { + public function test_get_explicit_price_with_store_currency_on_backend_with_multiple_enabled_currencies() { + set_current_screen( 'edit-page' ); $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30' ) ); } - public function test_get_explicit_price_skips_already_explicit_prices() { + public function test_get_explicit_price_skips_already_explicit_prices_on_backend_with_multiple_enabled_currencies() { + set_current_screen( 'edit-page' ); $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30 USD' ) ); } + + public function test_get_explicit_price_with_order_currency_on_frontend_with_one_enabled_currency() { + $this->prepare_one_enabled_currency(); + $order = $this->createMock( WC_Order::class ); + $order->method( 'get_currency' )->willReturn( 'BRL' ); + + $this->assertSame( 'R$ 5,90', WC_Payments_Explicit_Price_Formatter::get_explicit_price( 'R$ 5,90', $order ) ); + } + + public function test_get_explicit_price_with_store_currency_on_frontend_with_one_enabled_currency() { + $this->prepare_one_enabled_currency(); + $this->assertSame( '$10.30', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30' ) ); + } + + public function test_get_explicit_price_skips_already_explicit_prices_on_frontend_with_one_enabled_currency() { + $this->prepare_one_enabled_currency(); + $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30 USD' ) ); + } + + public function test_get_explicit_price_with_order_currency_on_frontend_with_multiple_enabled_currencies() { + $order = $this->createMock( WC_Order::class ); + $order->method( 'get_currency' )->willReturn( 'BRL' ); + + $this->assertSame( 'R$ 5,90 BRL', WC_Payments_Explicit_Price_Formatter::get_explicit_price( 'R$ 5,90', $order ) ); + } + + public function test_get_explicit_price_with_store_currency_on_frontend_with_multiple_enabled_currencies() { + $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30' ) ); + } + + public function test_get_explicit_price_skips_already_explicit_prices_on_frontend_with_multiple_enabled_currencies() { + $this->assertSame( '$10.30 USD', WC_Payments_Explicit_Price_Formatter::get_explicit_price( '$10.30 USD' ) ); + } + + private function prepare_one_enabled_currency() { + $this->multi_currency->set_enabled_currencies( [ 'USD' ] ); + } + + + private function mock_currency_settings( $currency_code, $settings ) { + foreach ( $settings as $setting => $value ) { + update_option( 'wcpay_multi_currency_' . $setting . '_' . strtolower( $currency_code ), $value ); + } + } + + private function remove_currency_settings_mock( $currency_code, $settings ) { + foreach ( $settings as $setting ) { + delete_option( 'wcpay_multi_currency_' . $setting . '_' . strtolower( $currency_code ) ); + } + } + + private function init_multi_currency( $mock_api_client = null, $wcpay_account_connected = true ) { + $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + + $this->mock_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); + + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); + + $this->mock_api_client->method( 'is_server_connected' )->willReturn( true ); + + $this->mock_localization_service->method( 'get_currency_format' )->willReturn( + [ + 'currency_pos' => 'left', + 'thousand_sep' => ',', + 'decimal_sep' => '.', + 'num_decimals' => 2, + ] + ); + + $this->multi_currency = new MultiCurrency( $mock_api_client ?? $this->mock_api_client, $this->mock_account, $this->mock_localization_service ); + $this->multi_currency->init(); + + WC_Payments_Explicit_Price_Formatter::set_multi_currency_instance( $this->multi_currency ); + } }