From ad5f41bf3993172c7c80b15765b541853f09d82a Mon Sep 17 00:00:00 2001 From: Vlad Olaru Date: Mon, 9 Sep 2024 18:09:15 +0300 Subject: [PATCH 01/40] Add active incentive ID to Payments task data to use in Tracks props (#9403) --- ...ts-incentive-to-payments-task-tracks-props | 5 ++++ .../class-wc-payments-incentives-service.php | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 changelog/update-9401-woopayments-incentive-to-payments-task-tracks-props diff --git a/changelog/update-9401-woopayments-incentive-to-payments-task-tracks-props b/changelog/update-9401-woopayments-incentive-to-payments-task-tracks-props new file mode 100644 index 00000000000..7610717bb68 --- /dev/null +++ b/changelog/update-9401-woopayments-incentive-to-payments-task-tracks-props @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Add the active incentive ID to the Payments task wcadmin_tasklist_click Tracks event. + + diff --git a/includes/class-wc-payments-incentives-service.php b/includes/class-wc-payments-incentives-service.php index 692f744669e..cda3c9a6312 100644 --- a/includes/class-wc-payments-incentives-service.php +++ b/includes/class-wc-payments-incentives-service.php @@ -41,6 +41,7 @@ public function init_hooks() { add_action( 'admin_menu', [ $this, 'add_payments_menu_badge' ] ); add_filter( 'woocommerce_admin_allowed_promo_notes', [ $this, 'allowed_promo_notes' ] ); add_filter( 'woocommerce_admin_woopayments_onboarding_task_badge', [ $this, 'onboarding_task_badge' ] ); + add_filter( 'woocommerce_admin_woopayments_onboarding_task_additional_data', [ $this, 'onboarding_task_additional_data' ], 20 ); } /** @@ -105,6 +106,28 @@ public function onboarding_task_badge( string $badge ): string { return $incentive['task_badge'] ?? $badge; } + /** + * Filter the onboarding task additional data to add the WooPayments incentive data to it. + * + * @param ?array $additional_data The current task additional data. + * + * @return ?array The filtered task additional data. + */ + public function onboarding_task_additional_data( ?array $additional_data ): ?array { + $incentive = $this->get_cached_connect_incentive(); + // Return early if there is no eligible incentive. + if ( empty( $incentive['id'] ) ) { + return $additional_data; + } + + if ( empty( $additional_data ) ) { + $additional_data = []; + } + $additional_data['wooPaymentsIncentiveId'] = $incentive['id']; + + return $additional_data; + } + /** * Gets and caches eligible connect incentive from the server. * From 2eaebf67bc5992e5d4e68d2da9e255a93bbb1142 Mon Sep 17 00:00:00 2001 From: Leonardo Lopes de Albuquerque Date: Tue, 10 Sep 2024 13:50:13 -0300 Subject: [PATCH 02/40] Fixed invalid appearance warning (#9385) --- changelog/fix-9384-invalid-appearence | 4 ++++ client/checkout/api/index.js | 2 +- client/checkout/upe-styles/index.js | 13 ++++++++++--- client/checkout/upe-styles/test/index.js | 5 ++++- client/checkout/woopay/email-input-iframe.js | 2 +- .../express-button/express-checkout-iframe.js | 2 +- .../woopay-express-checkout-button.js | 4 ++-- tests/e2e/config/jest.setup.js | 2 -- 8 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 changelog/fix-9384-invalid-appearence diff --git a/changelog/fix-9384-invalid-appearence b/changelog/fix-9384-invalid-appearence new file mode 100644 index 00000000000..477b9637d46 --- /dev/null +++ b/changelog/fix-9384-invalid-appearence @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed invalid appearance warnings diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index c9b87c69f8d..c43bd2405d8 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -466,7 +466,7 @@ export default class WCPayAPI { return this.request( buildAjaxURL( wcAjaxUrl, 'init_woopay' ), { _wpnonce: nonce, appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType ) + ? getAppearance( appearanceType, true ) : null, email: userEmail, user_session: woopayUserSession, diff --git a/client/checkout/upe-styles/index.js b/client/checkout/upe-styles/index.js index db3723aae6f..1e924f41a42 100644 --- a/client/checkout/upe-styles/index.js +++ b/client/checkout/upe-styles/index.js @@ -443,7 +443,7 @@ export const getFontRulesFromPage = () => { return fontRules; }; -export const getAppearance = ( elementsLocation ) => { +export const getAppearance = ( elementsLocation, forWooPay = false ) => { const selectors = appearanceSelectors.getSelectors( elementsLocation ); // Add hidden fields to DOM for generating styles. @@ -505,11 +505,18 @@ export const getAppearance = ( elementsLocation ) => { '.TabIcon--selected': selectedTabIconRules, '.Text': labelRules, '.Text--redirect': labelRules, + }, + }; + + if ( forWooPay ) { + appearance.rules = { + ...appearance.rules, '.Heading': headingRules, '.Button': buttonRules, '.Link': linkRules, - }, - }; + }; + } + // Remove hidden fields from DOM. hiddenElementsForUPE.cleanup(); return appearance; diff --git a/client/checkout/upe-styles/test/index.js b/client/checkout/upe-styles/test/index.js index f13d5cb2e5c..47467adeb21 100644 --- a/client/checkout/upe-styles/test/index.js +++ b/client/checkout/upe-styles/test/index.js @@ -119,7 +119,10 @@ describe( 'Getting styles for automated theming', () => { return mockCSStyleDeclaration; } ); - const appearance = upeStyles.getAppearance( 'shortcode_checkout' ); + const appearance = upeStyles.getAppearance( + 'shortcode_checkout', + true + ); expect( appearance ).toEqual( { variables: { colorBackground: '#ffffff', diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index 4dfa1698fcd..df410850621 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -190,7 +190,7 @@ export const handleWooPayEmailInput = async ( key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType ) + ? getAppearance( appearanceType, true ) : null, } ).then( ( response ) => { diff --git a/client/checkout/woopay/express-button/express-checkout-iframe.js b/client/checkout/woopay/express-button/express-checkout-iframe.js index 6685d237f49..19d4ff54fe7 100644 --- a/client/checkout/woopay/express-button/express-checkout-iframe.js +++ b/client/checkout/woopay/express-button/express-checkout-iframe.js @@ -110,7 +110,7 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType ) + ? getAppearance( appearanceType, true ) : null, } ).then( ( response ) => { diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index 2b2e7931a9b..347f1a51de4 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -232,7 +232,7 @@ export const WoopayExpressCheckoutButton = ( { appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType ) + ? getAppearance( appearanceType, true ) : null, } ) .then( async ( response ) => { @@ -278,7 +278,7 @@ export const WoopayExpressCheckoutButton = ( { key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType ) + ? getAppearance( appearanceType, true ) : null, } ) .then( async ( response ) => { diff --git a/tests/e2e/config/jest.setup.js b/tests/e2e/config/jest.setup.js index 791c22a46ba..0c6624ea610 100644 --- a/tests/e2e/config/jest.setup.js +++ b/tests/e2e/config/jest.setup.js @@ -36,8 +36,6 @@ const ERROR_MESSAGES_TO_IGNORE = [ 'Failed to load resource: the server responded with a status of 400 (Bad Request)', 'No Amplitude API key provided', 'is registered with an invalid category', - 'is not a supported class', // Silence stripe.js warnings regarding styles. - 'is not a supported property for', // Silence stripe.js warnings regarding styles. ]; ERROR_MESSAGES_TO_IGNORE.forEach( ( errorMessage ) => { From 8f2b1ac66610479282b375078ba00b5aa4d1dfe9 Mon Sep 17 00:00:00 2001 From: Rafael Zaleski Date: Tue, 10 Sep 2024 22:38:52 -0300 Subject: [PATCH 03/40] Add error handling for `loadError` in ECE (#9416) --- changelog/fix-9414-unhandled-ece-loaderror | 4 ++ client/checkout/express-checkout-buttons.scss | 1 - client/express-checkout/index.js | 42 +++++++++++++++---- .../utils/checkPaymentMethodIsAvailable.js | 1 + ...xpress-checkout-button-display-handler.php | 2 +- ...yments-express-checkout-button-handler.php | 2 +- 6 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 changelog/fix-9414-unhandled-ece-loaderror diff --git a/changelog/fix-9414-unhandled-ece-loaderror b/changelog/fix-9414-unhandled-ece-loaderror new file mode 100644 index 00000000000..654ccdcfbba --- /dev/null +++ b/changelog/fix-9414-unhandled-ece-loaderror @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Handle loadError in ECE for Block Context Initialization. diff --git a/client/checkout/express-checkout-buttons.scss b/client/checkout/express-checkout-buttons.scss index 10b6058a3b3..b20ac8a5be8 100644 --- a/client/checkout/express-checkout-buttons.scss +++ b/client/checkout/express-checkout-buttons.scss @@ -2,7 +2,6 @@ margin-top: 1em; width: 100%; clear: both; - margin-bottom: 1.5em; .woocommerce-cart & { margin-bottom: 0; diff --git a/client/express-checkout/index.js b/client/express-checkout/index.js index d1fa717b606..97eae8a9f97 100644 --- a/client/express-checkout/index.js +++ b/client/express-checkout/index.js @@ -242,7 +242,17 @@ jQuery( ( $ ) => { getExpressCheckoutButtonStyleSettings() ); - wcpayECE.showButton( eceButton ); + wcpayECE.renderButton( eceButton ); + + eceButton.on( 'loaderror', () => { + wcPayECEError = __( + 'The cart is incompatible with express checkout.', + 'woocommerce-payments' + ); + if ( ! document.getElementById( 'wcpay-woopay-button' ) ) { + wcpayECE?.getButtonSeparator()?.hide(); + } + } ); eceButton.on( 'click', function ( event ) { // If login is required for checkout, display redirect confirmation dialog. @@ -326,7 +336,19 @@ jQuery( ( $ ) => { onCancelHandler(); } ); - eceButton.on( 'ready', onReadyHandler ); + eceButton.on( 'ready', ( onReadyParams ) => { + onReadyHandler( onReadyParams ); + + if ( + onReadyParams?.availablePaymentMethods && + Object.values( + onReadyParams.availablePaymentMethods + ).filter( Boolean ).length + ) { + wcpayECE.show(); + wcpayECE.getButtonSeparator().show(); + } + } ); if ( getExpressCheckoutData( 'is_product_page' ) ) { wcpayECE.attachProductPageEventListeners( elements ); @@ -520,18 +542,24 @@ jQuery( ( $ ) => { }, getElements: () => { - return $( - '.wcpay-payment-request-wrapper,#wcpay-express-checkout-button-separator' - ); + return $( '#wcpay-express-checkout-element' ); + }, + + getButtonSeparator: () => { + return $( '#wcpay-express-checkout-button-separator' ); }, show: () => { wcpayECE.getElements().show(); }, - showButton: ( eceButton ) => { + hide: () => { + wcpayECE.getElements().hide(); + wcpayECE.getButtonSeparator().hide(); + }, + + renderButton: ( eceButton ) => { if ( $( '#wcpay-express-checkout-element' ).length ) { - wcpayECE.show(); eceButton.mount( '#wcpay-express-checkout-element' ); } }, diff --git a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js index 75296840a9b..a42d7ffefe9 100644 --- a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js +++ b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js @@ -47,6 +47,7 @@ export const checkPaymentMethodIsAvailable = memoize( } } > resolve( false ) } options={ { paymentMethods: { amazonPay: 'never', diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php index fd7ba4eae92..a5e3e08939c 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php @@ -141,7 +141,7 @@ public function display_express_checkout_buttons() { // When Payment Request button is enabled, we need the separator markup on the page, but hidden in case the browser doesn't have any payment request methods to display. // More details: https://github.com/Automattic/woocommerce-payments/pull/5399#discussion_r1073633776. - $separator_starts_hidden = ( $should_show_payment_request || $should_show_express_checkout_button ) && ! $should_show_woopay; + $separator_starts_hidden = ! $should_show_woopay; if ( $should_show_woopay || $should_show_payment_request || $should_show_express_checkout_button ) { ?>
diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php index 1b6f68f6275..682b4eac903 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php @@ -288,7 +288,7 @@ public function display_express_checkout_button_html() { return; } ?> -
+ Date: Wed, 11 Sep 2024 10:37:57 +0100 Subject: [PATCH 04/40] Add title, description and gatewayId to express payment methods (#9379) Co-authored-by: Rafael Zaleski --- changelog/update-express-method-titles | 4 ++++ .../woopay-express-checkout-payment-method.js | 7 +++++++ client/express-checkout/blocks/index.js | 17 +++++++++++++++++ client/payment-request/blocks/index.js | 11 +++++++++++ 4 files changed, 39 insertions(+) create mode 100644 changelog/update-express-method-titles diff --git a/changelog/update-express-method-titles b/changelog/update-express-method-titles new file mode 100644 index 00000000000..76ea4d7695b --- /dev/null +++ b/changelog/update-express-method-titles @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update express payment methods with a title, description and gatewayId diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js b/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js index 228032bbf6d..7ff8870103a 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js @@ -3,6 +3,7 @@ */ import { useCallback } from 'react'; import ReactDOM from 'react-dom'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -44,6 +45,12 @@ const WooPayExpressCheckoutButtonContainer = () => { const wooPayExpressCheckoutPaymentMethod = () => ( { name: PAYMENT_METHOD_NAME_WOOPAY_EXPRESS_CHECKOUT, + title: 'WooPayments - WooPay', + description: __( + 'A one-click, high-converting, secure checkout built for Woo — themed to your brand.', + 'woocommerce-payments' + ), + gatewayId: 'woocommerce_payments', content: , edit: ( ( { paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT, name: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT + '_applePay', + title: 'WooPayments - Apple Pay', + description: __( + "An easy, secure way to pay that's accepted on millions of stores.", + 'woocommerce-payments' + ), + gatewayId: 'woocommerce_payments', content: ( ), @@ -33,6 +44,12 @@ const expressCheckoutElementGooglePay = ( api ) => { return { paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT, name: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT + '_googlePay', + title: 'WooPayments - Google Pay', + description: __( + 'Simplify checkout with fewer steps to pay.', + 'woocommerce-payments' + ), + gatewayId: 'woocommerce_payments', content: ( ; const paymentRequestPaymentMethod = ( api ) => ( { name: PAYMENT_METHOD_NAME_PAYMENT_REQUEST, + title: 'WooPayments - Payment Request', + description: __( + 'Display the Apple Pay, Google Pay, or Stripe Link button to users based on their browser and login status.', + 'woocommerce-payments' + ), + gatewayId: 'woocommerce_payments', content: ( ), From b4c7a7d1de94ca504823b6b3f5d0fe27cf3aca51 Mon Sep 17 00:00:00 2001 From: Vlad Olaru Date: Wed, 11 Sep 2024 13:04:12 +0300 Subject: [PATCH 05/40] Improve PHP unit tests handling of WP filters (#9411) --- ...dev-improve-unit-tests-handling-of-filters | 5 ++++ ...class-wc-payments-customer-service-api.php | 3 ++- ...est-class-buyer-fingerprinting-service.php | 2 ++ .../multi-currency/test-class-analytics.php | 26 ++++++++++++++----- .../multi-currency/test-class-geolocation.php | 14 +++++++++- .../unit/test-class-compatibility-service.php | 10 +++---- ...-payment-gateway-wcpay-process-payment.php | 6 ++++- .../test-class-wc-payment-gateway-wcpay.php | 2 ++ tests/unit/test-class-wc-payments-account.php | 14 ++++++++++ ...est-class-wc-payments-customer-service.php | 2 ++ ...xpress-checkout-button-display-handler.php | 11 ++++++++ ...ayments-express-checkout-button-helper.php | 5 +++- .../unit/test-class-wc-payments-features.php | 21 ++++----------- ...t-class-wc-payments-incentives-service.php | 18 +++++++++++-- ...class-wc-payments-localization-service.php | 2 ++ .../test-class-wc-payments-order-service.php | 7 +---- ...ayments-payment-request-button-handler.php | 10 ++++++- ...test-class-wc-payments-session-service.php | 5 ++++ ...lass-wc-payments-woopay-button-handler.php | 17 +++++++++++- .../woopay/class-woopay-scheduler-test.php | 10 +++++++ .../unit/woopay/test-class-woopay-session.php | 2 ++ .../woopay/test-class-woopay-utilities.php | 1 + 22 files changed, 150 insertions(+), 43 deletions(-) create mode 100644 changelog/dev-improve-unit-tests-handling-of-filters diff --git a/changelog/dev-improve-unit-tests-handling-of-filters b/changelog/dev-improve-unit-tests-handling-of-filters new file mode 100644 index 00000000000..3cd0d192370 --- /dev/null +++ b/changelog/dev-improve-unit-tests-handling-of-filters @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Improvements to PHP unit tests to make them less flaky when it comes to WP filters. + + diff --git a/tests/unit/core/service/test-class-wc-payments-customer-service-api.php b/tests/unit/core/service/test-class-wc-payments-customer-service-api.php index bb0d994ef88..e4d64b5b02e 100644 --- a/tests/unit/core/service/test-class-wc-payments-customer-service-api.php +++ b/tests/unit/core/service/test-class-wc-payments-customer-service-api.php @@ -74,11 +74,12 @@ public function set_up() { * Post-test teardown */ public function tear_down() { - parent::tear_down(); remove_filter( 'wc_payments_http', [ $this, 'replace_http_client' ] ); + + parent::tear_down(); } /** diff --git a/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php b/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php index 723ee4b729d..75fa54c540f 100644 --- a/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php +++ b/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php @@ -63,5 +63,7 @@ function () use ( $ip_country ) { ]; $this->assertSame( $order_hashes, $expected_hashed_array ); + + remove_all_filters( 'woocommerce_geolocate_ip' ); } } diff --git a/tests/unit/multi-currency/test-class-analytics.php b/tests/unit/multi-currency/test-class-analytics.php index c981373a584..00b46e2d7ef 100644 --- a/tests/unit/multi-currency/test-class-analytics.php +++ b/tests/unit/multi-currency/test-class-analytics.php @@ -67,12 +67,7 @@ public function set_up() { $this->add_mock_order_with_meta(); $this->set_is_admin( true ); $this->set_is_rest_request( true ); - add_filter( - 'woocommerce_is_rest_api_request', - function () { - return true; - } - ); + add_filter( 'woocommerce_is_rest_api_request', '__return_true' ); // Add manage_woocommerce capability to user. $cb = $this->create_can_manage_woocommerce_cap_override( true ); add_filter( 'user_has_cap', $cb ); @@ -98,8 +93,11 @@ function () { * Post-test tear down. */ public function tear_down() { - parent::tear_down(); $this->delete_mock_orders(); + + remove_filter( 'woocommerce_is_rest_api_request', '__return_true' ); + + parent::tear_down(); } /** @@ -273,6 +271,8 @@ public function test_filter_select_clauses_disable_filter() { $expected = [ 'Santa Claus', 'Mrs. Claus' ]; add_filter( 'wcpay_multi_currency_disable_filter_select_clauses', '__return_true' ); $this->assertEquals( $expected, $this->analytics->filter_select_clauses( $expected, 'orders_stats' ) ); + + remove_filter( 'wcpay_multi_currency_disable_filter_select_clauses', '__return_true' ); } public function test_filter_select_clauses_return_filter() { @@ -285,6 +285,8 @@ function ( $new_clauses ) use ( $clauses ) { } ); $this->assertEquals( $expected, $this->analytics->filter_select_clauses( $clauses, 'orders_stats' ) ); + + remove_all_filters( 'wcpay_multi_currency_filter_select_clauses' ); } public function test_filter_where_clauses_when_no_currency_provided() { @@ -417,6 +419,8 @@ public function test_filter_where_clauses_disable_filter() { $_GET['currency_is'] = [ 'USD' ]; $this->assertEquals( $expected, $this->analytics->filter_where_clauses( $expected ) ); + + remove_filter( 'wcpay_multi_currency_disable_filter_where_clauses', '__return_true' ); } /** @@ -461,6 +465,8 @@ public function test_filter_join_clauses_disable_filter() { $expected = [ 'Santa Claus', 'Mrs. Claus' ]; add_filter( 'wcpay_multi_currency_disable_filter_join_clauses', '__return_true' ); $this->assertEquals( $expected, $this->analytics->filter_join_clauses( $expected, 'orders_stats' ) ); + + remove_filter( 'wcpay_multi_currency_disable_filter_join_clauses', '__return_true' ); } public function test_filter_join_clauses_return_filter() { @@ -473,6 +479,8 @@ function ( $new_clauses ) use ( $clauses ) { } ); $this->assertEquals( $expected, $this->analytics->filter_join_clauses( $clauses, 'orders_stats' ) ); + + remove_all_filters( 'wcpay_multi_currency_filter_join_clauses' ); } /** @@ -513,6 +521,8 @@ public function test_filter_select_orders_clauses_disable_filter() { $expected = [ 'Santa Claus', 'Mrs. Claus' ]; add_filter( 'wcpay_multi_currency_disable_filter_select_orders_clauses', '__return_true' ); $this->assertEquals( $expected, $this->analytics->filter_select_orders_clauses( $expected ) ); + + remove_filter( 'wcpay_multi_currency_disable_filter_select_orders_clauses', '__return_true' ); } public function test_filter_select_orders_clauses_return_filter() { @@ -525,6 +535,8 @@ function ( $new_clauses ) use ( $clauses ) { } ); $this->assertEquals( $expected, $this->analytics->filter_select_orders_clauses( $clauses ) ); + + remove_all_filters( 'wcpay_multi_currency_filter_select_orders_clauses' ); } private function order_args_provider( $order_id, $parent_id, $num_items_sold, $total_sales, $tax_total, $shipping_total, $net_total ) { diff --git a/tests/unit/multi-currency/test-class-geolocation.php b/tests/unit/multi-currency/test-class-geolocation.php index d2836a79985..b66543dbf67 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -26,7 +26,7 @@ class WCPay_Multi_Currency_Geolocation_Tests extends WCPAY_UnitTestCase { private $geolocation; /** - * Pre-test setup + * Pre-test setup. */ public function set_up() { parent::set_up(); @@ -35,6 +35,18 @@ public function set_up() { $this->geolocation = new WCPay\MultiCurrency\Geolocation( $this->mock_localization_service ); } + /** + * Post-test cleanup. + * + * @return void + */ + public function tear_down() { + remove_all_filters( 'woocommerce_geolocate_ip' ); + remove_all_filters( 'woocommerce_customer_default_location' ); + + parent::tear_down(); + } + public function test_get_country_by_customer_location_returns_geolocation_country() { add_filter( 'woocommerce_geolocate_ip', diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php index 3ddaccb47ad..1175db11442 100644 --- a/tests/unit/test-class-compatibility-service.php +++ b/tests/unit/test-class-compatibility-service.php @@ -238,14 +238,13 @@ private function add_stylesheet_filter( $stylesheet = null ): void { 'stylesheet', function ( $theme ) use ( $stylesheet ) { return $stylesheet; - }, - 404 // 404 is used to be able to use remove_all_filters later. + } ); } // Removes all stylesheet/theme name filters. private function remove_stylesheet_filters(): void { - remove_all_filters( 'stylesheet', 404 ); + remove_all_filters( 'stylesheet' ); } /** @@ -262,14 +261,13 @@ private function add_option_active_plugins_filter( $plugins = null ): void { 'option_active_plugins', function ( $active_plugins ) use ( $plugins ) { return $plugins; - }, - 404 // 404 is used to be able to use remove_all_filters later. + } ); } // Removes all active plugin filters. private function remove_option_active_plugins_filters() { - remove_all_filters( 'option_active_plugins', [ $this, 'active_plugins_filter_return' ], 404 ); + remove_all_filters( 'option_active_plugins' ); } // Used to purposely delete the active_plugins option in WP. diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php index 0faa39f9b8f..771e3475288 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php @@ -217,9 +217,10 @@ public function set_up() { * @return void */ public function tear_down() { - parent::tear_down(); WC_Payments::set_gateway( $this->wcpay_gateway ); WC()->session->set( 'wc_notices', [] ); + + parent::tear_down(); } /** @@ -755,6 +756,9 @@ function () { // Assert: woocommerce_order_status_pending was not called. $this->assertFalse( $results['has_called_woocommerce_order_status_pending'] ); + + remove_all_actions( 'woocommerce_order_status_pending' ); + remove_all_filters( 'woocommerce_default_order_status' ); } /** diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 15754e06701..e14803b6812 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -3767,6 +3767,8 @@ public function test_should_use_new_process_determines_negative_wcpay_subscripti $this->expect_router_factor( Factor::WCPAY_SUBSCRIPTION_SIGNUP(), false ); $this->card_gateway->should_use_new_process( $order ); + + remove_filter( 'wcpay_is_wcpay_subscriptions_enabled', '__return_true' ); } public function test_new_process_payment() { diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index 7b26455c9a2..7627fe92102 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -1294,6 +1294,8 @@ public function test_maybe_redirect_after_plugin_activation_stripe_disconnected_ $this->assertFalse( WC_Payments_Account::is_on_boarding_disabled() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_stripe_disconnected_and_onboarding_disabled_redirects() { @@ -1323,6 +1325,8 @@ public function test_maybe_redirect_after_plugin_activation_stripe_disconnected_ $this->assertTrue( WC_Payments_Account::is_on_boarding_disabled() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_account_error() { @@ -1349,6 +1353,8 @@ public function test_maybe_redirect_after_plugin_activation_account_error() { $this->assertTrue( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_account_valid() { @@ -1384,6 +1390,8 @@ public function test_maybe_redirect_after_plugin_activation_account_valid() { $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_with_non_admin_user() { @@ -1403,6 +1411,8 @@ public function test_maybe_redirect_after_plugin_activation_with_non_admin_user( $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should NOT be updated. $this->assertTrue( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_checks_the_account_once() { @@ -1440,6 +1450,8 @@ public function test_maybe_redirect_after_plugin_activation_checks_the_account_o $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_after_plugin_activation_returns_true_and_onboarding_re_enabled() { @@ -1496,6 +1508,8 @@ public function test_maybe_redirect_after_plugin_activation_returns_true_and_onb // Second call, on-boarding re-enabled. $this->wcpay_account->maybe_redirect_after_plugin_activation(); $this->assertFalse( WC_Payments_Account::is_on_boarding_disabled() ); + + remove_filter( 'user_has_cap', $cb ); } public function test_maybe_redirect_to_wcpay_connect_do_redirect() { diff --git a/tests/unit/test-class-wc-payments-customer-service.php b/tests/unit/test-class-wc-payments-customer-service.php index f29729cf6f4..54180bb22dc 100644 --- a/tests/unit/test-class-wc-payments-customer-service.php +++ b/tests/unit/test-class-wc-payments-customer-service.php @@ -543,6 +543,8 @@ function ( $fields ) { $order = WC_Helper_Order::create_order(); $this->customer_service->update_payment_method_with_billing_details_from_order( 'pm_mock', $order ); + + remove_all_filters( 'woocommerce_billing_fields' ); } public function test_get_payment_methods_for_customer_not_throw_resource_missing_code_exception() { diff --git a/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php b/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php index be7d18ac2a6..0444375d01d 100644 --- a/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php +++ b/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php @@ -200,6 +200,17 @@ function () { ); } + /** + * Clean up after each test. + * + * @return void + */ + public function tear_down() { + remove_all_filters( 'woocommerce_available_payment_gateways' ); + + parent::tear_down(); + } + /** * @return WC_Payment_Gateway_WCPay */ diff --git a/tests/unit/test-class-wc-payments-express-checkout-button-helper.php b/tests/unit/test-class-wc-payments-express-checkout-button-helper.php index 18528d4a4bc..4c388fd7404 100644 --- a/tests/unit/test-class-wc-payments-express-checkout-button-helper.php +++ b/tests/unit/test-class-wc-payments-express-checkout-button-helper.php @@ -133,7 +133,6 @@ public function set_up() { } public function tear_down() { - parent::tear_down(); WC_Subscriptions_Cart::set_cart_contains_subscription( false ); WC()->cart->empty_cart(); WC()->session->cleanup_sessions(); @@ -142,6 +141,8 @@ public function tear_down() { remove_filter( 'wc_tax_enabled', '__return_false' ); remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_excl' ] ); remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] ); + + parent::tear_down(); } public function __return_excl() { @@ -240,6 +241,8 @@ function () { $result = $this->mock_express_checkout_helper->get_total_label(); $this->assertEquals( 'Google Pay (via WooPayments)', $result ); + + remove_all_filters( 'wcpay_payment_request_total_label_suffix' ); } public function test_filter_cart_needs_shipping_address_returns_false() { diff --git a/tests/unit/test-class-wc-payments-features.php b/tests/unit/test-class-wc-payments-features.php index e9405375d8f..3f2e41c9a99 100644 --- a/tests/unit/test-class-wc-payments-features.php +++ b/tests/unit/test-class-wc-payments-features.php @@ -67,6 +67,7 @@ function ( $key ) { // Restore the cache service in the main class. WC_Payments::set_database_cache( $this->_cache ); + parent::tear_down(); } @@ -302,26 +303,14 @@ public function test_is_frt_review_feature_active_returns_false_when_flag_is_not } public function test_is_embedded_kyc_enabled_returns_true() { - add_filter( - 'pre_option_' . WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, - function ( $pre_option, $option, $default ) { - return '1'; - }, - 10, - 3 - ); + $this->set_feature_flag_option( WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, '1' ); + $this->assertTrue( WC_Payments_Features::is_embedded_kyc_enabled() ); } public function test_is_embedded_kyc_enabled_returns_false_when_flag_is_false() { - add_filter( - 'pre_option_' . WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, - function ( $pre_option, $option, $default ) { - return '0'; - }, - 10, - 3 - ); + $this->set_feature_flag_option( WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, '0' ); + $this->assertFalse( WC_Payments_Features::is_embedded_kyc_enabled() ); $this->assertArrayNotHasKey( 'isEmbeddedKycEnabled', WC_Payments_Features::to_array() ); } diff --git a/tests/unit/test-class-wc-payments-incentives-service.php b/tests/unit/test-class-wc-payments-incentives-service.php index f9166eb51ac..db61cbea6e2 100644 --- a/tests/unit/test-class-wc-payments-incentives-service.php +++ b/tests/unit/test-class-wc-payments-incentives-service.php @@ -36,18 +36,30 @@ public function set_up() { $this->incentives_service = new WC_Payments_Incentives_Service( $this->mock_database_cache ); $this->incentives_service->init_hooks(); + // Ensure the Payments menu is present. global $menu; // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited $menu = [ [ 'Payments', null, 'wc-admin&path=/payments/connect' ], ]; + + // Ensure no payment gateways are available. + add_filter( 'woocommerce_available_payment_gateways', '__return_empty_array' ); } + /** + * Clean up after each test. + * + * @return void + */ public function tear_down() { - parent::tear_down(); - global $menu; $menu = null; // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited + + remove_all_filters( 'pre_http_request' ); + remove_filter( 'woocommerce_available_payment_gateways', '__return_empty_array' ); + + parent::tear_down(); } public function test_filters_registered_properly() { @@ -127,6 +139,8 @@ function () { ); $this->assertNull( $this->incentives_service->get_cached_connect_incentive() ); + + remove_all_filters( 'woocommerce_countries_base_country' ); } public function test_get_cached_connect_incentive_cached_error() { diff --git a/tests/unit/test-class-wc-payments-localization-service.php b/tests/unit/test-class-wc-payments-localization-service.php index 32be4fc17fd..cdb59719be0 100644 --- a/tests/unit/test-class-wc-payments-localization-service.php +++ b/tests/unit/test-class-wc-payments-localization-service.php @@ -31,6 +31,8 @@ public function tear_down() { wp_set_current_user( 0 ); remove_all_filters( 'locale' ); remove_all_filters( 'wcpay_eur_format' ); + + parent::tear_down(); } public function test_get_currency_format_returns_default_format() { diff --git a/tests/unit/test-class-wc-payments-order-service.php b/tests/unit/test-class-wc-payments-order-service.php index 8686e53260d..d02681e42d9 100644 --- a/tests/unit/test-class-wc-payments-order-service.php +++ b/tests/unit/test-class-wc-payments-order-service.php @@ -1021,12 +1021,7 @@ function () { // Assert: Check that the order status was updated to processing status. $this->assertTrue( $this->order->has_status( [ Order_Status::PROCESSING ] ) ); - remove_filter( - 'wcpay_terminal_payment_completed_order_status', - function () { - return Order_Status::PROCESSING; - } - ); + remove_all_filters( 'wcpay_terminal_payment_completed_order_status' ); } /** diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index db1c564d44c..07df12a9885 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -162,7 +162,6 @@ function () { } public function tear_down() { - parent::tear_down(); WC_Subscriptions_Cart::set_cart_contains_subscription( false ); WC()->cart->empty_cart(); WC()->session->cleanup_sessions(); @@ -178,6 +177,9 @@ public function tear_down() { remove_filter( 'wc_tax_enabled', '__return_true' ); remove_filter( 'wc_tax_enabled', '__return_false' ); remove_filter( 'wc_shipping_enabled', '__return_false' ); + remove_all_filters( 'woocommerce_find_rates' ); + + parent::tear_down(); } public function __return_yes() { @@ -661,6 +663,8 @@ function () use ( $mock_product ) { // Restore the sign-up fee after the test. WC_Subscriptions_Product::set_sign_up_fee( 0 ); + + remove_all_filters( 'test_deposit_get_product' ); } public function test_get_product_price_includes_variable_subscription_sign_up_fee() { @@ -679,6 +683,8 @@ function () use ( $mock_product ) { // Restore the sign-up fee after the test. WC_Subscriptions_Product::set_sign_up_fee( 0 ); + + remove_all_filters( 'test_deposit_get_product' ); } public function test_get_product_price_throws_exception_for_products_without_prices() { @@ -709,6 +715,8 @@ function () use ( $mock_product ) { // Restore the sign-up fee after the test. WC_Subscriptions_Product::set_sign_up_fee( 0 ); + + remove_all_filters( 'test_deposit_get_product' ); } private function create_mock_subscription( $type ) { diff --git a/tests/unit/test-class-wc-payments-session-service.php b/tests/unit/test-class-wc-payments-session-service.php index 4e9127a7919..b31c5c54136 100644 --- a/tests/unit/test-class-wc-payments-session-service.php +++ b/tests/unit/test-class-wc-payments-session-service.php @@ -53,6 +53,11 @@ public function setUp(): void { add_filter( 'pre_option_' . WC_Payments_Session_Service::SESSION_STORE_ID_OPTION, [ $this, 'mock_session_store_id' ] ); } + /** + * Clean up after each test. + * + * @return void + */ public function tear_down() { parent::tear_down(); diff --git a/tests/unit/test-class-wc-payments-woopay-button-handler.php b/tests/unit/test-class-wc-payments-woopay-button-handler.php index 30cd8325e0c..746552f783e 100644 --- a/tests/unit/test-class-wc-payments-woopay-button-handler.php +++ b/tests/unit/test-class-wc-payments-woopay-button-handler.php @@ -135,9 +135,12 @@ function () { } public function tear_down() { - parent::tear_down(); WC()->cart->empty_cart(); WC()->session->cleanup_sessions(); + + remove_all_filters( 'woocommerce_available_payment_gateways' ); + + parent::tear_down(); } /** @@ -238,6 +241,8 @@ public function test_should_show_woopay_button_all_good_at_checkout() { ->willReturn( true ); $this->assertTrue( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'wcpay_platform_checkout_button_are_cart_items_supported', '__return_true' ); } public function test_should_only_load_common_config_script() { @@ -334,6 +339,8 @@ public function test_should_show_woopay_button_unsupported_product_at_checkout() ->willReturn( true ); $this->assertFalse( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'wcpay_platform_checkout_button_are_cart_items_supported', '__return_false' ); } public function test_should_show_woopay_button_all_good_at_product() { @@ -359,6 +366,8 @@ public function test_should_show_woopay_button_all_good_at_product() { ->willReturn( true ); $this->assertTrue( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'wcpay_woopay_button_is_product_supported', '__return_true' ); } public function test_should_show_woopay_button_unsupported_product_at_product() { @@ -384,6 +393,8 @@ public function test_should_show_woopay_button_unsupported_product_at_product() ->willReturn( true ); $this->assertFalse( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'wcpay_woopay_button_is_product_supported', '__return_false' ); } public function test_should_show_woopay_button_not_available_at_product() { @@ -409,6 +420,8 @@ public function test_should_show_woopay_button_not_available_at_product() { ->willReturn( false ); $this->assertFalse( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'wcpay_woopay_button_is_product_supported', '__return_true' ); } public function test_should_show_woopay_button_page_not_supported() { @@ -476,6 +489,8 @@ public function test_should_show_woopay_button_unavailable_wcpay() { ->method( 'is_product' ); $this->assertFalse( $this->mock_pr->should_show_woopay_button() ); + + remove_filter( 'woocommerce_available_payment_gateways', '__return_empty_array' ); } public function test_should_show_woopay_button_woopay_not_enabled() { diff --git a/tests/unit/woopay/class-woopay-scheduler-test.php b/tests/unit/woopay/class-woopay-scheduler-test.php index 0fa6db45784..c14e40a7e76 100644 --- a/tests/unit/woopay/class-woopay-scheduler-test.php +++ b/tests/unit/woopay/class-woopay-scheduler-test.php @@ -57,6 +57,8 @@ public function test_disable_woopay_when_incompatible_extension_active() { $this->scheduler->update_compatibility_and_maybe_show_incompatibility_warning(); $this->assertTrue( get_option( WooPay_Scheduler::INVALID_EXTENSIONS_FOUND_OPTION_NAME, null ) ); + + remove_filter( 'pre_option_active_plugins', $active_plugins_mock ); } /** @@ -82,6 +84,8 @@ public function test_disable_woopay_when_no_incompatible_extension_active() { $this->scheduler->update_compatibility_and_maybe_show_incompatibility_warning(); $this->assertNull( get_option( WooPay_Scheduler::INVALID_EXTENSIONS_FOUND_OPTION_NAME, null ) ); + + remove_filter( 'pre_option_active_plugins', $active_plugins_mock ); } /** @@ -127,6 +131,8 @@ public function test_will_stop_showing_warning_when_incompatible_extension_is_re $this->scheduler->hide_warning_when_incompatible_extension_is_disabled( 'test-extension/test-extension.php' ); $this->assertNull( get_option( WooPay_Scheduler::INVALID_EXTENSIONS_FOUND_OPTION_NAME, null ) ); + + remove_filter( 'pre_option_active_plugins', $active_plugins_mock ); } /** @@ -154,6 +160,8 @@ public function test_will_keep_showing_warning_when_only_one_incompatible_extens $this->scheduler->hide_warning_when_incompatible_extension_is_disabled( 'test-extension/test-extension.php' ); $this->assertTrue( get_option( WooPay_Scheduler::INVALID_EXTENSIONS_FOUND_OPTION_NAME, null ) ); + + remove_filter( 'pre_option_active_plugins', $active_plugins_mock ); } /** @@ -218,6 +226,8 @@ public function test_update_adapted_extensions_and_available_countries_list() { $this->assertEquals( get_option( WooPay_Scheduler::ADAPTED_EXTENSIONS_LIST_OPTION_NAME ), $adapted_extensions ); $this->assertEquals( get_option( WooPay_Utilities::AVAILABLE_COUNTRIES_OPTION_NAME ), '["US","BR"]' ); $this->assertEquals( get_option( WooPay_Scheduler::ENABLED_ADAPTED_EXTENSIONS_OPTION_NAME, [] ), [ 'test-extension' ] ); + + remove_filter( 'pre_option_active_plugins', $active_plugins_mock ); } /** diff --git a/tests/unit/woopay/test-class-woopay-session.php b/tests/unit/woopay/test-class-woopay-session.php index 5368cbd9878..b6227592a36 100644 --- a/tests/unit/woopay/test-class-woopay-session.php +++ b/tests/unit/woopay/test-class-woopay-session.php @@ -62,6 +62,8 @@ public function tear_down() { wp_set_current_user( 0 ); + remove_filter( 'wcpay_woopay_is_signed_with_blog_token', '__return_true' ); + parent::tear_down(); } diff --git a/tests/unit/woopay/test-class-woopay-utilities.php b/tests/unit/woopay/test-class-woopay-utilities.php index 72f7d30e480..df8a0533f26 100644 --- a/tests/unit/woopay/test-class-woopay-utilities.php +++ b/tests/unit/woopay/test-class-woopay-utilities.php @@ -24,6 +24,7 @@ public function set_up() { public function tear_down() { // Restore the cache service in the main class. WC_Payments::set_database_cache( $this->_cache ); + parent::tear_down(); } From d4b6ffcd97a775e0da2ed17f6064fa61d1640435 Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Thu, 12 Sep 2024 08:29:37 +0300 Subject: [PATCH 06/40] Add rate limiter to compatibility data updates (#9412) --- ...erver-6156-compatibility-data-rate-limiter | 4 ++ includes/class-compatibility-service.php | 21 +++++-- ...s-wc-payments-action-scheduler-service.php | 17 +++++- includes/class-wc-payments.php | 4 +- ...s-wc-rest-payments-settings-controller.php | 4 +- ...-class-wc-rest-payments-tos-controller.php | 4 +- .../unit/test-class-compatibility-service.php | 56 +++++++++++++++---- ...s-wc-payments-action-scheduler-service.php | 19 ++++++- 8 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 changelog/add-server-6156-compatibility-data-rate-limiter diff --git a/changelog/add-server-6156-compatibility-data-rate-limiter b/changelog/add-server-6156-compatibility-data-rate-limiter new file mode 100644 index 00000000000..aec4ceb577a --- /dev/null +++ b/changelog/add-server-6156-compatibility-data-rate-limiter @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add rate limiter to compatibility data updates diff --git a/includes/class-compatibility-service.php b/includes/class-compatibility-service.php index 6a66941a193..a460471f800 100644 --- a/includes/class-compatibility-service.php +++ b/includes/class-compatibility-service.php @@ -7,6 +7,7 @@ namespace WCPay; +use WC_Payments; use WC_Payments_API_Client; use WCPay\Exceptions\API_Exception; @@ -16,6 +17,8 @@ * Class to send compatibility data to the server. */ class Compatibility_Service { + const UPDATE_COMPATIBILITY_DATA = 'wcpay_update_compatibility_data'; + /** * Client for making requests to the WooCommerce Payments API * @@ -44,16 +47,22 @@ public function init_hooks() { } /** - * Gets the data we need to confirm compatibility and sends it to the server. + * Schedules the sending of the compatibility data to send only the last update in T minutes. * * @return void */ public function update_compatibility_data() { - try { - $this->payments_api_client->update_compatibility_data( $this->get_compatibility_data() ); - } catch ( API_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch - // The exception is already logged if logging is on, nothing else needed. - } + // This will delete the previous compatibility requests in the last two minutes, and only send the last update to the server, ensuring there's only one update in two minutes. + WC_Payments::get_action_scheduler_service()->schedule_job( time() + 2 * MINUTE_IN_SECONDS, self::UPDATE_COMPATIBILITY_DATA ); + } + + /** + * Gets the data we need to confirm compatibility and sends it to the server. + * + * @return void + */ + public function update_compatibility_data_hook() { + $this->payments_api_client->update_compatibility_data( $this->get_compatibility_data() ); } /** diff --git a/includes/class-wc-payments-action-scheduler-service.php b/includes/class-wc-payments-action-scheduler-service.php index d66677834c6..f9057870e44 100644 --- a/includes/class-wc-payments-action-scheduler-service.php +++ b/includes/class-wc-payments-action-scheduler-service.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments */ +use WCPay\Compatibility_Service; use WCPay\Constants\Order_Mode; defined( 'ABSPATH' ) || exit; @@ -30,19 +31,28 @@ class WC_Payments_Action_Scheduler_Service { */ private $order_service; + /** + * Compatibility service instance for updating compatibility data. + * + * @var Compatibility_Service + */ + private $compatibility_service; /** * Constructor for WC_Payments_Action_Scheduler_Service. * * @param WC_Payments_API_Client $payments_api_client - WooCommerce Payments API client. * @param WC_Payments_Order_Service $order_service - Order Service. + * @param Compatibility_Service $compatibility_service - Compatibility service instance. */ public function __construct( WC_Payments_API_Client $payments_api_client, - WC_Payments_Order_Service $order_service + WC_Payments_Order_Service $order_service, + Compatibility_Service $compatibility_service ) { - $this->payments_api_client = $payments_api_client; - $this->order_service = $order_service; + $this->payments_api_client = $payments_api_client; + $this->order_service = $order_service; + $this->compatibility_service = $compatibility_service; $this->add_action_scheduler_hooks(); } @@ -56,6 +66,7 @@ public function add_action_scheduler_hooks() { add_action( 'wcpay_track_new_order', [ $this, 'track_new_order_action' ] ); add_action( 'wcpay_track_update_order', [ $this, 'track_update_order_action' ] ); add_action( WC_Payments_Order_Service::ADD_FEE_BREAKDOWN_TO_ORDER_NOTES, [ $this->order_service, 'add_fee_breakdown_to_order_notes' ], 10, 3 ); + add_action( Compatibility_Service::UPDATE_COMPATIBILITY_DATA, [ $this->compatibility_service, 'update_compatibility_data_hook' ], 10, 0 ); } /** diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index cd8854afa93..ac6aca75c50 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -502,7 +502,8 @@ public static function init() { include_once WCPAY_ABSPATH . 'includes/class-woopay-tracker.php'; self::$order_service = new WC_Payments_Order_Service( self::$api_client ); - self::$action_scheduler_service = new WC_Payments_Action_Scheduler_Service( self::$api_client, self::$order_service ); + self::$compatibility_service = new Compatibility_Service( self::$api_client ); + self::$action_scheduler_service = new WC_Payments_Action_Scheduler_Service( self::$api_client, self::$order_service, self::$compatibility_service ); self::$session_service = new WC_Payments_Session_Service( self::$api_client ); self::$redirect_service = new WC_Payments_Redirect_Service( self::$api_client ); self::$onboarding_service = new WC_Payments_Onboarding_Service( self::$api_client, self::$database_cache, self::$session_service ); @@ -519,7 +520,6 @@ public static function init() { self::$woopay_tracker = new WooPay_Tracker( self::get_wc_payments_http() ); self::$incentives_service = new WC_Payments_Incentives_Service( self::$database_cache ); self::$duplicate_payment_prevention_service = new Duplicate_Payment_Prevention_Service(); - self::$compatibility_service = new Compatibility_Service( self::$api_client ); self::$duplicates_detection_service = new Duplicates_Detection_Service(); ( new WooPay_Scheduler( self::$api_client ) )->init(); diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index 84c06290b80..d84f4b680ba 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -8,6 +8,7 @@ use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\RestApi; use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Compatibility_Service; use WCPay\Constants\Country_Code; use WCPay\Constants\Payment_Method; use WCPay\Database_Cache; @@ -126,7 +127,8 @@ public function set_up() { $order_service = new WC_Payments_Order_Service( $this->mock_api_client ); $customer_service = new WC_Payments_Customer_Service( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_db_cache, $this->mock_session_service, $order_service ); $token_service = new WC_Payments_Token_Service( $this->mock_api_client, $customer_service ); - $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $this->mock_api_client, $order_service ); + $compatibility_service = new Compatibility_Service( $this->mock_api_client ); + $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $this->mock_api_client, $order_service, $compatibility_service ); $mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); diff --git a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php index 804ca19117f..20e4cf57169 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Compatibility_Service; use WCPay\Core\Server\Request\Add_Account_Tos_Agreement; use WCPay\Database_Cache; use WCPay\Duplicate_Payment_Prevention_Service; @@ -63,7 +64,8 @@ public function set_up() { $order_service = new WC_Payments_Order_Service( $this->createMock( WC_Payments_API_Client::class ) ); $customer_service = new WC_Payments_Customer_Service( $mock_api_client, $mock_wcpay_account, $mock_db_cache, $mock_session_service, $order_service ); $token_service = new WC_Payments_Token_Service( $mock_api_client, $customer_service ); - $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $mock_api_client, $order_service ); + $mock_compatibility_service = $this->createMock( Compatibility_Service::class ); + $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $mock_api_client, $order_service, $mock_compatibility_service ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); $mock_payment_method = $this->createMock( CC_Payment_Method::class ); $mock_duplicates_detection_service = $this->createMock( Duplicates_Detection_Service::class ); diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php index 1175db11442..18ac477dd8b 100644 --- a/tests/unit/test-class-compatibility-service.php +++ b/tests/unit/test-class-compatibility-service.php @@ -120,21 +120,53 @@ public function provider_test_registers_woocommerce_filters_properly(): array { ]; } - public function test_update_compatibility_data() { - // Arrange: Create the expected value to be passed to update_compatibility_data. - $expected = $this->get_mock_compatibility_data(); + public function test_update_compatibility_data_adds_scheduled_job() { + // Arrange: Clear all previously scheduled compatibility update jobs. + as_unschedule_all_actions( Compatibility_Service::UPDATE_COMPATIBILITY_DATA ); - // Arrange/Assert: Set the expectations for update_compatibility_data. - $this->mock_api_client - ->expects( $this->once() ) - ->method( 'update_compatibility_data' ) - ->with( $expected ); + // Act: Call the method we're testing. + $this->compatibility_service->update_compatibility_data(); + + // Assert: Test the scheduled actions. + $actions = as_get_scheduled_actions( + [ + 'hook' => Compatibility_Service::UPDATE_COMPATIBILITY_DATA, + 'status' => ActionScheduler_Store::STATUS_PENDING, + 'group' => WC_Payments_Action_Scheduler_Service::GROUP_ID, + ] + ); + + $this->assertCount( 1, $actions ); + $action = array_pop( $actions ); + $this->assertInstanceOf( ActionScheduler_Action::class, $action ); + } + + public function test_update_compatibility_data_adds_a_single_scheduled_job() { + + // Arrange: Clear all previously scheduled compatibility update jobs. + as_unschedule_all_actions( Compatibility_Service::UPDATE_COMPATIBILITY_DATA ); // Act: Call the method we're testing. $this->compatibility_service->update_compatibility_data(); + $this->compatibility_service->update_compatibility_data(); + $this->compatibility_service->update_compatibility_data(); + $this->compatibility_service->update_compatibility_data(); + + // Assert: Test the scheduled actions. + $actions = as_get_scheduled_actions( + [ + 'hook' => Compatibility_Service::UPDATE_COMPATIBILITY_DATA, + 'status' => ActionScheduler_Store::STATUS_PENDING, + 'group' => WC_Payments_Action_Scheduler_Service::GROUP_ID, + ] + ); + + $this->assertCount( 1, $actions ); + $action = array_pop( $actions ); + $this->assertInstanceOf( ActionScheduler_Action::class, $action ); } - public function test_update_compatibility_data_active_plugins_false() { + public function test_update_compatibility_data_hook_active_plugins_false() { // Arrange: Create the expected value to be passed to update_compatibility_data. $expected = $this->get_mock_compatibility_data( [ @@ -152,7 +184,7 @@ public function test_update_compatibility_data_active_plugins_false() { ->with( $expected ); // Act: Call the method we're testing. - $this->compatibility_service->update_compatibility_data(); + $this->compatibility_service->update_compatibility_data_hook(); // Arrange: Fix the broke active_plugins option in WP. $this->fix_active_plugins_option(); @@ -163,7 +195,7 @@ public function test_update_compatibility_data_active_plugins_false() { * * @dataProvider provider_update_compatibility_data_permalinks_not_set */ - public function test_update_compatibility_data_permalinks_not_set( $page_name ) { + public function test_update_compatibility_data_hook_permalinks_not_set( $page_name ) { // Arrange: Create the expected value to be passed to update_compatibility_data. $expected = $this->get_mock_compatibility_data( [ @@ -181,7 +213,7 @@ public function test_update_compatibility_data_permalinks_not_set( $page_name ) ->with( $expected ); // Act: Call the method we're testing. - $this->compatibility_service->update_compatibility_data(); + $this->compatibility_service->update_compatibility_data_hook(); } public function provider_update_compatibility_data_permalinks_not_set(): array { diff --git a/tests/unit/test-class-wc-payments-action-scheduler-service.php b/tests/unit/test-class-wc-payments-action-scheduler-service.php index 69785347f19..4d92f745799 100644 --- a/tests/unit/test-class-wc-payments-action-scheduler-service.php +++ b/tests/unit/test-class-wc-payments-action-scheduler-service.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Compatibility_Service; use WCPay\Exceptions\API_Exception; /** @@ -33,16 +34,28 @@ class WC_Payments_Action_Scheduler_Service_Test extends WCPAY_UnitTestCase { */ private $mock_order_service; + /** + * Mock Compatibility_Service. + * + * @var Compatibility_Service|MockObject + */ + private $mock_compatibility_service; + /** * Pre-test setup */ public function set_up() { parent::set_up(); - $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); - $this->mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); + $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + $this->mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); + $this->mock_compatibility_service = $this->createMock( Compatibility_Service::class ); + + $this->action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $this->mock_api_client, $this->mock_order_service, $this->mock_compatibility_service ); + } - $this->action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $this->mock_api_client, $this->mock_order_service ); + public function test_update_compatibility_data_hook_registered() { + $this->assertEquals( 10, has_action( Compatibility_Service::UPDATE_COMPATIBILITY_DATA, [ $this->mock_compatibility_service, 'update_compatibility_data_hook' ] ) ); } public function test_track_new_order_action() { From 6631488afd9a59d27f8705d09ea92b5261d52f12 Mon Sep 17 00:00:00 2001 From: Daniel Mallory Date: Thu, 12 Sep 2024 15:00:51 +0100 Subject: [PATCH 07/40] Tidying up and improving the React logic for Stripe components (#9425) --- changelog/dev-session-fixes | 4 + client/onboarding/steps/embedded-kyc.tsx | 254 ++++++++---------- client/onboarding/steps/loading.tsx | 4 +- client/onboarding/types.ts | 16 +- client/onboarding/utils.ts | 95 ++++++- ...wc-rest-payments-onboarding-controller.php | 10 +- 6 files changed, 222 insertions(+), 161 deletions(-) create mode 100644 changelog/dev-session-fixes diff --git a/changelog/dev-session-fixes b/changelog/dev-session-fixes new file mode 100644 index 00000000000..aa73f7a216c --- /dev/null +++ b/changelog/dev-session-fixes @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Some refactors to embedded KYC logic. diff --git a/client/onboarding/steps/embedded-kyc.tsx b/client/onboarding/steps/embedded-kyc.tsx index 68981261efd..dd41c11b335 100644 --- a/client/onboarding/steps/embedded-kyc.tsx +++ b/client/onboarding/steps/embedded-kyc.tsx @@ -1,51 +1,40 @@ /** * External dependencies */ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { loadConnectAndInitialize, StripeConnectInstance, } from '@stripe/connect-js'; -import { LoadError } from '@stripe/connect-js/types/config'; import { ConnectAccountOnboarding, ConnectComponentsProvider, } from '@stripe/react-connect-js'; -import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; -import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { NAMESPACE } from 'data/constants'; import appearance from '../kyc/appearance'; import BannerNotice from 'wcpay/components/banner-notice'; import LoadBar from 'wcpay/components/load-bar'; import { useOnboardingContext } from 'wcpay/onboarding/context'; import { - AccountKycSession, - PoEligibleData, - PoEligibleResult, -} from 'wcpay/onboarding/types'; -import { fromDotNotation } from 'wcpay/onboarding/utils'; + createAccountSession, + finalizeOnboarding, + isPoEligible, +} from 'wcpay/onboarding/utils'; import { getConnectUrl, getOverviewUrl } from 'wcpay/utils'; -type AccountKycSessionData = AccountKycSession; - -interface FinalizeResponse { - success: boolean; - params: Record< string, string >; -} - interface Props { continueKyc?: boolean; } +// TODO: extract this logic and move it to a generic component to be used for all embedded components, not just onboarding. const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { const { data } = useOnboardingContext(); - const [ publishableKey, setPublishableKey ] = useState( '' ); const [ locale, setLocale ] = useState( '' ); + const [ publishableKey, setPublishableKey ] = useState( '' ); const [ clientSecret, setClientSecret ] = useState< ( () => Promise< string > ) | null >( null ); @@ -55,101 +44,81 @@ const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { ] = useState< StripeConnectInstance | null >( null ); const [ loading, setLoading ] = useState( true ); const [ loadErrorMessage, setLoadErrorMessage ] = useState( '' ); - const onLoaderStart = () => { - setLoading( false ); - }; - const onLoadError = ( loadError: LoadError ) => { - setLoadErrorMessage( loadError.error.message || 'Unknown error' ); - }; - useEffect( () => { - const isEligibleForPo = async () => { - if ( - ! data.country || - ! data.business_type || - ! data.mcc || - ! data.annual_revenue || - ! data.go_live_timeframe - ) { - return false; + const fetchAccountSession = useCallback( async () => { + try { + const isEligible = ! continueKyc && ( await isPoEligible( data ) ); + const accountSession = await createAccountSession( + data, + isEligible + ); + if ( accountSession && accountSession.clientSecret ) { + return accountSession; // Return the full account session object } - const eligibilityDetails: PoEligibleData = { - business: { - country: data.country, - type: data.business_type, - mcc: data.mcc, - }, - store: { - annual_revenue: data.annual_revenue, - go_live_timeframe: data.go_live_timeframe, - }, - }; - try { - const eligibleResult = await apiFetch< PoEligibleResult >( { - path: '/wc/v3/payments/onboarding/router/po_eligible', - method: 'POST', - data: eligibilityDetails, - } ); - - return 'eligible' === eligibleResult.result; - } catch ( error ) { - // Fall back to full KYC scenario. - return false; - } - }; + setLoading( false ); + setLoadErrorMessage( + __( + "Failed to create account session. Please check that you're using the latest version of WooPayments.", + 'woocommerce-payments' + ) + ); + } catch ( error ) { + setLoading( false ); + setLoadErrorMessage( + __( + 'Failed to retrieve account session. Please try again later.', + 'woocommerce-payments' + ) + ); + } - const fetchKeys = async () => { - // By default, we assume the merchant is not eligible for PO. - let isEligible = false; + // Return null if an error occurred. + return null; + }, [ continueKyc, data ] ); - // If we are resuming an onboarding session, we don't need to check for PO eligibility again. - if ( ! continueKyc ) { - isEligible = await isEligibleForPo(); - } + // Function to fetch clientSecret for use in Stripe auto-refresh or initialization + const fetchClientSecret = useCallback( async () => { + const accountSession = await fetchAccountSession(); + if ( accountSession ) { + return accountSession.clientSecret; // Only return the clientSecret + } + throw new Error( 'Error fetching the client secret' ); + }, [ fetchAccountSession ] ); - const path = addQueryArgs( - `${ NAMESPACE }/onboarding/kyc/session`, - { - self_assessment: fromDotNotation( data ), - progressive: isEligible, + // Effect to fetch the publishable key and clientSecret on initial render + useEffect( () => { + const fetchKeys = async () => { + try { + const accountSession = await fetchAccountSession(); + if ( accountSession ) { + setLocale( accountSession.locale ); + setPublishableKey( accountSession.publishableKey ); + setClientSecret( () => fetchClientSecret ); } - ); - const accountSession = await apiFetch< AccountKycSessionData >( { - path: path, - method: 'GET', - } ); - if ( - accountSession.publishableKey && - accountSession.clientSecret - ) { - setPublishableKey( accountSession.publishableKey ); - setLocale( accountSession.locale ); - setClientSecret( () => () => - Promise.resolve( accountSession.clientSecret ) - ); // Ensure clientSecret is wrapped as a function returning a Promise - } else { + } catch ( error ) { setLoading( false ); setLoadErrorMessage( __( - "Failed to create account session. Please check that you're using the latest version of WooPayments.", + 'Failed to create account session. Please check that you are using the latest version of WooPayments.', 'woocommerce-payments' ) ); + } finally { + setLoading( false ); } }; fetchKeys(); - }, [ data, continueKyc ] ); + }, [ data, continueKyc, fetchAccountSession, fetchClientSecret ] ); - // Initialize the Stripe Connect instance only once when publishableKey and clientSecret are ready + // Effect to initialize the Stripe Connect instance once publishableKey and clientSecret are ready. useEffect( () => { if ( publishableKey && clientSecret && ! stripeConnectInstance ) { const stripeInstance = loadConnectAndInitialize( { - publishableKey: publishableKey, - fetchClientSecret: clientSecret, // Pass the function returning the Promise + publishableKey, + fetchClientSecret, appearance: { - // See all possible variables below overlays: 'drawer', variables: appearance.variables, }, @@ -158,7 +127,48 @@ const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { setStripeConnectInstance( stripeInstance ); } - }, [ publishableKey, clientSecret, stripeConnectInstance, locale ] ); + }, [ + publishableKey, + clientSecret, + stripeConnectInstance, + fetchClientSecret, + locale, + ] ); + + const handleOnExit = async () => { + const urlParams = new URLSearchParams( window.location.search ); + const urlSource = + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown'; + + try { + const response = await finalizeOnboarding( urlSource ); + if ( response.success ) { + window.location.href = getOverviewUrl( + { + ...response.params, + 'wcpay-connection-success': '1', + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } else { + window.location.href = getConnectUrl( + { + ...response.params, + 'wcpay-connection-error': '1', + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } + } catch ( error ) { + window.location.href = getConnectUrl( + { + 'wcpay-connection-error': '1', + source: urlSource, + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } + }; return ( <> @@ -171,59 +181,13 @@ const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { connectInstance={ stripeConnectInstance } > { - const urlParams = new URLSearchParams( - window.location.search - ); - const urlSource = - urlParams - .get( 'source' ) - ?.replace( /[^\w-]+/g, '' ) || 'unknown'; - try { - const response = await apiFetch< - FinalizeResponse - >( { - path: `${ NAMESPACE }/onboarding/kyc/finalize`, - method: 'POST', - data: { - source: urlSource, - from: 'WCPAY_ONBOARDING_WIZARD', - clientSecret: clientSecret, - }, - } ); - - if ( response.success ) { - window.location.href = getOverviewUrl( - { - ...response.params, - 'wcpay-connection-success': '1', - }, - 'WCPAY_ONBOARDING_WIZARD' - ); - } else { - // If a non-success response is received we should redirect to the Connect page with an error flag: - window.location.href = getConnectUrl( - { - ...response.params, - 'wcpay-connection-error': '1', - }, - 'WCPAY_ONBOARDING_WIZARD' - ); - } - } catch ( error ) { - // If an error response is received we should redirect to the Connect page with an error flag: - // Note that this should never happen, since we always expect a response from the server. - window.location.href = getConnectUrl( - { - 'wcpay-connection-error': '1', - source: urlSource, - }, - 'WCPAY_ONBOARDING_WIZARD' - ); - } - } } + onLoaderStart={ () => setLoading( false ) } + onLoadError={ ( loadError ) => + setLoadErrorMessage( + loadError.error.message || 'Unknown error' + ) + } + onExit={ handleOnExit } /> ) } diff --git a/client/onboarding/steps/loading.tsx b/client/onboarding/steps/loading.tsx index c7b08dc905b..876591205e1 100644 --- a/client/onboarding/steps/loading.tsx +++ b/client/onboarding/steps/loading.tsx @@ -9,7 +9,7 @@ import apiFetch from '@wordpress/api-fetch'; * Internal dependencies */ import { useOnboardingContext } from '../context'; -import { PoEligibleData, PoEligibleResult } from '../types'; +import { PoEligibleData, PoEligibleResponse } from '../types'; import { fromDotNotation } from '../utils'; import { trackRedirected, useTrackAbandoned } from '../tracking'; import LoadBar from 'components/load-bar'; @@ -45,7 +45,7 @@ const LoadingStep: React.FC< Props > = () => { go_live_timeframe: data.go_live_timeframe, }, }; - const eligibleResult = await apiFetch< PoEligibleResult >( { + const eligibleResult = await apiFetch< PoEligibleResponse >( { path: '/wc/v3/payments/onboarding/router/po_eligible', method: 'POST', data: eligibilityDetails, diff --git a/client/onboarding/types.ts b/client/onboarding/types.ts index c2781390524..9a07a3063b3 100644 --- a/client/onboarding/types.ts +++ b/client/onboarding/types.ts @@ -13,16 +13,7 @@ export type OnboardingFields = { go_live_timeframe?: string; }; -export interface OnboardingProps { - country?: string; - type?: string; - structure?: string; - mcc?: string; - annual_revenue?: string; - go_live_timeframe?: string; -} - -export interface PoEligibleResult { +export interface PoEligibleResponse { result: 'eligible' | 'not_eligible'; } @@ -74,3 +65,8 @@ export interface AccountKycSession { publishableKey: string; locale: string; } + +export interface FinalizeOnboardingResponse { + success: boolean; + params: Record< string, string >; +} diff --git a/client/onboarding/utils.ts b/client/onboarding/utils.ts index a427f3c3a93..efab0971875 100644 --- a/client/onboarding/utils.ts +++ b/client/onboarding/utils.ts @@ -2,13 +2,23 @@ * External dependencies */ import { set, toPairs } from 'lodash'; +import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ +import { NAMESPACE } from 'data/constants'; import { ListItem } from 'components/grouped-select-control'; import businessTypeDescriptionStrings from './translations/descriptions'; -import { Country } from './types'; +import { + AccountKycSession, + Country, + OnboardingFields, + PoEligibleData, + PoEligibleResponse, + FinalizeOnboardingResponse, +} from './types'; +import { addQueryArgs } from '@wordpress/url'; export const fromDotNotation = ( record: Record< string, unknown > @@ -17,6 +27,9 @@ export const fromDotNotation = ( return value != null ? set( result, key, value ) : result; }, {} ); +const hasUndefinedValues = ( obj: Record< string, any > ): boolean => + Object.values( obj ).some( ( value ) => value === undefined ); + export const getAvailableCountries = (): Country[] => Object.entries( wcpaySettings?.connect.availableCountries || [] ) .map( ( [ key, name ] ) => ( { key, name, types: [] } ) ) @@ -42,6 +55,86 @@ export const getBusinessTypes = (): Country[] => { ); }; +/** + * Make an API request to create an account session. + * + * @param data The form data. + * @param isPoEligible Whether the user is eligible for a PO account. + * @param collectPayoutRequirements Whether to collect payout requirements. + */ +export const createAccountSession = async ( + data: OnboardingFields, + isPoEligible: boolean, + collectPayoutRequirements = false +): Promise< AccountKycSession > => { + return await apiFetch< AccountKycSession >( { + path: addQueryArgs( `${ NAMESPACE }/onboarding/kyc/session`, { + self_assessment: fromDotNotation( data ), + progressive: isPoEligible, + collect_payout_requirements: collectPayoutRequirements, + } ), + method: 'GET', + } ); +}; + +/** + * Make an API request to finalize the onboarding process. + * + * @param urlSource The source URL. + */ +export const finalizeOnboarding = async ( urlSource: string ) => { + return await apiFetch< FinalizeOnboardingResponse >( { + path: `${ NAMESPACE }/onboarding/kyc/finalize`, + method: 'POST', + data: { + source: urlSource, + from: 'WCPAY_ONBOARDING_WIZARD', + }, + } ); +}; + +/** + * Make an API request to determine if the user is eligible for a PO account. + * + * @param onboardingFields The form data, used to determine eligibility. + */ +export const isPoEligible = async ( + onboardingFields: OnboardingFields +): Promise< boolean > => { + // Check if any required property is undefined + if ( + hasUndefinedValues( { + country: onboardingFields.country, + business_type: onboardingFields.business_type, + mcc: onboardingFields.mcc, + annual_revenue: onboardingFields.annual_revenue, + go_live_timeframe: onboardingFields.go_live_timeframe, + } ) + ) { + return false; + } + + const eligibilityData: PoEligibleData = { + business: { + country: onboardingFields.country as string, + type: onboardingFields.business_type as string, + mcc: onboardingFields.mcc as string, + }, + store: { + annual_revenue: onboardingFields.annual_revenue as string, + go_live_timeframe: onboardingFields.go_live_timeframe as string, + }, + }; + + const response: PoEligibleResponse = await apiFetch( { + path: `${ NAMESPACE }/onboarding/router/po_eligible`, + method: 'POST', + data: eligibilityData, + } ); + + return response.result === 'eligible'; +}; + /** * Get the MCC code for the selected industry. * diff --git a/includes/admin/class-wc-rest-payments-onboarding-controller.php b/includes/admin/class-wc-rest-payments-onboarding-controller.php index 1a9553d46c8..da65bf5770c 100644 --- a/includes/admin/class-wc-rest-payments-onboarding-controller.php +++ b/includes/admin/class-wc-rest-payments-onboarding-controller.php @@ -204,10 +204,14 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_embedded_kyc_session( WP_REST_Request $request ) { + $self_assessment_data = ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : []; + $progressive = ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' ); + $collect_payout_requirements = ! empty( $request->get_param( 'collect_payout_requirements' ) ) && 'true' === $request->get_param( 'collect_payout_requirements' ); + $account_session = $this->onboarding_service->create_embedded_kyc_session( - ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : [], - ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' ), - ! empty( $request->get_param( 'collect_payout_requirements' ) ) && 'true' === $request->get_param( 'collect_payout_requirements' ) + $self_assessment_data, + $progressive, + $collect_payout_requirements ); if ( $account_session ) { From cb5460d86870543f3eb59c71b6948d4c58524fca Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 12 Sep 2024 16:25:16 -0500 Subject: [PATCH 08/40] Fix React error when the Express Checkout's container element is not found. (#9420) --- .../as-fix-react-error-container-not-found | 4 +++ .../utils/checkPaymentMethodIsAvailable.js | 15 ++++++---- ...xpress-checkout-button-display-handler.php | 29 ------------------- 3 files changed, 14 insertions(+), 34 deletions(-) create mode 100644 changelog/as-fix-react-error-container-not-found diff --git a/changelog/as-fix-react-error-container-not-found b/changelog/as-fix-react-error-container-not-found new file mode 100644 index 00000000000..4ca1309b3e3 --- /dev/null +++ b/changelog/as-fix-react-error-container-not-found @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Create div container element with JS dynamically. diff --git a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js index a42d7ffefe9..0792974909b 100644 --- a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js +++ b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js @@ -15,11 +15,15 @@ import { getUPEConfig } from 'wcpay/utils/checkout'; export const checkPaymentMethodIsAvailable = memoize( ( paymentMethod, cart, resolve ) => { - const root = ReactDOM.createRoot( - document.getElementById( - `express-checkout-check-availability-container-${ paymentMethod }` - ) - ); + // Create the DIV container on the fly + const containerEl = document.createElement( 'div' ); + + // Ensure the element is hidden and doesn’t interfere with the page layout. + containerEl.style.display = 'none'; + + document.querySelector( 'body' ).appendChild( containerEl ); + + const root = ReactDOM.createRoot( containerEl ); const api = new WCPayAPI( { @@ -71,6 +75,7 @@ export const checkPaymentMethodIsAvailable = memoize( } resolve( canMakePayment ); root.unmount(); + containerEl.remove(); } } /> diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php index a5e3e08939c..4a78920db88 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php @@ -95,10 +95,6 @@ public function init() { $is_woopay_enabled = WC_Payments_Features::is_woopay_enabled(); $is_payment_request_enabled = 'yes' === $this->gateway->get_option( 'payment_request' ); - if ( $is_payment_request_enabled ) { - $this->add_html_container_for_test_express_checkout_buttons(); - } - if ( $is_woopay_enabled || $is_payment_request_enabled ) { add_action( 'wc_ajax_wcpay_add_to_cart', [ $this->express_checkout_ajax_handler, 'ajax_add_to_cart' ] ); add_action( 'wc_ajax_wcpay_empty_cart', [ $this->express_checkout_ajax_handler, 'ajax_empty_cart' ] ); @@ -178,31 +174,6 @@ public function add_order_attribution_inputs() { echo ''; } - - /** - * Add HTML containers to be used by the Express Checkout buttons that check if the payment method is available. - * - * @return void - */ - private function add_html_container_for_test_express_checkout_buttons() { - add_filter( - 'the_content', - function ( $content ) { - $supported_payment_methods = [ 'applePay' , 'googlePay' ]; - // Restrict adding these HTML containers to only the necessary pages. - if ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) { - foreach ( $supported_payment_methods as $value ) { - // The inline styles ensure that the HTML elements don't occupy space on the page. - $content = '
' . $content; - } - } - return $content; - }, - 10, - 1 - ); - } - /** * Check if the pay-for-order flow is supported. * From e8a48cb2b8c0b5727fed7135be3921cda586675b Mon Sep 17 00:00:00 2001 From: Alex Florisca Date: Fri, 13 Sep 2024 12:53:39 +0100 Subject: [PATCH 09/40] Integrate express payments buttonAttributes API from the Checkout Block (#8888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> Co-authored-by: Kristófer R Co-authored-by: Kristófer Reykjalín <13835680+reykjalin@users.noreply.github.com> --- changelog/add-express-payment-button-styles | 4 ++ .../woopay-express-checkout-button.test.js | 21 ++++++++++ .../woopay-express-checkout-button.js | 24 +++++++++--- .../woopay-express-checkout-payment-method.js | 30 +++++++------- .../components/express-checkout-component.js | 20 +++++++++- .../components/express-checkout-container.js | 4 +- client/express-checkout/utils/index.ts | 21 ++++++++-- .../blocks/payment-request-express.js | 12 ++++-- ...general-payment-request-button-settings.js | 39 ++++++++++++------- client/utils/express-checkout/index.js | 6 +++ 10 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 changelog/add-express-payment-button-styles diff --git a/changelog/add-express-payment-button-styles b/changelog/add-express-payment-button-styles new file mode 100644 index 00000000000..0062c82145c --- /dev/null +++ b/changelog/add-express-payment-button-styles @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add compatibility with the buttonAttributes API from Woo Blocks diff --git a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js index 6b8179088b5..d31f4e19151 100644 --- a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js +++ b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js @@ -128,6 +128,27 @@ describe( 'WoopayExpressCheckoutButton', () => { ).toBeInTheDocument(); } ); + test( 'respect buttonAttributes API when available ', () => { + render( + + ); + + const button = screen.queryByRole( 'button', { name: 'WooPay' } ); + expect( button.getAttribute( 'style' ) ).toBe( + 'height: 55px; border-radius: 20px;' + ); + } ); + test( 'does not prefetch session data by default', async () => { getConfig.mockImplementation( ( v ) => { switch ( v ) { diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index 347f1a51de4..0f9bee580b6 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -40,6 +40,7 @@ export const WoopayExpressCheckoutButton = ( { api, isProductPage = false, emailSelector = '#email', + buttonAttributes, } ) => { const buttonWidthTypes = { narrow: 'narrow', @@ -48,10 +49,9 @@ export const WoopayExpressCheckoutButton = ( { const onClickCallbackRef = useRef( null ); const buttonRef = useRef( null ); const isLoadingRef = useRef( false ); - const { + let { + height: buttonHeight, type: buttonType, - height, - size, theme, context, radius: borderRadius, @@ -60,6 +60,18 @@ export const WoopayExpressCheckoutButton = ( { const [ buttonWidthType, setButtonWidthType ] = useState( buttonWidthTypes.wide ); + const buttonSizeMap = new Map(); + buttonSizeMap.set( '40', 'small' ); + buttonSizeMap.set( '48', 'medium' ); + buttonSizeMap.set( '55', 'large' ); + + // If we are on the checkout block, we receive button attributes which overwrite the extension specific settings + if ( typeof buttonAttributes !== 'undefined' ) { + buttonHeight = buttonAttributes.height || buttonHeight; + borderRadius = buttonAttributes.borderRadius || borderRadius; + } + + const buttonSize = buttonSizeMap.get( buttonHeight ); const buttonText = ButtonTypeTextMap[ buttonType || 'default' ] ?? @@ -353,18 +365,18 @@ export const WoopayExpressCheckoutButton = ( { return (
{ title } + { visibleDescription && ! isActive && ( + + { visibleDescription } + + ) }
{ children }
); diff --git a/client/additional-methods-setup/wizard/task-item.scss b/client/additional-methods-setup/wizard/task-item.scss index c2b7dbb5ec6..ae24c06ac08 100644 --- a/client/additional-methods-setup/wizard/task-item.scss +++ b/client/additional-methods-setup/wizard/task-item.scss @@ -117,6 +117,22 @@ } } + &__visible-description-element { + position: absolute; + margin-left: 40px; + margin-top: 0; + margin-bottom: 1em; + + &.is-muted-color { + color: $gray-700; + } + + .components-external-link svg { + width: 1em; + height: 1em; + } + } + .add-payment-methods-task { &__payment-selector { &-wrapper { diff --git a/client/capital/index.tsx b/client/capital/index.tsx index 8d1ab3f4741..81e76ad91b4 100644 --- a/client/capital/index.tsx +++ b/client/capital/index.tsx @@ -15,7 +15,10 @@ import Page from 'components/page'; import { TestModeNotice } from 'components/test-mode-notice'; import ErrorBoundary from 'components/error-boundary'; import ActiveLoanSummary from 'components/active-loan-summary'; -import { formatExplicitCurrency, isZeroDecimalCurrency } from 'utils/currency'; +import { + formatExplicitCurrency, + isZeroDecimalCurrency, +} from 'multi-currency/interface/functions'; import { CapitalLoan } from 'data/capital/types'; import ClickableCell from 'components/clickable-cell'; import Chip from 'components/chip'; diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index df410850621..c5ccaceed96 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -9,12 +9,12 @@ import { buildAjaxURL } from 'utils/express-checkout'; import { getAppearance } from 'checkout/upe-styles'; import { getTargetElement, - getAppearanceType, validateEmail, appendRedirectionParams, shouldSkipWooPay, deleteSkipWooPayCookie, } from './utils'; +import { getAppearanceType } from '../utils'; export const handleWooPayEmailInput = async ( field, diff --git a/client/components/account-balances/balance-block.tsx b/client/components/account-balances/balance-block.tsx index 4b71f4ef273..c9c9535c9ee 100644 --- a/client/components/account-balances/balance-block.tsx +++ b/client/components/account-balances/balance-block.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; /** * Internal dependencies */ -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import Loadable from 'components/loadable'; /** diff --git a/client/components/account-balances/index.tsx b/client/components/account-balances/index.tsx index 2a1ace68487..fa3be8b8422 100644 --- a/client/components/account-balances/index.tsx +++ b/client/components/account-balances/index.tsx @@ -23,7 +23,7 @@ import { } from './balance-tooltip'; import { fundLabelStrings } from './strings'; import { ClickTooltip } from '../tooltip'; -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import { useAllDepositsOverviews } from 'wcpay/data'; import { useSelectedCurrency } from 'wcpay/overview/hooks'; import './style.scss'; diff --git a/client/components/account-status/account-fees/expiration-bar.js b/client/components/account-status/account-fees/expiration-bar.js index 80003bba2a4..2d293e948c9 100644 --- a/client/components/account-status/account-fees/expiration-bar.js +++ b/client/components/account-status/account-fees/expiration-bar.js @@ -4,7 +4,7 @@ * Internal dependencies */ import ProgressBar from 'components/progress-bar'; -import { formatCurrency } from 'utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; const ExpirationBar = ( { feeData: { diff --git a/client/components/account-status/account-fees/expiration-description.js b/client/components/account-status/account-fees/expiration-description.js index a07067b6495..6be5b58681c 100644 --- a/client/components/account-status/account-fees/expiration-description.js +++ b/client/components/account-status/account-fees/expiration-description.js @@ -10,7 +10,7 @@ import moment from 'moment'; /** * Internal dependencies */ -import { formatCurrency } from 'utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; const ExpirationDescription = ( { feeData: { volume_allowance: volumeAllowance, end_time: endTime, ...rest }, diff --git a/client/components/account-status/account-fees/index.js b/client/components/account-status/account-fees/index.js index 93a93293c11..4b77b39e5f3 100644 --- a/client/components/account-status/account-fees/index.js +++ b/client/components/account-status/account-fees/index.js @@ -10,7 +10,10 @@ import { __ } from '@wordpress/i18n'; */ import ExpirationBar from './expiration-bar'; import ExpirationDescription from './expiration-description'; -import { formatCurrencyName, getCurrency } from 'utils/currency'; +import { + formatCurrencyName, + getCurrency, +} from 'multi-currency/interface/functions'; import { formatAccountFeesDescription, getCurrentBaseFee, diff --git a/client/components/active-loan-summary/index.tsx b/client/components/active-loan-summary/index.tsx index 97aac9ac082..0c5059ef87c 100755 --- a/client/components/active-loan-summary/index.tsx +++ b/client/components/active-loan-summary/index.tsx @@ -18,7 +18,7 @@ import { dateI18n } from '@wordpress/date'; /** * Internal dependencies. */ -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import Loadable from 'components/loadable'; import { useActiveLoanSummary } from 'wcpay/data'; import { getAdminUrl } from 'wcpay/utils'; diff --git a/client/components/deposits-overview/index.tsx b/client/components/deposits-overview/index.tsx index 23ce733bf50..5b89c21a95a 100644 --- a/client/components/deposits-overview/index.tsx +++ b/client/components/deposits-overview/index.tsx @@ -16,7 +16,7 @@ import { getHistory } from '@woocommerce/navigation'; * Internal dependencies. */ import { getAdminUrl } from 'wcpay/utils'; -import { formatExplicitCurrency } from 'wcpay/utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import { recordEvent } from 'tracks'; import Loadable from 'components/loadable'; import { useSelectedCurrencyOverview } from 'wcpay/overview/hooks'; diff --git a/client/components/deposits-overview/recent-deposits-list.tsx b/client/components/deposits-overview/recent-deposits-list.tsx index 1fed2448758..88555793d0d 100644 --- a/client/components/deposits-overview/recent-deposits-list.tsx +++ b/client/components/deposits-overview/recent-deposits-list.tsx @@ -21,7 +21,7 @@ import './style.scss'; import DepositStatusChip from 'components/deposit-status-chip'; import { getDepositDate } from 'deposits/utils'; import { CachedDeposit } from 'wcpay/types/deposits'; -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import { getDetailsURL } from 'wcpay/components/details-link'; interface RecentDepositsProps { diff --git a/client/components/disputed-order-notice/index.js b/client/components/disputed-order-notice/index.js index de5b720715a..ab51a52d16e 100644 --- a/client/components/disputed-order-notice/index.js +++ b/client/components/disputed-order-notice/index.js @@ -8,7 +8,7 @@ import { createInterpolateElement } from '@wordpress/element'; * Internal dependencies */ import InlineNotice from 'wcpay/components/inline-notice'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import { reasons } from 'wcpay/disputes/strings'; import { getDetailsURL } from 'wcpay/components/details-link'; import { diff --git a/client/components/payment-activity/payment-data-tile.tsx b/client/components/payment-activity/payment-data-tile.tsx index f9bbfbf7318..7c0d190d79f 100644 --- a/client/components/payment-activity/payment-data-tile.tsx +++ b/client/components/payment-activity/payment-data-tile.tsx @@ -9,7 +9,7 @@ import { recordEvent } from 'wcpay/tracks'; /** * Internal dependencies */ -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import Loadable from '../loadable'; import './style.scss'; diff --git a/client/components/welcome/currency-select.tsx b/client/components/welcome/currency-select.tsx index 7f82c412313..32c1e5a3cd9 100644 --- a/client/components/welcome/currency-select.tsx +++ b/client/components/welcome/currency-select.tsx @@ -8,7 +8,7 @@ import { decodeEntities } from '@wordpress/html-entities'; * Internal dependencies */ import { useSelectedCurrency } from 'overview/hooks'; -import { getCurrency } from 'utils/currency'; +import { getCurrency } from 'multi-currency/interface/functions'; import InlineLabelSelect from '../inline-label-select'; import { recordEvent } from 'tracks'; diff --git a/client/data/deposits/actions.js b/client/data/deposits/actions.js index 392b890657c..60b4479ab24 100644 --- a/client/data/deposits/actions.js +++ b/client/data/deposits/actions.js @@ -6,7 +6,7 @@ import { apiFetch } from '@wordpress/data-controls'; import { dispatch } from '@wordpress/data'; import { __, sprintf } from '@wordpress/i18n'; -import { formatCurrency } from 'utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; /** * Internal Dependencies diff --git a/client/data/index.ts b/client/data/index.ts index d20938feb56..878ecdc11f6 100644 --- a/client/data/index.ts +++ b/client/data/index.ts @@ -18,7 +18,6 @@ export * from './charges/hooks'; export * from './timeline/hooks'; export * from './disputes/hooks'; export * from './settings/hooks'; -export * from './multi-currency'; export * from './card-readers/hooks'; export * from './capital/hooks'; export * from './documents/hooks'; diff --git a/client/data/multi-currency/index.js b/client/data/multi-currency/index.js deleted file mode 100644 index c524cca8b05..00000000000 --- a/client/data/multi-currency/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @format */ - -/** - * Internal dependencies - */ -import reducer from './reducer'; -import * as selectors from './selectors'; -import * as actions from './actions'; -import * as resolvers from './resolvers'; - -export { reducer, selectors, actions, resolvers }; -export * from './hooks'; diff --git a/client/data/store.js b/client/data/store.js index 974efdb1e07..c4387901c37 100644 --- a/client/data/store.js +++ b/client/data/store.js @@ -14,7 +14,6 @@ import * as charges from './charges'; import * as timeline from './timeline'; import * as disputes from './disputes'; import * as settings from './settings'; -import * as multiCurrency from './multi-currency'; import * as readers from './card-readers'; import * as capital from './capital'; import * as documents from './documents'; @@ -33,7 +32,6 @@ export const initStore = () => timeline: timeline.reducer, disputes: disputes.reducer, settings: settings.reducer, - multiCurrency: multiCurrency.reducer, readers: readers.reducer, capital: capital.reducer, documents: documents.reducer, @@ -49,7 +47,6 @@ export const initStore = () => ...timeline.actions, ...disputes.actions, ...settings.actions, - ...multiCurrency.actions, ...readers.actions, ...capital.actions, ...documents.actions, @@ -66,7 +63,6 @@ export const initStore = () => ...timeline.selectors, ...disputes.selectors, ...settings.selectors, - ...multiCurrency.selectors, ...readers.selectors, ...capital.selectors, ...documents.selectors, @@ -82,7 +78,6 @@ export const initStore = () => ...timeline.resolvers, ...disputes.resolvers, ...settings.resolvers, - ...multiCurrency.resolvers, ...readers.resolvers, ...capital.resolvers, ...documents.resolvers, diff --git a/client/deposits/details/index.tsx b/client/deposits/details/index.tsx index 4d47ae51d6a..74aec3f2a7d 100644 --- a/client/deposits/details/index.tsx +++ b/client/deposits/details/index.tsx @@ -34,7 +34,10 @@ import Page from 'components/page'; import ErrorBoundary from 'components/error-boundary'; import { TestModeNotice } from 'components/test-mode-notice'; import InlineNotice from 'components/inline-notice'; -import { formatCurrency, formatExplicitCurrency } from 'utils/currency'; +import { + formatCurrency, + formatExplicitCurrency, +} from 'multi-currency/interface/functions'; import { displayStatus } from '../strings'; import './style.scss'; diff --git a/client/deposits/filters/index.js b/client/deposits/filters/index.js index 34cc4bbdf52..0d6d57afecc 100644 --- a/client/deposits/filters/index.js +++ b/client/deposits/filters/index.js @@ -8,7 +8,7 @@ import { getQuery } from '@woocommerce/navigation'; * Internal dependencies */ import { filters, advancedFilters } from './config'; -import { formatCurrencyName } from '../../utils/currency'; +import { formatCurrencyName } from 'multi-currency/interface/functions'; export const DepositsFilters = ( props ) => { const populateDepositCurrencies = ( filtersConfiguration ) => { diff --git a/client/deposits/instant-deposits/index.tsx b/client/deposits/instant-deposits/index.tsx index 683aecb03a3..64c49bc069f 100644 --- a/client/deposits/instant-deposits/index.tsx +++ b/client/deposits/instant-deposits/index.tsx @@ -12,7 +12,7 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import './style.scss'; -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import InstantDepositModal from './modal'; import { useInstantDeposit } from 'wcpay/data'; import type * as AccountOverview from 'wcpay/types/account-overview'; diff --git a/client/deposits/instant-deposits/modal.tsx b/client/deposits/instant-deposits/modal.tsx index 25b2783a348..e9d0f530aeb 100644 --- a/client/deposits/instant-deposits/modal.tsx +++ b/client/deposits/instant-deposits/modal.tsx @@ -11,7 +11,10 @@ import { createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies */ -import { formatCurrency, formatExplicitCurrency } from 'utils/currency'; +import { + formatCurrency, + formatExplicitCurrency, +} from 'multi-currency/interface/functions'; import type * as AccountOverview from 'wcpay/types/account-overview'; import './style.scss'; diff --git a/client/deposits/list/index.tsx b/client/deposits/list/index.tsx index 21c48d2f631..03789c466e1 100644 --- a/client/deposits/list/index.tsx +++ b/client/deposits/list/index.tsx @@ -26,7 +26,10 @@ import { useDispatch } from '@wordpress/data'; import { useDeposits, useDepositsSummary } from 'wcpay/data'; import { useReportingExportLanguage } from 'data/index'; import { displayType, displayStatus } from '../strings'; -import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; +import { + formatExplicitCurrency, + formatExportAmount, +} from 'multi-currency/interface/functions'; import DetailsLink, { getDetailsURL } from 'components/details-link'; import ClickableCell from 'components/clickable-cell'; import Page from '../../components/page'; diff --git a/client/disputes/filters/index.tsx b/client/disputes/filters/index.tsx index 6e8288f2611..0f6fc09069f 100644 --- a/client/disputes/filters/index.tsx +++ b/client/disputes/filters/index.tsx @@ -9,7 +9,7 @@ import { getQuery } from '@woocommerce/navigation'; * Internal dependencies */ import { filters, advancedFilters, DisputesFilterType } from './config'; -import { formatCurrencyName } from '../../utils/currency'; +import { formatCurrencyName } from 'multi-currency/interface/functions'; interface DisputesFiltersProps { storeCurrencies?: string[]; diff --git a/client/disputes/filters/test/index.tsx b/client/disputes/filters/test/index.tsx index c122ba7eb3a..015f28aec19 100644 --- a/client/disputes/filters/test/index.tsx +++ b/client/disputes/filters/test/index.tsx @@ -12,7 +12,7 @@ import { getQuery, updateQueryString } from '@woocommerce/navigation'; * Internal dependencies */ import { DisputesFilters } from '../'; -import { formatCurrencyName } from '../../../utils/currency'; +import { formatCurrencyName } from 'multi-currency/interface/functions'; // TODO: this is a bit of a hack as we're mocking an old version of WC, we should relook at this. jest.mock( '@woocommerce/settings', () => ( { diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx index 0832ee5515b..060afccce35 100644 --- a/client/disputes/index.tsx +++ b/client/disputes/index.tsx @@ -37,7 +37,10 @@ import Page from 'components/page'; import { TestModeNotice } from 'components/test-mode-notice'; import { reasons } from './strings'; import { formatStringValue } from 'utils'; -import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; +import { + formatExplicitCurrency, + formatExportAmount, +} from 'multi-currency/interface/functions'; import DisputesFilters from './filters'; import DownloadButton from 'components/download-button'; import disputeStatusMapping from 'components/dispute-status-chip/mappings'; diff --git a/client/disputes/info/index.tsx b/client/disputes/info/index.tsx index 7aa20ca6174..12f7ba0e64a 100644 --- a/client/disputes/info/index.tsx +++ b/client/disputes/info/index.tsx @@ -16,7 +16,7 @@ import OrderLink from 'components/order-link'; import { getDetailsURL } from 'components/details-link'; import { reasons } from '../strings'; import { formatStringValue } from 'utils'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import './style.scss'; import Loadable from 'components/loadable'; import { Dispute } from 'wcpay/types/disputes'; diff --git a/client/disputes/utils.ts b/client/disputes/utils.ts index e96251c7863..96a1ca40129 100644 --- a/client/disputes/utils.ts +++ b/client/disputes/utils.ts @@ -18,7 +18,10 @@ import { disputeAwaitingResponseStatuses, disputeUnderReviewStatuses, } from 'wcpay/disputes/filters/config'; -import { formatCurrency, formatExplicitCurrency } from 'wcpay/utils/currency'; +import { + formatCurrency, + formatExplicitCurrency, +} from 'multi-currency/interface/functions'; interface IsDueWithinProps { dueBy: CachedDispute[ 'due_by' ] | EvidenceDetails[ 'due_by' ]; diff --git a/client/index.js b/client/index.js index a5eb2d8376c..eb444c1f9de 100644 --- a/client/index.js +++ b/client/index.js @@ -23,7 +23,7 @@ import DisputesPage from 'disputes'; import RedirectToTransactionDetails from 'disputes/redirect-to-transaction-details'; import DisputeEvidencePage from 'disputes/evidence'; import AdditionalMethodsPage from 'wcpay/additional-methods-setup'; -import MultiCurrencySetupPage from 'wcpay/multi-currency-setup'; +import { MultiCurrencySetupPage } from 'multi-currency/interface/components'; import CardReadersPage from 'card-readers'; import CapitalPage from 'capital'; import OverviewPage from 'overview'; diff --git a/client/multi-currency-setup/wizard/task-item.js b/client/multi-currency-setup/wizard/task-item.js deleted file mode 100644 index 02a4c081feb..00000000000 --- a/client/multi-currency-setup/wizard/task-item.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * External dependencies - */ -import React, { useContext } from 'react'; -import classNames from 'classnames'; -import { Icon, check } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import WizardTaskContext from '../../additional-methods-setup/wizard/task/context'; -import './task-item.scss'; - -const WizardTaskItem = ( { - children, - title, - index, - className, - visibleDescription, -} ) => { - const { isCompleted, isActive } = useContext( WizardTaskContext ); - - return ( -
  • -
    -
    -
    -
    - { index } -
    - -
    - { title } -
    - { visibleDescription && ! isActive && ( - - { visibleDescription } - - ) } -
    { children }
    -
  • - ); -}; - -export default WizardTaskItem; diff --git a/client/multi-currency-setup/wizard/task-item.scss b/client/multi-currency-setup/wizard/task-item.scss deleted file mode 100644 index 9751a378ab6..00000000000 --- a/client/multi-currency-setup/wizard/task-item.scss +++ /dev/null @@ -1,17 +0,0 @@ -.wcpay-wizard-task { - &__visible-description-element { - position: absolute; - margin-left: 40px; - margin-top: 0; - margin-bottom: 1em; - - &.is-muted-color { - color: $gray-700; - } - - .components-external-link svg { - width: 1em; - height: 1em; - } - } -} diff --git a/client/overview/task-list/tasks/dispute-task.tsx b/client/overview/task-list/tasks/dispute-task.tsx index fdf48c03d78..235b92696b9 100644 --- a/client/overview/task-list/tasks/dispute-task.tsx +++ b/client/overview/task-list/tasks/dispute-task.tsx @@ -11,7 +11,7 @@ import { getHistory } from '@woocommerce/navigation'; */ import type { TaskItemProps } from '../types'; import type { CachedDispute } from 'wcpay/types/disputes'; -import { formatCurrency } from 'wcpay/utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import { getAdminUrl } from 'wcpay/utils'; import { recordEvent } from 'tracks'; import { isDueWithin } from 'wcpay/disputes/utils'; diff --git a/client/payment-details/dispute-details/dispute-steps.tsx b/client/payment-details/dispute-details/dispute-steps.tsx index ccc0764f38b..01f87431274 100644 --- a/client/payment-details/dispute-details/dispute-steps.tsx +++ b/client/payment-details/dispute-details/dispute-steps.tsx @@ -16,7 +16,7 @@ import HelpOutlineIcon from 'gridicons/dist/help-outline'; */ import type { Dispute } from 'wcpay/types/disputes'; import { ChargeBillingDetails } from 'wcpay/types/charges'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import { ClickTooltip } from 'wcpay/components/tooltip'; import { getDisputeFeeFormatted } from 'wcpay/disputes/utils'; import DisputeDueByDate from './dispute-due-by-date'; diff --git a/client/payment-details/dispute-details/dispute-summary-row.tsx b/client/payment-details/dispute-details/dispute-summary-row.tsx index ac6dada265e..0a43cb223e0 100644 --- a/client/payment-details/dispute-details/dispute-summary-row.tsx +++ b/client/payment-details/dispute-details/dispute-summary-row.tsx @@ -14,7 +14,7 @@ import { dateI18n } from '@wordpress/date'; */ import type { Dispute } from 'wcpay/types/disputes'; import { HorizontalList } from 'wcpay/components/horizontal-list'; -import { formatExplicitCurrency } from 'wcpay/utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import { reasons } from 'wcpay/disputes/strings'; import { formatStringValue } from 'wcpay/utils'; import { ClickTooltip } from 'wcpay/components/tooltip'; diff --git a/client/payment-details/readers/index.js b/client/payment-details/readers/index.js index 9ff428c94fe..193ee236288 100644 --- a/client/payment-details/readers/index.js +++ b/client/payment-details/readers/index.js @@ -19,7 +19,10 @@ import { useCardReaderStats } from 'wcpay/data'; import { TestModeNotice } from 'components/test-mode-notice'; import Page from 'components/page'; import DownloadButton from 'components/download-button'; -import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; +import { + formatExplicitCurrency, + formatExportAmount, +} from 'multi-currency/interface/functions'; const PaymentCardReaderChargeDetails = ( props ) => { const { readers, chargeError, isLoading } = useCardReaderStats( diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index 4350d609cd7..4e9d9559a4b 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -37,7 +37,10 @@ import { HorizontalList, HorizontalListItem } from 'components/horizontal-list'; import Loadable, { LoadableBlock } from 'components/loadable'; import riskMappings from 'components/risk-level/strings'; import OrderLink from 'components/order-link'; -import { formatCurrency, formatExplicitCurrency } from 'utils/currency'; +import { + formatCurrency, + formatExplicitCurrency, +} from 'multi-currency/interface/functions'; import CustomerLink from 'components/customer-link'; import { ClickTooltip } from 'components/tooltip'; import DisputeStatusChip from 'components/dispute-status-chip'; diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js index 1cb14d6aa7a..64bc74d91d2 100644 --- a/client/payment-details/timeline/map-events.js +++ b/client/payment-details/timeline/map-events.js @@ -26,7 +26,7 @@ import { formatCurrency, formatFX, formatExplicitCurrency, -} from 'utils/currency'; +} from 'multi-currency/interface/functions'; import { formatFee } from 'utils/fees'; import { getAdminUrl } from 'wcpay/utils'; import { ShieldIcon } from 'wcpay/icons'; diff --git a/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx b/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx index ca1e45b470f..d99b3b7ca4b 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx @@ -15,7 +15,7 @@ import AmountInput from 'wcpay/components/amount-input'; /** * Internal dependencies */ -import { getCurrency } from 'utils/currency'; +import { getCurrency } from 'multi-currency/interface/functions'; import FraudProtectionRuleCard from '../rule-card'; import FraudProtectionRuleToggle from '../rule-toggle'; import FraudProtectionRuleDescription from '../rule-description'; diff --git a/client/settings/fraud-protection/test/index.test.tsx b/client/settings/fraud-protection/test/index.test.tsx index 53863e26efb..e7650195cde 100644 --- a/client/settings/fraud-protection/test/index.test.tsx +++ b/client/settings/fraud-protection/test/index.test.tsx @@ -11,7 +11,6 @@ import { useDispatch } from '@wordpress/data'; import FraudProtection from '..'; import { useCurrentProtectionLevel, - useCurrencies, useAdvancedFraudProtectionSettings, useSettings, } from 'wcpay/data'; @@ -29,7 +28,6 @@ jest.mock( 'wcpay/data', () => ( { useAdvancedFraudProtectionSettings: jest.fn(), useCurrentProtectionLevel: jest.fn(), useSettings: jest.fn(), - useCurrencies: jest.fn(), } ) ); jest.mock( '@wordpress/data', () => ( { @@ -44,10 +42,6 @@ const mockUseCurrentProtectionLevel = useCurrentProtectionLevel as jest.MockedFu () => [ string, ( level: string ) => void ] >; -const mockUseCurrencies = useCurrencies as jest.MockedFunction< - () => { currencies: Record< string, any >; isLoading: boolean } ->; - const mockUseAdvancedFraudProtectionSettings = useAdvancedFraudProtectionSettings as jest.MockedFunction< () => [ any[] | string, ( settings: string ) => void ] >; @@ -69,16 +63,6 @@ describe( 'FraudProtection', () => { 'standard', jest.fn(), ] ); - mockUseCurrencies.mockReturnValue( { - isLoading: false, - currencies: { - available: { - EUR: { name: 'Euro', symbol: '€' }, - USD: { name: 'US Dollar', symbol: '$' }, - PLN: { name: 'Polish złoty', symbol: 'zł' }, - }, - }, - } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ [], diff --git a/client/settings/payment-methods-list/index.js b/client/settings/payment-methods-list/index.js index 99a34b40daf..997c2bb9874 100644 --- a/client/settings/payment-methods-list/index.js +++ b/client/settings/payment-methods-list/index.js @@ -22,7 +22,7 @@ import { upeCapabilityStatuses } from 'wcpay/additional-methods-setup/constants' import ConfirmPaymentMethodActivationModal from './activation-modal'; import ConfirmPaymentMethodDeleteModal from './delete-modal'; import CapabilityRequestNotice from './capability-request'; -import { getMissingCurrenciesTooltipMessage } from 'wcpay/multi-currency/missing-currencies-message'; +import { getMissingCurrenciesTooltipMessage } from 'multi-currency/interface/functions'; const PaymentMethodsList = ( { methodIds } ) => { const [ enabledMethodIds ] = useEnabledPaymentMethodIds(); diff --git a/client/settings/payment-methods-section/__tests__/payment-methods-section.test.js b/client/settings/payment-methods-section/__tests__/payment-methods-section.test.js index 2e1f1da1538..d64f2ded639 100644 --- a/client/settings/payment-methods-section/__tests__/payment-methods-section.test.js +++ b/client/settings/payment-methods-section/__tests__/payment-methods-section.test.js @@ -35,8 +35,6 @@ jest.mock( '@woocommerce/components', () => { jest.mock( 'wcpay/data', () => ( { useEnabledPaymentMethodIds: jest.fn(), useGetAvailablePaymentMethodIds: jest.fn(), - useCurrencies: jest.fn().mockReturnValue( { isLoading: true } ), - useEnabledCurrencies: jest.fn().mockReturnValue( {} ), useGetPaymentMethodStatuses: jest.fn().mockReturnValue( {} ), useManualCapture: jest.fn(), useSelectedPaymentMethod: jest.fn(), @@ -45,6 +43,16 @@ jest.mock( 'wcpay/data', () => ( { useSettings: jest.fn().mockReturnValue( { isLoading: false } ), } ) ); +jest.mock( 'multi-currency/interface/data', () => ( { + useCurrencies: jest.fn().mockReturnValue( { isLoading: true } ), + useEnabledCurrencies: jest.fn().mockReturnValue( {} ), +} ) ); + +jest.mock( 'multi-currency/interface/data', () => ( { + useCurrencies: jest.fn().mockReturnValue( { isLoading: true } ), + useEnabledCurrencies: jest.fn().mockReturnValue( {} ), +} ) ); + jest.mock( '@wordpress/data', () => ( { useDispatch: jest .fn() diff --git a/client/transactions/blocked/columns.tsx b/client/transactions/blocked/columns.tsx index e5edd19809d..d321d9fe028 100644 --- a/client/transactions/blocked/columns.tsx +++ b/client/transactions/blocked/columns.tsx @@ -10,7 +10,7 @@ import { TableCardColumn, TableCardBodyColumn } from '@woocommerce/components'; /** * Internal dependencies */ -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import TransactionStatusPill from 'wcpay/components/transaction-status-pill'; import { FraudOutcomeTransaction } from '../../data'; import { getDetailsURL } from '../../components/details-link'; diff --git a/client/transactions/blocked/index.tsx b/client/transactions/blocked/index.tsx index e1bdce63664..f6153231f69 100644 --- a/client/transactions/blocked/index.tsx +++ b/client/transactions/blocked/index.tsx @@ -34,7 +34,7 @@ import { getBlockedListColumns, getBlockedListColumnsStructure, } from './columns'; -import { formatExplicitCurrency } from '../../utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import autocompleter from '../fraud-protection/autocompleter'; import DownloadButton from '../../components/download-button'; import { getFraudOutcomeTransactionsExport } from '../../data/transactions/resolvers'; diff --git a/client/transactions/filters/index.tsx b/client/transactions/filters/index.tsx index cbb729ce5d9..ef8c11d4f15 100644 --- a/client/transactions/filters/index.tsx +++ b/client/transactions/filters/index.tsx @@ -9,7 +9,7 @@ import { getQuery } from '@woocommerce/navigation'; * Internal dependencies */ import { getFilters, getAdvancedFilters } from './config'; -import { formatCurrencyName } from '../../utils/currency'; +import { formatCurrencyName } from 'multi-currency/interface/functions'; import { recordEvent } from 'tracks'; interface TransactionsFiltersProps { diff --git a/client/transactions/list/converted-amount.tsx b/client/transactions/list/converted-amount.tsx index 6a74b01a25a..fac4d9acd25 100644 --- a/client/transactions/list/converted-amount.tsx +++ b/client/transactions/list/converted-amount.tsx @@ -12,7 +12,7 @@ import classNames from 'classnames'; /** * Internal dependencies */ -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; declare const window: any; diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index 398da0d8f12..33dba4253f7 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -54,7 +54,7 @@ import { formatCurrency, formatExplicitCurrency, formatExportAmount, -} from 'utils/currency'; +} from 'multi-currency/interface/functions'; import { getChargeChannel } from 'utils/charge'; import Deposit from './deposit'; import ConvertedAmount from './converted-amount'; diff --git a/client/transactions/risk-review/columns.tsx b/client/transactions/risk-review/columns.tsx index 59db13c52c8..d7f5de95111 100644 --- a/client/transactions/risk-review/columns.tsx +++ b/client/transactions/risk-review/columns.tsx @@ -13,7 +13,7 @@ import { Button } from '@wordpress/components'; */ import { getDetailsURL } from 'components/details-link'; import ClickableCell from 'components/clickable-cell'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import { recordEvent } from 'tracks'; import TransactionStatusPill from 'wcpay/components/transaction-status-pill'; import { FraudOutcomeTransaction } from '../../data'; diff --git a/client/transactions/risk-review/index.tsx b/client/transactions/risk-review/index.tsx index 0681110d678..f83010dc1e8 100644 --- a/client/transactions/risk-review/index.tsx +++ b/client/transactions/risk-review/index.tsx @@ -35,7 +35,7 @@ import { getRiskReviewListColumnsStructure, } from './columns'; import './style.scss'; -import { formatExplicitCurrency } from '../../utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import autocompleter from '../fraud-protection/autocompleter'; import DownloadButton from '../../components/download-button'; import { getFraudOutcomeTransactionsExport } from '../../data/transactions/resolvers'; diff --git a/client/transactions/uncaptured/index.tsx b/client/transactions/uncaptured/index.tsx index 3cf14c956f7..17058760c19 100644 --- a/client/transactions/uncaptured/index.tsx +++ b/client/transactions/uncaptured/index.tsx @@ -17,7 +17,7 @@ import { useAuthorizations, useAuthorizationsSummary } from 'data/index'; import Page from '../../components/page'; import { getDetailsURL } from 'components/details-link'; import ClickableCell from 'components/clickable-cell'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import RiskLevel, { calculateRiskMapping } from 'components/risk-level'; import { recordEvent } from 'tracks'; import CaptureAuthorizationButton from 'wcpay/components/capture-authorization-button'; diff --git a/client/utils/account-fees.tsx b/client/utils/account-fees.tsx index b3d0f5b9f2c..711d3d337ed 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -1,7 +1,7 @@ /** @format */ /** - * External depencencies + * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; @@ -10,7 +10,7 @@ import './account-fees.scss'; /** * Internal dependencies */ -import { formatCurrency } from 'utils/currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import { formatFee } from 'utils/fees'; import React from 'react'; import { BaseFee, DiscountFee, FeeStructure } from 'wcpay/types/fees'; diff --git a/client/utils/test/account-fees.tsx b/client/utils/test/account-fees.tsx index c1b181ac160..3184e42d644 100644 --- a/client/utils/test/account-fees.tsx +++ b/client/utils/test/account-fees.tsx @@ -14,10 +14,10 @@ import { formatMethodFeesTooltip, getCurrentBaseFee, } from '../account-fees'; -import { formatCurrency } from '../currency'; +import { formatCurrency } from 'multi-currency/interface/functions'; import { BaseFee, DiscountFee, FeeStructure } from 'wcpay/types/fees'; -jest.mock( '../currency', () => ( { +jest.mock( 'multi-currency/interface/functions', () => ( { formatCurrency: jest.fn( ( amount: number ): string => { return sprintf( '$%.2f', amount / 100 ); } ), diff --git a/client/vat/form/tasks/company-data-task.tsx b/client/vat/form/tasks/company-data-task.tsx index 223c35d309d..ee97f7045b4 100644 --- a/client/vat/form/tasks/company-data-task.tsx +++ b/client/vat/form/tasks/company-data-task.tsx @@ -11,14 +11,14 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import React, { useContext, useEffect, useState } from 'react'; -import CollapsibleBody from 'wcpay/additional-methods-setup/wizard/collapsible-body'; -import WizardTaskItem from 'wcpay/additional-methods-setup/wizard/task-item'; -import WizardTaskContext from 'wcpay/additional-methods-setup/wizard/task/context'; import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ +import CollapsibleBody from 'wcpay/additional-methods-setup/wizard/collapsible-body'; +import WizardTaskItem from 'wcpay/additional-methods-setup/wizard/task-item'; +import WizardTaskContext from 'wcpay/additional-methods-setup/wizard/task/context'; import { VatError, VatFormOnCompleted, @@ -97,6 +97,7 @@ export const CompanyDataTask = ( { 'woocommerce-payments' ) } className={ null } + visibleDescription={ null } >

    { __( diff --git a/composer.json b/composer.json index 284f633e65b..74a8c2d1aee 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "autoload": { "psr-4": { - "WCPay\\MultiCurrency\\": "includes/multi-currency", + "WCPay\\MultiCurrency\\": "multi-currency/src", "WCPay\\Vendor\\": "lib/packages", "WCPay\\": "src" }, diff --git a/includes/class-database-cache.php b/includes/class-database-cache.php index 68b4c7d4d89..042d99f7f9e 100644 --- a/includes/class-database-cache.php +++ b/includes/class-database-cache.php @@ -7,16 +7,17 @@ namespace WCPay; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyCacheInterface; + defined( 'ABSPATH' ) || exit; // block direct access. /** * A class for caching data as an option in the database. */ -class Database_Cache { +class Database_Cache implements MultiCurrencyCacheInterface { const ACCOUNT_KEY = 'wcpay_account_data'; const ONBOARDING_FIELDS_DATA_KEY = 'wcpay_onboarding_fields_data'; const BUSINESS_TYPES_KEY = 'wcpay_business_types_data'; - const CURRENCIES_KEY = 'wcpay_multi_currency_cached_currencies'; const PAYMENT_PROCESS_FACTORS_KEY = 'wcpay_payment_process_factors'; const FRAUD_SERVICES_KEY = 'wcpay_fraud_services_data'; diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 2f4afc40155..33f829ee0de 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -17,11 +17,12 @@ use WCPay\Exceptions\API_Exception; use WCPay\Logger; use WCPay\Database_Cache; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; /** * Class handling any account connection functionality */ -class WC_Payments_Account { +class WC_Payments_Account implements MultiCurrencyAccountInterface { // ACCOUNT_OPTION is only used in the supporting dev tools plugin, it can be removed once everyone has upgraded. const ACCOUNT_OPTION = 'wcpay_account_data'; @@ -170,6 +171,18 @@ public function get_publishable_key( $is_test ) { return $account['live_publishable_key']; } + /** + * Checks if the account is connected to the payment provider. + * Note: This method is a proxy for `is_stripe_connected` for the MultiCurrencyAccountInterface. + * + * @param bool $on_error Value to return on server error, defaults to false. + * + * @return bool True if the account is connected, false otherwise, $on_error on error. + */ + public function is_provider_connected( bool $on_error = false ): bool { + return $this->is_stripe_connected( $on_error ); + } + /** * Determine if the store has a working Jetpack connection. * @@ -635,11 +648,22 @@ public function get_account_email(): string { * * @return array Currencies. */ - public function get_account_customer_supported_currencies() { + public function get_account_customer_supported_currencies(): array { $account = $this->get_cached_account_data(); return ! empty( $account ) && isset( $account['customer_currencies']['supported'] ) ? $account['customer_currencies']['supported'] : []; } + /** + * List of countries enabled for Stripe platform account. See also this URL: + * https://woocommerce.com/document/woopayments/compatibility/countries/#supported-countries + * + * @return array + */ + public function get_supported_countries(): array { + // This is a wrapper function because of the MultiCurrencyAccountInterface. + return WC_Payments_Utils::supported_countries(); + } + /** * Gets the account live mode value. * @@ -1637,6 +1661,21 @@ private function get_login_url() { ); } + /** + * Get provider onboarding page url. + * + * @return string + */ + public function get_provider_onboarding_page_url(): string { + return add_query_arg( + [ + 'page' => 'wc-admin', + 'path' => '/payments/connect', + ], + admin_url( 'admin.php' ) + ); + } + /** * Get connect url. * @@ -1678,21 +1717,6 @@ public static function get_payments_task_page_url() { ); } - /** - * Get Connect page url. - * - * @return string - */ - public static function get_connect_page_url(): string { - return add_query_arg( - [ - 'page' => 'wc-admin', - 'path' => '/payments/connect', - ], - admin_url( 'admin.php' ) - ); - } - /** * Get overview page url * diff --git a/includes/class-wc-payments-explicit-price-formatter.php b/includes/class-wc-payments-explicit-price-formatter.php index d96bfa4f25f..31c5364cfbe 100644 --- a/includes/class-wc-payments-explicit-price-formatter.php +++ b/includes/class-wc-payments-explicit-price-formatter.php @@ -6,7 +6,6 @@ */ use WCPay\MultiCurrency\MultiCurrency; -use WCPay\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. diff --git a/includes/class-wc-payments-localization-service.php b/includes/class-wc-payments-localization-service.php index 5ea9375429d..c0c0d7d84f8 100644 --- a/includes/class-wc-payments-localization-service.php +++ b/includes/class-wc-payments-localization-service.php @@ -5,12 +5,14 @@ * @package WooCommerce\Payments */ +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; + defined( 'ABSPATH' ) || exit; /** * WC_Payments_Localization_Service. */ -class WC_Payments_Localization_Service { +class WC_Payments_Localization_Service implements MultiCurrencyLocalizationInterface { const WCPAY_CURRENCY_FORMAT_TRANSIENT = 'wcpay_currency_format'; const WCPAY_LOCALE_INFO_TRANSIENT = 'wcpay_locale_info'; diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index ac6aca75c50..93e22fe9897 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -42,6 +42,7 @@ use WCPay\WooPay\WooPay_Session; use WCPay\Compatibility_Service; use WCPay\Duplicates_Detection_Service; +use WCPay\WC_Payments_Currency_Manager; /** * Main class for the WooPayments extension. Its responsibility is to initialize the extension. @@ -299,6 +300,13 @@ class WC_Payments { */ private static $duplicates_detection_service; + /** + * Instance of WC_Payments_Currency_Manager, created in init function + * + * @var WC_Payments_Currency_Manager + */ + private static $currency_manager; + /** * Entry point to the initialization logic. */ @@ -483,7 +491,8 @@ public static function init() { include_once __DIR__ . '/class-duplicate-payment-prevention-service.php'; include_once __DIR__ . '/class-wc-payments-incentives-service.php'; include_once __DIR__ . '/class-compatibility-service.php'; - include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php'; + include_once __DIR__ . '/compat/multi-currency/wc-payments-multi-currency.php'; + include_once __DIR__ . '/compat/multi-currency/class-wc-payments-currency-manager.php'; include_once __DIR__ . '/class-duplicates-detection-service.php'; self::$woopay_checkout_service = new Checkout_Service(); @@ -576,6 +585,9 @@ public static function init() { self::$customer_service_api = new WC_Payments_Customer_Service_API( self::$customer_service ); + self::$currency_manager = new WC_Payments_Currency_Manager( self::get_gateway() ); + self::$currency_manager->init_hooks(); + // Only register hooks of the new `src` service with the same feature of Duplicate_Payment_Prevention_Service. // To avoid register the same hooks twice. wcpay_get_container()->get( \WCPay\Internal\Service\DuplicatePaymentPreventionService::class )->init_hooks(); @@ -1398,6 +1410,22 @@ public static function get_session_service() { return self::$session_service; } + /** + * Returns gateway context variables needed for multi-currency support. + * + * @return array + */ + public static function get_context_for_multi_currency() { + // While multi-currency is being decoupled from WooPayments into a separate module, it is still rendered within the plugin. + // We don't want to reference WCPAY constants from within the module, therefore, we need a few variables from the gateway, + // as reflected in the array below. + return [ + 'plugin_version' => WCPAY_VERSION_NUMBER, + 'plugin_file_path' => WCPAY_PLUGIN_FILE, + 'is_dev_mode' => self::mode()->is_dev(), + ]; + } + /** * Registers the payment method with the blocks registry. * diff --git a/includes/multi-currency/PaymentMethodsCompatibility.php b/includes/compat/multi-currency/class-wc-payments-currency-manager.php similarity index 69% rename from includes/multi-currency/PaymentMethodsCompatibility.php rename to includes/compat/multi-currency/class-wc-payments-currency-manager.php index 0c5f7d4b736..c00da0d1a69 100644 --- a/includes/multi-currency/PaymentMethodsCompatibility.php +++ b/includes/compat/multi-currency/class-wc-payments-currency-manager.php @@ -1,29 +1,21 @@ multi_currency = $multi_currency; - $this->gateway = $gateway; + public function __construct( WC_Payment_Gateway_WCPay $gateway ) { + $this->gateway = $gateway; } /** @@ -48,17 +38,32 @@ public function __construct( MultiCurrency $multi_currency, WC_Payment_Gateway_W * @return void */ public function init_hooks() { - add_action( - 'update_option_woocommerce_woocommerce_payments_settings', - [ $this, 'add_missing_currencies' ] - ); + add_action( 'update_option_woocommerce_woocommerce_payments_settings', [ $this, 'maybe_add_missing_currencies' ] ); add_action( 'admin_head', [ $this, 'add_payment_method_currency_dependencies_script' ] ); } + /** + * Gets the multi-currency instance or returns null if it's not available. + * This method allows for easier testing by allowing the multi-currency instance to be mocked. + * + * @return \WCPay\MultiCurrency\MultiCurrency|null + */ + public function get_multi_currency_instance() { + if ( ! function_exists( 'WC_Payments_Multi_Currency' ) ) { + return null; + } + + if ( ! WC_Payments_Multi_Currency()->is_initialized() ) { + return null; + } + + return WC_Payments_Multi_Currency(); + } + /** * Returns the currencies needed per enabled payment method * - * @return array The currencies keyed with the related payment method + * @return array The currencies keyed with the related payment method */ public function get_enabled_payment_method_currencies() { $enabled_payment_method_ids = $this->gateway->get_upe_enabled_payment_method_ids(); @@ -97,14 +102,19 @@ function ( $result, $method ) use ( $account_currency ) { /** * Ensures that when a payment method is added from the settings, the needed currency is also added. */ - public function add_missing_currencies() { + public function maybe_add_missing_currencies() { + $multi_currency = $this->get_multi_currency_instance(); + if ( is_null( $multi_currency ) ) { + return; + } + $payment_methods_needing_currency = $this->get_enabled_payment_method_currencies(); if ( empty( $payment_methods_needing_currency ) ) { return; } - $enabled_currencies = $this->multi_currency->get_enabled_currencies(); - $available_currencies = $this->multi_currency->get_available_currencies(); + $enabled_currencies = $multi_currency->get_enabled_currencies(); + $available_currencies = $multi_currency->get_available_currencies(); $missing_currency_codes = []; @@ -137,15 +147,21 @@ public function add_missing_currencies() { * The set_enabled_currencies method throws an exception if any currencies passed are not found in the current available currencies. * Any currencies not found are filtered out above, so we shouldn't need a try/catch here. */ - $this->multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); + $multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); } /** - * Adds the notices for currencies that are bound to an UPE payment method. + * Adds the `multiCurrencyPaymentMethodsMap` JS object to the multi-currency settings page. * - * @return void + * This object maps currencies to payment methods that require them, so the multi-currency settings page displays a notice in case of dependencies. */ public function add_payment_method_currency_dependencies_script() { + $multi_currency = $this->get_multi_currency_instance(); + + if ( is_null( $multi_currency ) || ! $multi_currency->is_multi_currency_settings_page() ) { + return; + } + $payment_methods_needing_currency = $this->get_enabled_payment_method_currencies(); if ( empty( $payment_methods_needing_currency ) ) { return; @@ -161,11 +177,10 @@ public function add_payment_method_currency_dependencies_script() { } } - if ( WC_Payments_Multi_Currency()->is_multi_currency_settings_page() ) : ?> + ?> - init_hooks(); + } + + return $instance; } add_action( 'plugins_loaded', 'WC_Payments_Multi_Currency', 12 ); diff --git a/includes/multi-currency/CountryFlags.php b/includes/multi-currency/CountryFlags.php deleted file mode 100644 index e841095255e..00000000000 --- a/includes/multi-currency/CountryFlags.php +++ /dev/null @@ -1,304 +0,0 @@ - '🇦🇩', - Country_Code::UNITED_ARAB_EMIRATES => '🇦🇪', - Country_Code::AFGHANISTAN => '🇦🇫', - Country_Code::ANTIGUA_AND_BARBUDA => '🇦🇬', - Country_Code::ANGUILLA => '🇦🇮', - Country_Code::ALBANIA => '🇦🇱', - Country_Code::ARMENIA => '🇦🇲', - Country_Code::ANGOLA => '🇦🇴', - Country_Code::ANTARCTICA => '🇦🇶', - Country_Code::ARGENTINA => '🇦🇷', - Country_Code::AMERICAN_SAMOA => '🇦🇸', - Country_Code::AUSTRIA => '🇦🇹', - Country_Code::AUSTRALIA => '🇦🇺', - Country_Code::ARUBA => '🇦🇼', - Country_Code::ALAND_ISLANDS => '🇦🇽', - Country_Code::AZERBAIJAN => '🇦🇿', - Country_Code::BOSNIA_AND_HERZEGOVINA => '🇧🇦', - Country_Code::BARBADOS => '🇧🇧', - Country_Code::BANGLADESH => '🇧🇩', - Country_Code::BELGIUM => '🇧🇪', - Country_Code::BURKINA_FASO => '🇧🇫', - Country_Code::BULGARIA => '🇧🇬', - Country_Code::BAHRAIN => '🇧🇭', - Country_Code::BURUNDI => '🇧🇮', - Country_Code::BENIN => '🇧🇯', - Country_Code::SAINT_BARTHELEMY => '🇧🇱', - Country_Code::BERMUDA => '🇧🇲', - Country_Code::BRUNEI => '🇧🇳', - Country_Code::BOLIVIA => '🇧🇴', - Country_Code::CARIBBEAN_NETHERLANDS => '🇧🇶', - Country_Code::BRAZIL => '🇧🇷', - Country_Code::BAHAMAS => '🇧🇸', - Country_Code::BHUTAN => '🇧🇹', - Country_Code::BOUVET_ISLAND => '🇧🇻', - Country_Code::BOTSWANA => '🇧🇼', - Country_Code::BELARUS => '🇧🇾', - Country_Code::BELIZE => '🇧🇿', - Country_Code::CANADA => '🇨🇦', - Country_Code::COCOS_KEELING_ISLANDS => '🇨🇨', - Country_Code::DEMOCRATIC_REPUBLIC_OF_THE_CONGO => '🇨🇩', - Country_Code::CENTRAL_AFRICAN_REPUBLIC => '🇨🇫', - Country_Code::CONGO => '🇨🇬', - Country_Code::SWITZERLAND => '🇨🇭', - Country_Code::IVORY_COAST => '🇨🇮', - Country_Code::COOK_ISLANDS => '🇨🇰', - Country_Code::CHILE => '🇨🇱', - Country_Code::CAMEROON => '🇨🇲', - Country_Code::CHINA => '🇨🇳', - Country_Code::COLOMBIA => '🇨🇴', - Country_Code::COSTA_RICA => '🇨🇷', - Country_Code::CUBA => '🇨🇺', - Country_Code::CABO_VERDE => '🇨🇻', - 'CW' => '🇨🇼', - 'CX' => '🇨🇽', - Country_Code::CYPRUS => '🇨🇾', - Country_Code::CZECHIA => '🇨🇿', - Country_Code::GERMANY => '🇩🇪', - Country_Code::DJIBOUTI => '🇩🇯', - Country_Code::DENMARK => '🇩🇰', - Country_Code::DOMINICA => '🇩🇲', - Country_Code::DOMINICAN_REPUBLIC => '🇩🇴', - Country_Code::ALGERIA => '🇩🇿', - Country_Code::ECUADOR => '🇪🇨', - Country_Code::ESTONIA => '🇪🇪', - Country_Code::EGYPT => '🇪🇬', - 'EH' => '🇪🇭', - Country_Code::ERITREA => '🇪🇷', - Country_Code::SPAIN => '🇪🇸', - Country_Code::ETHIOPIA => '🇪🇹', - 'EU' => '🇪🇺', - Country_Code::FINLAND => '🇫🇮', - Country_Code::FIJI => '🇫🇯', - 'FK' => '🇫🇰', - Country_Code::MICRONESIA => '🇫🇲', - 'FO' => '🇫🇴', - Country_Code::FRANCE => '🇫🇷', - Country_Code::GABON => '🇬🇦', - Country_Code::UNITED_KINGDOM => '🇬🇧', - Country_Code::GRENADA => '🇬🇩', - Country_Code::GEORGIA => '🇬🇪', - 'GF' => '🇬🇫', - 'GG' => '🇬🇬', - Country_Code::GHANA => '🇬🇭', - Country_Code::GIBRALTAR => '🇬🇮', - 'GL' => '🇬🇱', - Country_Code::GAMBIA => '🇬🇲', - Country_Code::GUINEA => '🇬🇳', - 'GP' => '🇬🇵', - Country_Code::EQUATORIAL_GUINEA => '🇬🇶', - Country_Code::GREECE => '🇬🇷', - 'GS' => '🇬🇸', - Country_Code::GUATEMALA => '🇬🇹', - 'GU' => '🇬🇺', - Country_Code::GUINEA_BISSAU => '🇬🇼', - Country_Code::GUYANA => '🇬🇾', - Country_Code::HONG_KONG => '🇭🇰', - 'HM' => '🇭🇲', - Country_Code::HONDURAS => '🇭🇳', - Country_Code::CROATIA => '🇭🇷', - Country_Code::HAITI => '🇭🇹', - Country_Code::HUNGARY => '🇭🇺', - Country_Code::INDONESIA => '🇮🇩', - Country_Code::IRELAND => '🇮🇪', - Country_Code::ISRAEL => '🇮🇱', - 'IM' => '🇮🇲', - Country_Code::INDIA => '🇮🇳', - Country_Code::BRITISH_INDIAN_OCEAN_TERRITORY => '🇮🇴', - Country_Code::IRAQ => '🇮🇶', - Country_Code::IRAN => '🇮🇷', - Country_Code::ICELAND => '🇮🇸', - Country_Code::ITALY => '🇮🇹', - 'JE' => '🇯🇪', - Country_Code::JAMAICA => '🇯🇲', - Country_Code::JORDAN => '🇯🇴', - Country_Code::JAPAN => '🇯🇵', - Country_Code::KENYA => '🇰🇪', - Country_Code::KYRGYZSTAN => '🇰🇬', - Country_Code::CAMBODIA => '🇰🇭', - Country_Code::KIRIBATI => '🇰🇮', - Country_Code::COMOROS => '🇰🇲', - Country_Code::SAINT_KITTS_AND_NEVIS => '🇰🇳', - Country_Code::NORTH_KOREA => '🇰🇵', - Country_Code::SOUTH_KOREA => '🇰🇷', - Country_Code::KUWAIT => '🇰🇼', - 'KY' => '🇰🇾', - Country_Code::KAZAKHSTAN => '🇰🇿', - Country_Code::LAOS => '🇱🇦', - Country_Code::LEBANON => '🇱🇧', - Country_Code::SAINT_LUCIA => '🇱🇨', - Country_Code::LIECHTENSTEIN => '🇱🇮', - Country_Code::SRI_LANKA => '🇱🇰', - Country_Code::LIBERIA => '🇱🇷', - Country_Code::LESOTHO => '🇱🇸', - Country_Code::LITHUANIA => '🇱🇹', - Country_Code::LUXEMBOURG => '🇱🇺', - Country_Code::LATVIA => '🇱🇻', - Country_Code::LIBYA => '🇱🇾', - Country_Code::MOROCCO => '🇲🇦', - Country_Code::MONACO => '🇲🇨', - Country_Code::MOLDOVA => '🇲🇩', - Country_Code::MONTENEGRO => '🇲🇪', - 'MF' => '🇲🇫', - Country_Code::MADAGASCAR => '🇲🇬', - Country_Code::MARSHALL_ISLANDS => '🇲🇭', - Country_Code::NORTH_MACEDONIA => '🇲🇰', - Country_Code::MALI => '🇲🇱', - Country_Code::MYANMAR => '🇲🇲', - Country_Code::MONGOLIA => '🇲🇳', - 'MO' => '🇲🇴', - 'MP' => '🇲🇵', - 'MQ' => '🇲🇶', - Country_Code::MAURITANIA => '🇲🇷', - 'MS' => '🇲🇸', - Country_Code::MALTA => '🇲🇹', - Country_Code::MAURITIUS => '🇲🇺', - Country_Code::MALDIVES => '🇲🇻', - Country_Code::MALAWI => '🇲🇼', - Country_Code::MEXICO => '🇲🇽', - Country_Code::MALAYSIA => '🇲🇾', - Country_Code::MOZAMBIQUE => '🇲🇿', - Country_Code::NAMIBIA => '🇳🇦', - 'NC' => '🇳🇨', - Country_Code::NIGER => '🇳🇪', - 'NF' => '🇳🇫', - Country_Code::NIGERIA => '🇳🇬', - Country_Code::NICARAGUA => '🇳🇮', - Country_Code::NETHERLANDS => '🇳🇱', - Country_Code::NORWAY => '🇳🇴', - Country_Code::NEPAL => '🇳🇵', - Country_Code::NAURU => '🇳🇷', - 'NU' => '🇳🇺', - Country_Code::NEW_ZEALAND => '🇳🇿', - Country_Code::OMAN => '🇴🇲', - Country_Code::PANAMA => '🇵🇦', - Country_Code::PERU => '🇵🇪', - 'PF' => '🇵🇫', - Country_Code::PAPUA_NEW_GUINEA => '🇵🇬', - Country_Code::PHILIPPINES => '🇵🇭', - Country_Code::PAKISTAN => '🇵🇰', - Country_Code::POLAND => '🇵🇱', - 'PM' => '🇵🇲', - 'PN' => '🇵🇳', - 'PR' => '🇵🇷', - Country_Code::PALESTINE => '🇵🇸', - Country_Code::PORTUGAL => '🇵🇹', - Country_Code::PALAU => '🇵🇼', - Country_Code::PARAGUAY => '🇵🇾', - Country_Code::QATAR => '🇶🇦', - 'RE' => '🇷🇪', - Country_Code::ROMANIA => '🇷🇴', - Country_Code::SERBIA => '🇷🇸', - Country_Code::RUSSIA => '🇷🇺', - Country_Code::RWANDA => '🇷🇼', - Country_Code::SAUDI_ARABIA => '🇸🇦', - Country_Code::SOLOMON_ISLANDS => '🇸🇧', - Country_Code::SEYCHELLES => '🇸🇨', - Country_Code::SUDAN => '🇸🇩', - Country_Code::SWEDEN => '🇸🇪', - Country_Code::SINGAPORE => '🇸🇬', - 'SH' => '🇸🇭', - Country_Code::SLOVENIA => '🇸🇮', - 'SJ' => '🇸🇯', - Country_Code::SLOVAKIA => '🇸🇰', - Country_Code::SIERRA_LEONE => '🇸🇱', - Country_Code::SAN_MARINO => '🇸🇲', - Country_Code::SENEGAL => '🇸🇳', - Country_Code::SOMALIA => '🇸🇴', - Country_Code::SURINAME => '🇸🇷', - Country_Code::SOUTH_SUDAN => '🇸🇸', - Country_Code::SAO_TOME_AND_PRINCIPE => '🇸🇹', - Country_Code::EL_SALVADOR => '🇸🇻', - 'SX' => '🇸🇽', - Country_Code::SYRIA => '🇸🇾', - Country_Code::ESWATINI => '🇸🇿', - 'TC' => '🇹🇨', - Country_Code::CHAD => '🇹🇩', - 'TF' => '🇹🇫', - Country_Code::TOGO => '🇹🇬', - Country_Code::THAILAND => '🇹🇭', - Country_Code::TAJIKISTAN => '🇹🇯', - 'TK' => '🇹🇰', - Country_Code::EAST_TIMOR => '🇹🇱', - Country_Code::TURKMENISTAN => '🇹🇲', - Country_Code::TUNISIA => '🇹🇳', - Country_Code::TONGA => '🇹🇴', - Country_Code::TURKEY => '🇹🇷', - Country_Code::TRINIDAD_AND_TOBAGO => '🇹🇹', - Country_Code::TUVALU => '🇹🇻', - Country_Code::TAIWAN => '🇹🇼', - Country_Code::TANZANIA => '🇹🇿', - Country_Code::UKRAINE => '🇺🇦', - Country_Code::UGANDA => '🇺🇬', - 'UM' => '🇺🇲', - Country_Code::UNITED_STATES => '🇺🇸', - Country_Code::URUGUAY => '🇺🇾', - Country_Code::UZBEKISTAN => '🇺🇿', - Country_Code::VATICAN_CITY => '🇻🇦', - Country_Code::SAINT_VINCENT_AND_THE_GRENADINES => '🇻🇨', - Country_Code::VENEZUELA => '🇻🇪', - 'VG' => '🇻🇬', - 'VI' => '🇻🇮', - Country_Code::VIETNAM => '🇻🇳', - Country_Code::VANUATU => '🇻🇺', - 'WF' => '🇼🇫', - Country_Code::SAMOA => '🇼🇸', - Country_Code::KOSOVO => '🇽🇰', - Country_Code::YEMEN => '🇾🇪', - 'YT' => '🇾🇹', - Country_Code::SOUTH_AFRICA => '🇿🇦', - Country_Code::ZAMBIA => '🇿🇲', - Country_Code::ZIMBABWE => '🇿🇼', - ]; - - /** - * Retrieves a flag by country code. - * - * @param string $country country alpha-2 code (ISO 3166) like US. - * @return string - */ - public static function get_by_country( string $country ): string { - return self::EMOJI_COUNTRIES_FLAGS[ $country ] ?? ''; - } - - /** - * Retrieves a flag by currency code. - * - * @param string $currency currency code (ISO 4217) like USD. - * @return string - */ - public static function get_by_currency( string $currency ): string { - $exceptions = [ - Currency_Code::NETHERLANDS_ANTILLEAN_GUILDER => '', - Currency_Code::BITCOIN => '', - Currency_Code::CENTRAL_AFRICAN_CFA_FRANC => '', - Currency_Code::EAST_CARIBBEAN_DOLLAR => '', - Currency_Code::WEST_AFRICAN_CFA_FRANC => '', - Currency_Code::CFP_FRANC => '', - ]; - - $flag = $exceptions[ $currency ] ?? self::get_by_country( substr( $currency, 0, -1 ) ); - - return $flag; - } -} diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index 3ec8bc40e4b..94d5dd6087b 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -21,11 +21,12 @@ use WCPay\Core\Server\Request; use WCPay\Core\Server\Request\List_Fraud_Outcome_Transactions; use WCPay\Exceptions\Cannot_Combine_Currencies_Exception; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; /** * Communicates with WooCommerce Payments API. */ -class WC_Payments_API_Client { +class WC_Payments_API_Client implements MultiCurrencyApiClientInterface { const ENDPOINT_BASE = 'https://public-api.wordpress.com/wpcom/v2'; const ENDPOINT_SITE_FRAGMENT = 'sites/%s'; @@ -191,7 +192,7 @@ public function __construct( $user_agent, $http_client, $wcpay_db ) { * * @return bool */ - public function is_server_connected() { + public function is_server_connected(): bool { return $this->http_client->is_connected(); } @@ -857,7 +858,7 @@ function ( $a, $b ) { * * @throws API_Exception - Error contacting the API. */ - public function get_currency_rates( string $currency_from, $currencies_to = null ) { + public function get_currency_rates( string $currency_from, $currencies_to = null ): array { if ( empty( $currency_from ) ) { throw new API_Exception( __( 'Currency From parameter is required', 'woocommerce-payments' ), diff --git a/client/multi-currency-analytics/index.js b/multi-currency/client/analytics/index.js similarity index 100% rename from client/multi-currency-analytics/index.js rename to multi-currency/client/analytics/index.js diff --git a/client/multi-currency/blocks/currency-switcher.js b/multi-currency/client/blocks/currency-switcher.js similarity index 99% rename from client/multi-currency/blocks/currency-switcher.js rename to multi-currency/client/blocks/currency-switcher.js index 5dee2d130be..75111f42768 100644 --- a/client/multi-currency/blocks/currency-switcher.js +++ b/multi-currency/client/blocks/currency-switcher.js @@ -2,7 +2,10 @@ /** * Internal dependencies */ -import { useEnabledCurrencies, useCurrencies } from 'wcpay/data'; +import { + useEnabledCurrencies, + useCurrencies, +} from 'multi-currency/interface/data'; /** * External dependencies diff --git a/client/components/currency-delete-illustration/index.js b/multi-currency/client/components/currency-delete-illustration/index.js similarity index 100% rename from client/components/currency-delete-illustration/index.js rename to multi-currency/client/components/currency-delete-illustration/index.js diff --git a/client/components/currency-delete-illustration/styles.scss b/multi-currency/client/components/currency-delete-illustration/styles.scss similarity index 100% rename from client/components/currency-delete-illustration/styles.scss rename to multi-currency/client/components/currency-delete-illustration/styles.scss diff --git a/client/multi-currency/preview-modal/index.js b/multi-currency/client/components/preview-modal/index.js similarity index 95% rename from client/multi-currency/preview-modal/index.js rename to multi-currency/client/components/preview-modal/index.js index 9857ddd71e3..60d4fac0a13 100644 --- a/client/multi-currency/preview-modal/index.js +++ b/multi-currency/client/components/preview-modal/index.js @@ -3,7 +3,7 @@ */ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useStoreSettings } from 'wcpay/data'; +import { useStoreSettings } from 'multi-currency/data'; /** * Internal dependencies diff --git a/client/multi-currency/preview-modal/index.scss b/multi-currency/client/components/preview-modal/index.scss similarity index 100% rename from client/multi-currency/preview-modal/index.scss rename to multi-currency/client/components/preview-modal/index.scss diff --git a/client/components/search/index.js b/multi-currency/client/components/search/index.js similarity index 100% rename from client/components/search/index.js rename to multi-currency/client/components/search/index.js diff --git a/client/components/search/style.scss b/multi-currency/client/components/search/style.scss similarity index 100% rename from client/components/search/style.scss rename to multi-currency/client/components/search/style.scss diff --git a/client/components/search/test/__snapshots__/index.js.snap b/multi-currency/client/components/search/test/__snapshots__/index.js.snap similarity index 100% rename from client/components/search/test/__snapshots__/index.js.snap rename to multi-currency/client/components/search/test/__snapshots__/index.js.snap diff --git a/client/components/search/test/index.js b/multi-currency/client/components/search/test/index.js similarity index 100% rename from client/components/search/test/index.js rename to multi-currency/client/components/search/test/index.js diff --git a/client/multi-currency/context.js b/multi-currency/client/context.js similarity index 100% rename from client/multi-currency/context.js rename to multi-currency/client/context.js diff --git a/client/data/multi-currency/action-types.js b/multi-currency/client/data/action-types.js similarity index 100% rename from client/data/multi-currency/action-types.js rename to multi-currency/client/data/action-types.js diff --git a/client/data/multi-currency/actions.js b/multi-currency/client/data/actions.js similarity index 96% rename from client/data/multi-currency/actions.js rename to multi-currency/client/data/actions.js index 7c28cf73781..0b822dde4cb 100644 --- a/client/data/multi-currency/actions.js +++ b/multi-currency/client/data/actions.js @@ -6,13 +6,13 @@ import { apiFetch } from '@wordpress/data-controls'; import { dispatch, select } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { recordEvent } from 'tracks'; /** * Internal Dependencies */ +import { recordEvent } from 'multi-currency/interface/functions'; import TYPES from './action-types'; -import { NAMESPACE, STORE_NAME } from '../constants'; +import { NAMESPACE, STORE_NAME } from './constants'; export function updateCurrencies( data ) { return { diff --git a/multi-currency/client/data/constants.js b/multi-currency/client/data/constants.js new file mode 100644 index 00000000000..eaee38c6ee6 --- /dev/null +++ b/multi-currency/client/data/constants.js @@ -0,0 +1,4 @@ +/** @format */ + +export const NAMESPACE = '/wc/v3/payments'; +export const STORE_NAME = 'wc/payments/multi-currency'; diff --git a/client/data/multi-currency/hooks.js b/multi-currency/client/data/hooks.js similarity index 96% rename from client/data/multi-currency/hooks.js rename to multi-currency/client/data/hooks.js index aa39e24ee6f..ea5c23e5e75 100644 --- a/client/data/multi-currency/hooks.js +++ b/multi-currency/client/data/hooks.js @@ -4,7 +4,11 @@ * External dependencies */ import { useSelect, useDispatch, dispatch } from '@wordpress/data'; -import { STORE_NAME } from '../constants'; + +/** + * Internal dependencies + */ +import { STORE_NAME } from './constants'; export const useCurrencies = () => useSelect( ( select ) => { diff --git a/multi-currency/client/data/index.ts b/multi-currency/client/data/index.ts new file mode 100644 index 00000000000..f1d8d84a456 --- /dev/null +++ b/multi-currency/client/data/index.ts @@ -0,0 +1,20 @@ +/** @format */ + +/** + * Internal dependencies + */ +import { STORE_NAME } from './constants'; +import { initStore } from './store'; + +initStore(); + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const WCPAY_STORE_NAME = STORE_NAME; + +// We only ask for hooks when importing directly from 'multi-currency/data'. +import * as selectors from './selectors'; +import * as actions from './actions'; +import * as resolvers from './resolvers'; + +export { selectors, actions, resolvers }; +export * from './hooks'; diff --git a/client/data/multi-currency/reducer.js b/multi-currency/client/data/reducer.js similarity index 100% rename from client/data/multi-currency/reducer.js rename to multi-currency/client/data/reducer.js diff --git a/client/data/multi-currency/resolvers.js b/multi-currency/client/data/resolvers.js similarity index 93% rename from client/data/multi-currency/resolvers.js rename to multi-currency/client/data/resolvers.js index 4eada7da994..78168ad708a 100644 --- a/client/data/multi-currency/resolvers.js +++ b/multi-currency/client/data/resolvers.js @@ -10,7 +10,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { NAMESPACE } from '../constants'; +import { NAMESPACE } from './constants'; import { updateCurrencies, updateCurrencySettings, @@ -34,7 +34,7 @@ export function* getCurrencies() { } /** - * Retrieve single currency sttings from the site's REST API. + * Retrieve single currency settings from the site's REST API. * * @param {string} currencyCode The currency code to fetch settings for. */ diff --git a/client/data/multi-currency/selectors.js b/multi-currency/client/data/selectors.js similarity index 96% rename from client/data/multi-currency/selectors.js rename to multi-currency/client/data/selectors.js index 6c2a2b20f97..825a9271db0 100644 --- a/client/data/multi-currency/selectors.js +++ b/multi-currency/client/data/selectors.js @@ -13,7 +13,7 @@ const getMultiCurrencyState = ( state ) => { return {}; } - return state.multiCurrency || {}; + return state || {}; }; export const getCurrencies = ( state ) => { diff --git a/multi-currency/client/data/store.js b/multi-currency/client/data/store.js new file mode 100644 index 00000000000..2e142e66f9c --- /dev/null +++ b/multi-currency/client/data/store.js @@ -0,0 +1,28 @@ +/* + * External dependencies + */ +import { registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { STORE_NAME } from './constants'; +import * as multiCurrency from './'; +import reducer from './reducer'; + +// Extracted into wrapper function to facilitate testing. +export const initStore = () => + registerStore( STORE_NAME, { + reducer, + actions: { + ...multiCurrency.actions, + }, + controls, + selectors: { + ...multiCurrency.selectors, + }, + resolvers: { + ...multiCurrency.resolvers, + }, + } ); diff --git a/client/multi-currency/index.js b/multi-currency/client/index.js similarity index 91% rename from client/multi-currency/index.js rename to multi-currency/client/index.js index a479c326914..f4daf3b9a3a 100644 --- a/client/multi-currency/index.js +++ b/multi-currency/client/index.js @@ -7,8 +7,8 @@ import ReactDOM from 'react-dom'; /** * Internal dependencies */ -import MultiCurrencySettings from './multi-currency-settings'; -import SingleCurrencySettings from './single-currency-settings'; +import MultiCurrencySettings from './settings/multi-currency'; +import SingleCurrencySettings from './settings/single-currency'; import MultiCurrencySettingsContext from './context'; const MultiCurrencySettingsPage = () => { diff --git a/multi-currency/client/interface/assets.js b/multi-currency/client/interface/assets.js new file mode 100644 index 00000000000..3a3a095891a --- /dev/null +++ b/multi-currency/client/interface/assets.js @@ -0,0 +1,6 @@ +/** + * External Dependencies + */ +import paymentMethodsMap from 'wcpay/payment-methods-map'; + +export { paymentMethodsMap }; diff --git a/multi-currency/client/interface/components.js b/multi-currency/client/interface/components.js new file mode 100644 index 00000000000..eddab5ae84e --- /dev/null +++ b/multi-currency/client/interface/components.js @@ -0,0 +1,23 @@ +/** + * Dependencies from WooPayments to MCCY. + */ +// wcpay/additional-methods-setup/* +export { default as CollapsibleBody } from 'wcpay/additional-methods-setup/wizard/collapsible-body'; +export { default as Wizard } from 'wcpay/additional-methods-setup/wizard/wrapper'; +export { default as WizardTask } from 'wcpay/additional-methods-setup/wizard/task'; +export { default as WizardTaskItem } from 'wcpay/additional-methods-setup/wizard/task-item'; +export { default as WizardTaskList } from 'wcpay/additional-methods-setup/wizard/task-list'; +// wcpay/components/* +export { default as ConfirmationModal } from 'wcpay/components/confirmation-modal'; +export { default as Page } from 'wcpay/components/page'; +export { LoadableBlock } from 'wcpay/components/loadable'; +// wcpay/settings/* +export { default as PaymentMethodIcon } from 'wcpay/settings/payment-method-icon'; +export { default as SettingsLayout } from 'wcpay/settings/settings-layout'; +export { default as SettingsSection } from 'wcpay/settings/settings-section'; + +/** + * Dependencies from MCCY to WooPayments. + */ +// multi-currency/setup +export { default as MultiCurrencySetupPage } from 'multi-currency/setup'; diff --git a/multi-currency/client/interface/data.js b/multi-currency/client/interface/data.js new file mode 100644 index 00000000000..af162ddd6ed --- /dev/null +++ b/multi-currency/client/interface/data.js @@ -0,0 +1,10 @@ +/** + * Dependencies from WooPayments to MCCY. + */ +// wcpay/data +export { useSettings, useMultiCurrency } from 'wcpay/data'; + +/** + * Dependencies from MCCY to WooPayments. + */ +export { useCurrencies, useEnabledCurrencies } from 'multi-currency/data'; diff --git a/multi-currency/client/interface/functions.js b/multi-currency/client/interface/functions.js new file mode 100644 index 00000000000..5ffed26e00d --- /dev/null +++ b/multi-currency/client/interface/functions.js @@ -0,0 +1,25 @@ +/** + * Dependencies from WooPayments to MCCY. + */ +// wcpay/tracks +export { recordEvent } from 'wcpay/tracks'; +// wcpay/settings +export { default as WCPaySettingsContext } from 'wcpay/settings/wcpay-settings-context'; +// wcpay/additional-methods-setup/* +export { default as WizardTaskContext } from 'wcpay/additional-methods-setup/wizard/task/context'; +// wcpay/utils/* +export { formatListOfItems } from 'wcpay/utils/format-list-of-items'; + +/** + * Dependencies from MCCY to WooPayments. + */ +export { getMissingCurrenciesTooltipMessage } from 'multi-currency/utils/missing-currencies-message'; +export { + formatCurrency, + formatCurrencyName, + formatFX, + formatExplicitCurrency, + formatExportAmount, + getCurrency, + isZeroDecimalCurrency, +} from 'multi-currency/utils/currency'; diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/delete-button.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/delete-button.js similarity index 92% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/delete-button.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/delete-button.js index cc58435ed22..ad99c04d327 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/delete-button.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/delete-button.js @@ -6,10 +6,12 @@ import { __, sprintf } from '@wordpress/i18n'; import { Button, Icon } from '@wordpress/components'; import interpolateComponents from '@automattic/interpolate-components'; import { useCallback, useState } from '@wordpress/element'; -import ConfirmationModal from 'wcpay/components/confirmation-modal'; -import CurrencyDeleteIllustration from 'wcpay/components/currency-delete-illustration'; -import PaymentMethodIcon from 'wcpay/settings/payment-method-icon'; -import paymentMethodsMap from 'wcpay/payment-methods-map'; +import { + ConfirmationModal, + PaymentMethodIcon, +} from 'multi-currency/interface/components'; +import CurrencyDeleteIllustration from 'multi-currency/components/currency-delete-illustration'; +import { paymentMethodsMap } from 'multi-currency/interface/assets'; const DeleteButton = ( { code, label, symbol, onClick, className } ) => { const [ isConfirmationModalOpen, setIsConfirmationModalOpen ] = useState( diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/index.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/index.js similarity index 97% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/index.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/index.js index 8d08b90ef11..00ac75e0c6e 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/index.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/index.js @@ -15,13 +15,13 @@ import { useCurrencies, useDefaultCurrency, useEnabledCurrencies, -} from 'wcpay/data'; +} from 'multi-currency/data'; import EnabledCurrenciesList from './list'; import EnabledCurrenciesListItem from './list-item'; import EnabledCurrenciesListItemPlaceholder from './list-item-placeholder'; import EnabledCurrenciesModal from './modal'; -import SettingsSection from 'wcpay/settings/settings-section'; +import { SettingsSection } from 'multi-currency/interface/components'; const EnabledCurrenciesSettingsDescription = () => { const LEARN_MORE_URL = diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item-placeholder.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item-placeholder.js similarity index 94% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item-placeholder.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item-placeholder.js index 6a44378e72a..c29d2902a8b 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item-placeholder.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item-placeholder.js @@ -3,7 +3,7 @@ * External dependencies */ import classNames from 'classnames'; -import { LoadableBlock } from 'wcpay/components/loadable'; +import { LoadableBlock } from 'multi-currency/interface/components'; const EnabledCurrenciesListItemPlaceholder = ( { isLoading } ) => { return ( diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item.js similarity index 97% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item.js index 135e95a0a94..0b8d0b14689 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/list-item.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/list-item.js @@ -10,7 +10,7 @@ import { Button } from '@wordpress/components'; * Internal dependencies */ import DeleteButton from './delete-button'; -import MultiCurrencySettingsContext from '../../context'; +import MultiCurrencySettingsContext from 'multi-currency/context'; import { useContext } from 'react'; const EnabledCurrenciesListItem = ( { diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/list.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/list.js similarity index 100% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/list.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/list.js diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox-list.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/modal-checkbox-list.js similarity index 100% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox-list.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/modal-checkbox-list.js diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/modal-checkbox.js similarity index 100% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/modal-checkbox.js diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/modal.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/modal.js similarity index 97% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/modal.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/modal.js index 619e51a01c5..13610e78c17 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/modal.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/modal.js @@ -13,11 +13,11 @@ import { useAvailableCurrencies, useEnabledCurrencies, useDefaultCurrency, -} from 'wcpay/data'; +} from 'multi-currency/data'; import EnabledCurrenciesModalCheckboxList from './modal-checkbox-list'; import EnabledCurrenciesModalCheckbox from './modal-checkbox'; -import ConfirmationModal from 'wcpay/components/confirmation-modal'; -import Search from 'components/search'; +import { ConfirmationModal } from 'multi-currency/interface/components'; +import Search from 'multi-currency/components/search'; import './style.scss'; // TODO: This works when saving, but list does not refresh. diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/style.scss b/multi-currency/client/settings/multi-currency/enabled-currencies-list/style.scss similarity index 100% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/style.scss rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/style.scss diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/test/__snapshots__/index.js.snap b/multi-currency/client/settings/multi-currency/enabled-currencies-list/test/__snapshots__/index.js.snap similarity index 100% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/test/__snapshots__/index.js.snap rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/test/__snapshots__/index.js.snap diff --git a/client/multi-currency/multi-currency-settings/enabled-currencies-list/test/index.js b/multi-currency/client/settings/multi-currency/enabled-currencies-list/test/index.js similarity index 97% rename from client/multi-currency/multi-currency-settings/enabled-currencies-list/test/index.js rename to multi-currency/client/settings/multi-currency/enabled-currencies-list/test/index.js index 38e15b1b04b..fb4e89bd92c 100644 --- a/client/multi-currency/multi-currency-settings/enabled-currencies-list/test/index.js +++ b/multi-currency/client/settings/multi-currency/enabled-currencies-list/test/index.js @@ -14,13 +14,13 @@ import { useCurrencies, useDefaultCurrency, useEnabledCurrencies, -} from 'wcpay/data'; +} from 'multi-currency/data'; -import MultiCurrencySettingsContext from '../../../context'; +import MultiCurrencySettingsContext from 'multi-currency/context'; -jest.mock( 'wcpay/data', () => ( { - useCurrencies: jest.fn(), +jest.mock( 'multi-currency/data', () => ( { useAvailableCurrencies: jest.fn(), + useCurrencies: jest.fn(), useDefaultCurrency: jest.fn(), useEnabledCurrencies: jest.fn(), } ) ); diff --git a/client/multi-currency/multi-currency-settings/index.js b/multi-currency/client/settings/multi-currency/index.js similarity index 87% rename from client/multi-currency/multi-currency-settings/index.js rename to multi-currency/client/settings/multi-currency/index.js index 89ee0fd6361..a2caf57151e 100644 --- a/client/multi-currency/multi-currency-settings/index.js +++ b/multi-currency/client/settings/multi-currency/index.js @@ -6,7 +6,7 @@ import React from 'react'; /** * Internal dependencies */ -import SettingsLayout from '../../settings/settings-layout'; +import { SettingsLayout } from 'multi-currency/interface/components'; import EnabledCurrenciesList from './enabled-currencies-list'; import StoreSettings from './store-settings'; import './style.scss'; diff --git a/client/multi-currency/multi-currency-settings/store-settings/index.js b/multi-currency/client/settings/multi-currency/store-settings/index.js similarity index 95% rename from client/multi-currency/multi-currency-settings/store-settings/index.js rename to multi-currency/client/settings/multi-currency/store-settings/index.js index eddeb570ccf..c858f91eebe 100644 --- a/client/multi-currency/multi-currency-settings/store-settings/index.js +++ b/multi-currency/client/settings/multi-currency/store-settings/index.js @@ -11,10 +11,12 @@ import { createInterpolateElement } from '@wordpress/element'; */ import './style.scss'; -import { useStoreSettings } from 'wcpay/data'; -import SettingsSection from 'wcpay/settings/settings-section'; -import { LoadableBlock } from 'wcpay/components/loadable'; -import PreviewModal from 'wcpay/multi-currency/preview-modal'; +import { useStoreSettings } from 'multi-currency/data'; +import { + LoadableBlock, + SettingsSection, +} from 'multi-currency/interface/components'; +import PreviewModal from 'multi-currency/components/preview-modal'; const StoreSettingsDescription = () => { const LEARN_MORE_URL = diff --git a/client/multi-currency/multi-currency-settings/store-settings/style.scss b/multi-currency/client/settings/multi-currency/store-settings/style.scss similarity index 100% rename from client/multi-currency/multi-currency-settings/store-settings/style.scss rename to multi-currency/client/settings/multi-currency/store-settings/style.scss diff --git a/client/multi-currency/multi-currency-settings/store-settings/test/__snapshots__/index.test.js.snap b/multi-currency/client/settings/multi-currency/store-settings/test/__snapshots__/index.test.js.snap similarity index 100% rename from client/multi-currency/multi-currency-settings/store-settings/test/__snapshots__/index.test.js.snap rename to multi-currency/client/settings/multi-currency/store-settings/test/__snapshots__/index.test.js.snap diff --git a/client/multi-currency/multi-currency-settings/store-settings/test/index.test.js b/multi-currency/client/settings/multi-currency/store-settings/test/index.test.js similarity index 95% rename from client/multi-currency/multi-currency-settings/store-settings/test/index.test.js rename to multi-currency/client/settings/multi-currency/store-settings/test/index.test.js index 12d316d017f..ae7102b9cb5 100644 --- a/client/multi-currency/multi-currency-settings/store-settings/test/index.test.js +++ b/multi-currency/client/settings/multi-currency/store-settings/test/index.test.js @@ -7,10 +7,10 @@ import { render, screen, fireEvent } from '@testing-library/react'; /** * Internal dependencies */ -import { useStoreSettings } from 'wcpay/data'; +import { useStoreSettings } from 'multi-currency/data'; import StoreSettings from '..'; -jest.mock( 'wcpay/data', () => ( { +jest.mock( 'multi-currency/data', () => ( { useStoreSettings: jest.fn(), } ) ); diff --git a/client/multi-currency/multi-currency-settings/style.scss b/multi-currency/client/settings/multi-currency/style.scss similarity index 100% rename from client/multi-currency/multi-currency-settings/style.scss rename to multi-currency/client/settings/multi-currency/style.scss diff --git a/client/multi-currency/multi-currency-settings/test/__snapshots__/index.test.js.snap b/multi-currency/client/settings/multi-currency/test/__snapshots__/index.test.js.snap similarity index 100% rename from client/multi-currency/multi-currency-settings/test/__snapshots__/index.test.js.snap rename to multi-currency/client/settings/multi-currency/test/__snapshots__/index.test.js.snap diff --git a/client/multi-currency/multi-currency-settings/test/index.test.js b/multi-currency/client/settings/multi-currency/test/index.test.js similarity index 92% rename from client/multi-currency/multi-currency-settings/test/index.test.js rename to multi-currency/client/settings/multi-currency/test/index.test.js index 5940e763cc8..3acbfb022c0 100644 --- a/client/multi-currency/multi-currency-settings/test/index.test.js +++ b/multi-currency/client/settings/multi-currency/test/index.test.js @@ -7,7 +7,7 @@ import { render } from '@testing-library/react'; /** * Internal dependencies */ -import SettingsLayout from '../../../settings/settings-layout'; +import { SettingsLayout } from 'multi-currency/interface/components'; import EnabledCurrenciesList from '../enabled-currencies-list'; import StoreSettings from '../store-settings'; diff --git a/client/multi-currency/single-currency-settings/constants.js b/multi-currency/client/settings/single-currency/constants.js similarity index 100% rename from client/multi-currency/single-currency-settings/constants.js rename to multi-currency/client/settings/single-currency/constants.js diff --git a/client/multi-currency/single-currency-settings/currency-preview.js b/multi-currency/client/settings/single-currency/currency-preview.js similarity index 96% rename from client/multi-currency/single-currency-settings/currency-preview.js rename to multi-currency/client/settings/single-currency/currency-preview.js index 952bc537363..8f16f4c5596 100644 --- a/client/multi-currency/single-currency-settings/currency-preview.js +++ b/multi-currency/client/settings/single-currency/currency-preview.js @@ -6,7 +6,10 @@ import React, { useCallback, useEffect, useState } from 'react'; import { __ } from '@wordpress/i18n'; import { Card, CardBody } from '@wordpress/components'; import { TextControlWithAffixes } from '@woocommerce/components'; -import { formatCurrency, isZeroDecimalCurrency } from 'wcpay/utils/currency'; +import { + formatCurrency, + isZeroDecimalCurrency, +} from 'multi-currency/utils/currency'; const CurrencyPreview = ( { storeCurrency, diff --git a/client/multi-currency/single-currency-settings/index.js b/multi-currency/client/settings/single-currency/index.js similarity index 98% rename from client/multi-currency/single-currency-settings/index.js rename to multi-currency/client/settings/single-currency/index.js index 8285f206dd8..06b989260f1 100644 --- a/client/multi-currency/single-currency-settings/index.js +++ b/multi-currency/client/settings/single-currency/index.js @@ -5,8 +5,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { dateI18n } from '@wordpress/date'; import { sprintf, __ } from '@wordpress/i18n'; -import SettingsLayout from 'wcpay/settings/settings-layout'; -import SettingsSection from 'wcpay/settings/settings-section'; import moment from 'moment'; /** @@ -27,9 +25,13 @@ import { useCurrencySettings, useEnabledCurrencies, useStoreSettings, -} from 'wcpay/data'; -import MultiCurrencySettingsContext from '../context'; -import { LoadableBlock } from 'wcpay/components/loadable'; +} from 'multi-currency/data'; +import MultiCurrencySettingsContext from 'multi-currency/context'; +import { + LoadableBlock, + SettingsLayout, + SettingsSection, +} from 'multi-currency/interface/components'; const SingleCurrencySettings = () => { const { diff --git a/client/multi-currency/single-currency-settings/style.scss b/multi-currency/client/settings/single-currency/style.scss similarity index 100% rename from client/multi-currency/single-currency-settings/style.scss rename to multi-currency/client/settings/single-currency/style.scss diff --git a/client/multi-currency/single-currency-settings/test/__snapshots__/index.test.js.snap b/multi-currency/client/settings/single-currency/test/__snapshots__/index.test.js.snap similarity index 100% rename from client/multi-currency/single-currency-settings/test/__snapshots__/index.test.js.snap rename to multi-currency/client/settings/single-currency/test/__snapshots__/index.test.js.snap diff --git a/client/multi-currency/single-currency-settings/test/index.test.js b/multi-currency/client/settings/single-currency/test/index.test.js similarity index 97% rename from client/multi-currency/single-currency-settings/test/index.test.js rename to multi-currency/client/settings/single-currency/test/index.test.js index 5f2597dff2c..0559953e54b 100644 --- a/client/multi-currency/single-currency-settings/test/index.test.js +++ b/multi-currency/client/settings/single-currency/test/index.test.js @@ -15,11 +15,11 @@ import { useEnabledCurrencies, useCurrencySettings, useStoreSettings, -} from 'wcpay/data'; +} from 'multi-currency/data'; -import MultiCurrencySettingsContext from '../../context'; +import MultiCurrencySettingsContext from 'multi-currency/context'; -jest.mock( 'wcpay/data', () => ( { +jest.mock( 'multi-currency/data', () => ( { useCurrencies: jest.fn(), useAvailableCurrencies: jest.fn(), useDefaultCurrency: jest.fn(), diff --git a/client/multi-currency-setup/index.js b/multi-currency/client/setup/index.js similarity index 77% rename from client/multi-currency-setup/index.js rename to multi-currency/client/setup/index.js index 512ab078182..05e76eb67ce 100644 --- a/client/multi-currency-setup/index.js +++ b/multi-currency/client/setup/index.js @@ -3,9 +3,9 @@ /** * Internal dependencies */ -import Page from 'components/page'; import MultiCurrencySetup from './tasks/multi-currency-setup'; -import WCPaySettingsContext from '../settings/wcpay-settings-context'; +import { Page } from 'multi-currency/interface/components'; +import { WCPaySettingsContext } from 'multi-currency/interface/functions'; const MultiCurrencySetupPage = () => { const { isSetupCompleted } = window.wcpaySettings.multiCurrencySetup; diff --git a/client/multi-currency-setup/tasks/add-currencies-task/constants.js b/multi-currency/client/setup/tasks/add-currencies-task/constants.js similarity index 100% rename from client/multi-currency-setup/tasks/add-currencies-task/constants.js rename to multi-currency/client/setup/tasks/add-currencies-task/constants.js diff --git a/client/multi-currency-setup/tasks/add-currencies-task/index.js b/multi-currency/client/setup/tasks/add-currencies-task/index.js similarity index 93% rename from client/multi-currency-setup/tasks/add-currencies-task/index.js rename to multi-currency/client/setup/tasks/add-currencies-task/index.js index 1795edaa6a0..f33fb5e95a3 100644 --- a/client/multi-currency-setup/tasks/add-currencies-task/index.js +++ b/multi-currency/client/setup/tasks/add-currencies-task/index.js @@ -10,23 +10,24 @@ import _ from 'lodash'; /** * Internal dependencies */ -import WizardTaskContext from '../../../additional-methods-setup/wizard/task/context'; -import CollapsibleBody from '../../../additional-methods-setup/wizard/collapsible-body'; -import WizardTaskItem from '../../wizard/task-item'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; +import Search from 'multi-currency/components/search'; +import { + CollapsibleBody, + LoadableBlock, + WizardTaskItem, +} from 'multi-currency/interface/components'; import { useCurrencies, useAvailableCurrencies, useEnabledCurrencies, useDefaultCurrency, -} from 'wcpay/data'; +} from 'multi-currency/data'; // eslint-disable-next-line max-len -import EnabledCurrenciesModalCheckboxList from '../../../multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox-list'; -import EnabledCurrenciesModalCheckbox from '../../../multi-currency/multi-currency-settings/enabled-currencies-list/modal-checkbox'; -import Search from 'components/search'; - -import { LoadableBlock } from '../../../components/loadable'; +import EnabledCurrenciesModalCheckboxList from 'multi-currency/settings/multi-currency/enabled-currencies-list/modal-checkbox-list'; +import EnabledCurrenciesModalCheckbox from 'multi-currency/settings/multi-currency/enabled-currencies-list/modal-checkbox'; import { recommendedCurrencyCodes, numberWords } from './constants'; import { diff --git a/client/multi-currency-setup/tasks/add-currencies-task/index.scss b/multi-currency/client/setup/tasks/add-currencies-task/index.scss similarity index 100% rename from client/multi-currency-setup/tasks/add-currencies-task/index.scss rename to multi-currency/client/setup/tasks/add-currencies-task/index.scss diff --git a/client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap b/multi-currency/client/setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap similarity index 100% rename from client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap rename to multi-currency/client/setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap diff --git a/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js b/multi-currency/client/setup/tasks/add-currencies-task/test/index.test.js similarity index 97% rename from client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js rename to multi-currency/client/setup/tasks/add-currencies-task/test/index.test.js index f58beb09ced..7041543dadc 100644 --- a/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js +++ b/multi-currency/client/setup/tasks/add-currencies-task/test/index.test.js @@ -9,23 +9,25 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import AddCurrenciesTask from '..'; +import { useSettings } from 'multi-currency/interface/data'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; import { useCurrencies, useAvailableCurrencies, useDefaultCurrency, useEnabledCurrencies, - useSettings, -} from 'wcpay/data'; +} from 'multi-currency/data'; -import WizardTaskContext from '../../../../additional-methods-setup/wizard/task/context'; import { recommendedCurrencyCodes } from '../constants'; import { __ } from '@wordpress/i18n'; -jest.mock( 'wcpay/data', () => ( { +jest.mock( 'multi-currency/data', () => ( { useCurrencies: jest.fn(), useAvailableCurrencies: jest.fn(), useDefaultCurrency: jest.fn(), useEnabledCurrencies: jest.fn(), +} ) ); +jest.mock( 'multi-currency/interface/data', () => ( { useSettings: jest.fn(), } ) ); diff --git a/client/multi-currency-setup/tasks/add-currencies-task/test/utils.test.js b/multi-currency/client/setup/tasks/add-currencies-task/test/utils.test.js similarity index 100% rename from client/multi-currency-setup/tasks/add-currencies-task/test/utils.test.js rename to multi-currency/client/setup/tasks/add-currencies-task/test/utils.test.js diff --git a/client/multi-currency-setup/tasks/add-currencies-task/utils.js b/multi-currency/client/setup/tasks/add-currencies-task/utils.js similarity index 100% rename from client/multi-currency-setup/tasks/add-currencies-task/utils.js rename to multi-currency/client/setup/tasks/add-currencies-task/utils.js diff --git a/client/multi-currency-setup/tasks/multi-currency-setup.js b/multi-currency/client/setup/tasks/multi-currency-setup.js similarity index 84% rename from client/multi-currency-setup/tasks/multi-currency-setup.js rename to multi-currency/client/setup/tasks/multi-currency-setup.js index 77263d9bf33..cbeb6387bda 100644 --- a/client/multi-currency-setup/tasks/multi-currency-setup.js +++ b/multi-currency/client/setup/tasks/multi-currency-setup.js @@ -7,9 +7,11 @@ import { Card, CardBody } from '@wordpress/components'; /** * Internal dependencies */ -import Wizard from '../../additional-methods-setup/wizard/wrapper'; -import WizardTask from '../../additional-methods-setup/wizard/task'; -import WizardTaskList from '../../additional-methods-setup/wizard/task-list'; +import { + Wizard, + WizardTask, + WizardTaskList, +} from 'multi-currency/interface/components'; import StoreSettingsTask from './store-settings-task'; import SetupCompleteTask from './setup-complete-task'; import AddCurrenciesTask from './add-currencies-task'; diff --git a/client/multi-currency-setup/tasks/multi-currency-setup.scss b/multi-currency/client/setup/tasks/multi-currency-setup.scss similarity index 100% rename from client/multi-currency-setup/tasks/multi-currency-setup.scss rename to multi-currency/client/setup/tasks/multi-currency-setup.scss diff --git a/client/multi-currency-setup/tasks/setup-complete-task/index.js b/multi-currency/client/setup/tasks/setup-complete-task/index.js similarity index 89% rename from client/multi-currency-setup/tasks/setup-complete-task/index.js rename to multi-currency/client/setup/tasks/setup-complete-task/index.js index e846498df0e..6c467ec3a08 100644 --- a/client/multi-currency-setup/tasks/setup-complete-task/index.js +++ b/multi-currency/client/setup/tasks/setup-complete-task/index.js @@ -10,13 +10,15 @@ import { useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import CollapsibleBody from '../../../additional-methods-setup/wizard/collapsible-body'; -import WizardTaskItem from '../../wizard/task-item'; -import WizardTaskContext from '../../../additional-methods-setup/wizard/task/context'; +import { + CollapsibleBody, + WizardTaskItem, +} from 'multi-currency/interface/components'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; import './index.scss'; -import { useDefaultCurrency } from 'wcpay/data'; +import { useDefaultCurrency } from 'multi-currency/data'; const SetupComplete = () => { const { isActive } = useContext( WizardTaskContext ); diff --git a/client/multi-currency-setup/tasks/setup-complete-task/index.scss b/multi-currency/client/setup/tasks/setup-complete-task/index.scss similarity index 100% rename from client/multi-currency-setup/tasks/setup-complete-task/index.scss rename to multi-currency/client/setup/tasks/setup-complete-task/index.scss diff --git a/client/multi-currency-setup/tasks/setup-complete-task/test/index.test.js b/multi-currency/client/setup/tasks/setup-complete-task/test/index.test.js similarity index 87% rename from client/multi-currency-setup/tasks/setup-complete-task/test/index.test.js rename to multi-currency/client/setup/tasks/setup-complete-task/test/index.test.js index 48c4ac16d7a..a7a9db81872 100644 --- a/client/multi-currency-setup/tasks/setup-complete-task/test/index.test.js +++ b/multi-currency/client/setup/tasks/setup-complete-task/test/index.test.js @@ -6,14 +6,15 @@ import { render } from '@testing-library/react'; /** * Internal dependencies */ -import WizardTaskContext from '../../../../additional-methods-setup/wizard/task/context'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; import SetupCompleteTask from '../../setup-complete-task'; jest.mock( '@wordpress/data', () => ( { useDispatch: jest.fn().mockReturnValue( { updateOptions: jest.fn() } ), } ) ); -jest.mock( 'wcpay/data', () => ( { +jest.mock( 'multi-currency/interface/data', () => ( {} ) ); +jest.mock( 'multi-currency/data', () => ( { useDefaultCurrency: jest.fn().mockReturnValue( { code: 'USD', rate: 1, diff --git a/client/multi-currency-setup/tasks/store-settings-task/index.js b/multi-currency/client/setup/tasks/store-settings-task/index.js similarity index 92% rename from client/multi-currency-setup/tasks/store-settings-task/index.js rename to multi-currency/client/setup/tasks/store-settings-task/index.js index 7e20fc0f3e4..9a3ca8f0666 100644 --- a/client/multi-currency-setup/tasks/store-settings-task/index.js +++ b/multi-currency/client/setup/tasks/store-settings-task/index.js @@ -9,13 +9,16 @@ import interpolateComponents from '@automattic/interpolate-components'; /** * Internal dependencies */ -import WizardTaskContext from '../../../additional-methods-setup/wizard/task/context'; -import CollapsibleBody from '../../../additional-methods-setup/wizard/collapsible-body'; -import WizardTaskItem from '../../wizard/task-item'; -import PreviewModal from '../../../multi-currency/preview-modal'; +import { + CollapsibleBody, + WizardTaskItem, +} from 'multi-currency/interface/components'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; +import { useSettings, useMultiCurrency } from 'multi-currency/interface/data'; +import PreviewModal from 'multi-currency/components/preview-modal'; import './index.scss'; -import { useStoreSettings, useSettings, useMultiCurrency } from 'wcpay/data'; +import { useStoreSettings } from 'multi-currency/data'; const StoreSettingsTask = () => { const { storeSettings, submitStoreSettingsUpdate } = useStoreSettings(); diff --git a/client/multi-currency-setup/tasks/store-settings-task/index.scss b/multi-currency/client/setup/tasks/store-settings-task/index.scss similarity index 100% rename from client/multi-currency-setup/tasks/store-settings-task/index.scss rename to multi-currency/client/setup/tasks/store-settings-task/index.scss diff --git a/client/multi-currency-setup/tasks/store-settings-task/test/__snapshots__/index.test.js.snap b/multi-currency/client/setup/tasks/store-settings-task/test/__snapshots__/index.test.js.snap similarity index 100% rename from client/multi-currency-setup/tasks/store-settings-task/test/__snapshots__/index.test.js.snap rename to multi-currency/client/setup/tasks/store-settings-task/test/__snapshots__/index.test.js.snap diff --git a/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js b/multi-currency/client/setup/tasks/store-settings-task/test/index.test.js similarity index 92% rename from client/multi-currency-setup/tasks/store-settings-task/test/index.test.js rename to multi-currency/client/setup/tasks/store-settings-task/test/index.test.js index e81b7b08f99..d80e8f8d12a 100644 --- a/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js +++ b/multi-currency/client/setup/tasks/store-settings-task/test/index.test.js @@ -7,21 +7,21 @@ import { render, screen, fireEvent } from '@testing-library/react'; /** * Internal dependencies */ -import WizardTaskContext from '../../../../additional-methods-setup/wizard/task/context'; -import { - useCurrencies, - useStoreSettings, - useSettings, - useMultiCurrency, -} from 'wcpay/data'; +import { useCurrencies, useStoreSettings } from 'multi-currency/data'; +import { useSettings, useMultiCurrency } from 'multi-currency/interface/data'; +import { WizardTaskContext } from 'multi-currency/interface/functions'; import StoreSettingsTask from '..'; -jest.mock( 'wcpay/data', () => ( { +jest.mock( 'multi-currency/data', () => ( { useStoreSettings: jest.fn(), useCurrencies: jest.fn(), useSettings: jest.fn(), useMultiCurrency: jest.fn(), } ) ); +jest.mock( 'multi-currency/interface/data', () => ( { + useSettings: jest.fn(), + useMultiCurrency: jest.fn(), +} ) ); const changeableSettings = [ 'enable_storefront_switcher', diff --git a/client/multi-currency-setup/tasks/test/__snapshots__/multi-currency-setup.test.js.snap b/multi-currency/client/setup/tasks/test/__snapshots__/multi-currency-setup.test.js.snap similarity index 100% rename from client/multi-currency-setup/tasks/test/__snapshots__/multi-currency-setup.test.js.snap rename to multi-currency/client/setup/tasks/test/__snapshots__/multi-currency-setup.test.js.snap diff --git a/client/multi-currency-setup/tasks/test/multi-currency-setup.test.js b/multi-currency/client/setup/tasks/test/multi-currency-setup.test.js similarity index 100% rename from client/multi-currency-setup/tasks/test/multi-currency-setup.test.js rename to multi-currency/client/setup/tasks/test/multi-currency-setup.test.js diff --git a/client/multi-currency-setup/test/index.js b/multi-currency/client/setup/test/index.js similarity index 100% rename from client/multi-currency-setup/test/index.js rename to multi-currency/client/setup/test/index.js diff --git a/client/utils/currency/index.js b/multi-currency/client/utils/currency/index.js similarity index 100% rename from client/utils/currency/index.js rename to multi-currency/client/utils/currency/index.js diff --git a/client/utils/currency/test/index.js b/multi-currency/client/utils/currency/test/index.js similarity index 98% rename from client/utils/currency/test/index.js rename to multi-currency/client/utils/currency/test/index.js index 25e67b62c3c..0a08bbdd7db 100644 --- a/client/utils/currency/test/index.js +++ b/multi-currency/client/utils/currency/test/index.js @@ -6,7 +6,7 @@ /** * Internal dependencies */ -import * as utils from 'utils/currency'; +import * as utils from 'multi-currency/utils/currency'; describe( 'Currency utilities', () => { beforeEach( () => { diff --git a/client/multi-currency/__tests__/missing-currencies-message.test.js b/multi-currency/client/utils/missing-currencies-message/__tests__/index.test.js similarity index 89% rename from client/multi-currency/__tests__/missing-currencies-message.test.js rename to multi-currency/client/utils/missing-currencies-message/__tests__/index.test.js index ac74c4ee517..bd7fcff7840 100644 --- a/client/multi-currency/__tests__/missing-currencies-message.test.js +++ b/multi-currency/client/utils/missing-currencies-message/__tests__/index.test.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getMissingCurrenciesTooltipMessage } from '../missing-currencies-message'; +import { getMissingCurrenciesTooltipMessage } from 'multi-currency/utils/missing-currencies-message'; describe( 'getMissingCurrenciesTooltipMessage', () => { it( 'returns correct string with the given LPM label and currency list', () => { diff --git a/client/multi-currency/missing-currencies-message.ts b/multi-currency/client/utils/missing-currencies-message/index.ts similarity index 91% rename from client/multi-currency/missing-currencies-message.ts rename to multi-currency/client/utils/missing-currencies-message/index.ts index 514ccff973d..b6e68116479 100644 --- a/client/multi-currency/missing-currencies-message.ts +++ b/multi-currency/client/utils/missing-currencies-message/index.ts @@ -6,7 +6,7 @@ import { sprintf, _n } from '@wordpress/i18n'; /** * Internal dependencies */ -import { formatListOfItems } from 'wcpay/utils/format-list-of-items'; +import { formatListOfItems } from 'multi-currency/interface/functions'; export const getMissingCurrenciesTooltipMessage = ( paymentMethodLabel: string, diff --git a/includes/multi-currency/AdminNotices.php b/multi-currency/src/AdminNotices.php similarity index 100% rename from includes/multi-currency/AdminNotices.php rename to multi-currency/src/AdminNotices.php diff --git a/includes/multi-currency/Analytics.php b/multi-currency/src/Analytics.php similarity index 99% rename from includes/multi-currency/Analytics.php rename to multi-currency/src/Analytics.php index ef3f5b17a5a..258be02d223 100644 --- a/includes/multi-currency/Analytics.php +++ b/multi-currency/src/Analytics.php @@ -12,7 +12,6 @@ use Automattic\WooCommerce\Utilities\OrderUtil; use WC_Order; use WC_Order_Refund; -use WC_Payments; defined( 'ABSPATH' ) || exit; @@ -63,7 +62,7 @@ public function init() { $this->register_customer_currencies(); } - if ( WC_Payments::mode()->is_dev() ) { + if ( $this->multi_currency->gateway_context['is_dev_mode'] ) { add_filter( 'woocommerce_analytics_report_should_use_cache', [ $this, 'disable_report_caching' ] ); } @@ -105,7 +104,7 @@ public function init() { * @return void */ public function register_admin_scripts() { - WC_Payments::register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); + $this->multi_currency->register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); } /** diff --git a/includes/multi-currency/BackendCurrencies.php b/multi-currency/src/BackendCurrencies.php similarity index 91% rename from includes/multi-currency/BackendCurrencies.php rename to multi-currency/src/BackendCurrencies.php index 9ea009ac4c4..bdb2da92b72 100644 --- a/includes/multi-currency/BackendCurrencies.php +++ b/multi-currency/src/BackendCurrencies.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WC_Payments_Localization_Service; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; defined( 'ABSPATH' ) || exit; @@ -23,9 +23,9 @@ class BackendCurrencies { protected $multi_currency; /** - * WC_Payments_Localization_Service instance. + * MultiCurrencyLocalizationInterface instance. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ protected $localization_service; @@ -39,10 +39,10 @@ class BackendCurrencies { /** * Constructor. * - * @param MultiCurrency $multi_currency The MultiCurrency instance. - * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. */ - public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service ) { + public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; } diff --git a/includes/multi-currency/Compatibility.php b/multi-currency/src/Compatibility.php similarity index 99% rename from includes/multi-currency/Compatibility.php rename to multi-currency/src/Compatibility.php index 4f60915fb97..87e10cbde78 100644 --- a/includes/multi-currency/Compatibility.php +++ b/multi-currency/src/Compatibility.php @@ -7,8 +7,6 @@ namespace WCPay\MultiCurrency; -use WC_Deposits; -use WC_Deposits_Product_Manager; use WC_Order; use WC_Order_Refund; use WCPay\MultiCurrency\Compatibility\BaseCompatibility; @@ -41,7 +39,7 @@ class Compatibility extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { add_action( 'init', [ $this, 'init_compatibility_classes' ], 11 ); if ( defined( 'DOING_CRON' ) ) { diff --git a/includes/multi-currency/Compatibility/BaseCompatibility.php b/multi-currency/src/Compatibility/BaseCompatibility.php similarity index 95% rename from includes/multi-currency/Compatibility/BaseCompatibility.php rename to multi-currency/src/Compatibility/BaseCompatibility.php index a98073fd113..3e7d1a67a20 100644 --- a/includes/multi-currency/Compatibility/BaseCompatibility.php +++ b/multi-currency/src/Compatibility/BaseCompatibility.php @@ -46,5 +46,5 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils ) { * * @return void */ - abstract protected function init(); + abstract public function init(); } diff --git a/includes/multi-currency/Compatibility/WooCommerceBookings.php b/multi-currency/src/Compatibility/WooCommerceBookings.php similarity index 99% rename from includes/multi-currency/Compatibility/WooCommerceBookings.php rename to multi-currency/src/Compatibility/WooCommerceBookings.php index 5a99534e5d6..756e4eef355 100644 --- a/includes/multi-currency/Compatibility/WooCommerceBookings.php +++ b/multi-currency/src/Compatibility/WooCommerceBookings.php @@ -39,7 +39,7 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils, Fronte * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if Bookings is active. if ( class_exists( 'WC_Bookings' ) ) { if ( ! is_admin() || wp_doing_ajax() ) { diff --git a/includes/multi-currency/Compatibility/WooCommerceDeposits.php b/multi-currency/src/Compatibility/WooCommerceDeposits.php similarity index 99% rename from includes/multi-currency/Compatibility/WooCommerceDeposits.php rename to multi-currency/src/Compatibility/WooCommerceDeposits.php index f92819785c5..e2ffa89d441 100644 --- a/includes/multi-currency/Compatibility/WooCommerceDeposits.php +++ b/multi-currency/src/Compatibility/WooCommerceDeposits.php @@ -20,7 +20,7 @@ class WooCommerceDeposits extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { if ( class_exists( 'WC_Deposits' ) ) { /* * Multi-currency support was added to WooCommerce Deposits in version 2.0.1. diff --git a/includes/multi-currency/Compatibility/WooCommerceFedEx.php b/multi-currency/src/Compatibility/WooCommerceFedEx.php similarity index 97% rename from includes/multi-currency/Compatibility/WooCommerceFedEx.php rename to multi-currency/src/Compatibility/WooCommerceFedEx.php index 738e738150f..8a38d058e40 100644 --- a/includes/multi-currency/Compatibility/WooCommerceFedEx.php +++ b/multi-currency/src/Compatibility/WooCommerceFedEx.php @@ -20,7 +20,7 @@ class WooCommerceFedEx extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if FedEx is active. if ( class_exists( 'WC_Shipping_Fedex_Init' ) ) { add_filter( MultiCurrency::FILTER_PREFIX . 'should_return_store_currency', [ $this, 'should_return_store_currency' ] ); diff --git a/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php b/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php similarity index 99% rename from includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php rename to multi-currency/src/Compatibility/WooCommerceNameYourPrice.php index 155f99e1a4d..fad352e1d1f 100644 --- a/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php +++ b/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php @@ -21,7 +21,7 @@ class WooCommerceNameYourPrice extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if Name Your Price is active. if ( class_exists( 'WC_Name_Your_Price' ) ) { // Convert meta prices. diff --git a/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php b/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php similarity index 98% rename from includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php rename to multi-currency/src/Compatibility/WooCommercePointsAndRewards.php index 9d78886eb41..38819d15322 100644 --- a/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php +++ b/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php @@ -33,7 +33,7 @@ class WooCommercePointsAndRewards extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed filters if Points & Rewards is active and it's not an admin request. if ( is_admin() || ! class_exists( 'WC_Points_Rewards' ) ) { return; diff --git a/includes/multi-currency/Compatibility/WooCommercePreOrders.php b/multi-currency/src/Compatibility/WooCommercePreOrders.php similarity index 96% rename from includes/multi-currency/Compatibility/WooCommercePreOrders.php rename to multi-currency/src/Compatibility/WooCommercePreOrders.php index 3c3fe9d5efc..b16dd91b646 100644 --- a/includes/multi-currency/Compatibility/WooCommercePreOrders.php +++ b/multi-currency/src/Compatibility/WooCommercePreOrders.php @@ -20,7 +20,7 @@ class WooCommercePreOrders extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if Pre-Orders is active. if ( class_exists( 'WC_Pre_Orders' ) ) { add_filter( 'wc_pre_orders_fee', [ $this, 'wc_pre_orders_fee' ] ); diff --git a/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php b/multi-currency/src/Compatibility/WooCommerceProductAddOns.php similarity index 99% rename from includes/multi-currency/Compatibility/WooCommerceProductAddOns.php rename to multi-currency/src/Compatibility/WooCommerceProductAddOns.php index 7d245cd4e4f..add583059f8 100644 --- a/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php +++ b/multi-currency/src/Compatibility/WooCommerceProductAddOns.php @@ -23,7 +23,7 @@ class WooCommerceProductAddOns extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if Product Add Ons is active. if ( class_exists( 'WC_Product_Addons' ) ) { if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) { diff --git a/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php b/multi-currency/src/Compatibility/WooCommerceSubscriptions.php similarity index 97% rename from includes/multi-currency/Compatibility/WooCommerceSubscriptions.php rename to multi-currency/src/Compatibility/WooCommerceSubscriptions.php index 94294827ea9..6701f6739dd 100644 --- a/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php +++ b/multi-currency/src/Compatibility/WooCommerceSubscriptions.php @@ -7,10 +7,8 @@ namespace WCPay\MultiCurrency\Compatibility; -use WC_Payments_Explicit_Price_Formatter; -use WC_Payments_Features; use WC_Subscription; -use WCPay\Logger; +use WCPay\MultiCurrency\Logger; use WCPay\MultiCurrency\FrontendCurrencies; use WCPay\MultiCurrency\MultiCurrency; @@ -61,9 +59,9 @@ class WooCommerceSubscriptions extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if WC Subscriptions or WCPay Subscriptions are active. - if ( class_exists( 'WC_Subscriptions' ) || WC_Payments_Features::is_wcpay_subscriptions_enabled() ) { + if ( class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Payments_Subscriptions' ) ) { if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) { $this->frontend_currencies = $this->multi_currency->get_frontend_currencies(); @@ -393,6 +391,10 @@ public function maybe_get_explicit_format_for_subscription_total( $html_price, $ return $html_price; } + if ( ! $this->multi_currency->has_additional_currencies_enabled() ) { + return $html_price; + } + /** * Get the currency code from the subscription, then return the explicit price. * Tell Psalm to ignore the WC_Subscription class, this class is only loaded if Subscriptions is active. @@ -400,7 +402,15 @@ public function maybe_get_explicit_format_for_subscription_total( $html_price, $ * @psalm-suppress UndefinedDocblockClass */ $currency_code = $this->current_my_account_subscription->get_currency() ?? get_woocommerce_currency(); - return WC_Payments_Explicit_Price_Formatter::get_explicit_price_with_currency( $html_price, $currency_code ); + + // This is sourced from WC_Payments_Explicit_Price_Formatter::get_explicit_price_with_currency. + $price_to_check = html_entity_decode( wp_strip_all_tags( $html_price ) ); + + if ( false === strpos( $price_to_check, trim( $currency_code ) ) ) { + return $html_price . ' ' . $currency_code; + } + + return $html_price; } /** diff --git a/includes/multi-currency/Compatibility/WooCommerceUPS.php b/multi-currency/src/Compatibility/WooCommerceUPS.php similarity index 97% rename from includes/multi-currency/Compatibility/WooCommerceUPS.php rename to multi-currency/src/Compatibility/WooCommerceUPS.php index 6a53f47bff3..427aa060d52 100644 --- a/includes/multi-currency/Compatibility/WooCommerceUPS.php +++ b/multi-currency/src/Compatibility/WooCommerceUPS.php @@ -20,7 +20,7 @@ class WooCommerceUPS extends BaseCompatibility { * * @return void */ - protected function init() { + public function init() { // Add needed actions and filters if UPS is active. if ( class_exists( 'WC_Shipping_UPS_Init' ) ) { add_filter( MultiCurrency::FILTER_PREFIX . 'should_return_store_currency', [ $this, 'should_return_store_currency' ] ); diff --git a/multi-currency/src/CountryFlags.php b/multi-currency/src/CountryFlags.php new file mode 100644 index 00000000000..286e77dfbe2 --- /dev/null +++ b/multi-currency/src/CountryFlags.php @@ -0,0 +1,301 @@ + '🇦🇩', + 'AE' => '🇦🇪', + 'AF' => '🇦🇫', + 'AG' => '🇦🇬', + 'AI' => '🇦🇮', + 'AL' => '🇦🇱', + 'AM' => '🇦🇲', + 'AO' => '🇦🇴', + 'AQ' => '🇦🇶', + 'AR' => '🇦🇷', + 'AS' => '🇦🇸', + 'AT' => '🇦🇹', + 'AU' => '🇦🇺', + 'AW' => '🇦🇼', + 'AX' => '🇦🇽', + 'AZ' => '🇦🇿', + 'BA' => '🇧🇦', + 'BB' => '🇧🇧', + 'BD' => '🇧🇩', + 'BE' => '🇧🇪', + 'BF' => '🇧🇫', + 'BG' => '🇧🇬', + 'BH' => '🇧🇭', + 'BI' => '🇧🇮', + 'BJ' => '🇧🇯', + 'BL' => '🇧🇱', + 'BM' => '🇧🇲', + 'BN' => '🇧🇳', + 'BO' => '🇧🇴', + 'BQ' => '🇧🇶', + 'BR' => '🇧🇷', + 'BS' => '🇧🇸', + 'BT' => '🇧🇹', + 'BV' => '🇧🇻', + 'BW' => '🇧🇼', + 'BY' => '🇧🇾', + 'BZ' => '🇧🇿', + 'CA' => '🇨🇦', + 'CC' => '🇨🇨', + 'CD' => '🇨🇩', + 'CF' => '🇨🇫', + 'CG' => '🇨🇬', + 'CH' => '🇨🇭', + 'CI' => '🇨🇮', + 'CK' => '🇨🇰', + 'CL' => '🇨🇱', + 'CM' => '🇨🇲', + 'CN' => '🇨🇳', + 'CO' => '🇨🇴', + 'CR' => '🇨🇷', + 'CU' => '🇨🇺', + 'CV' => '🇨🇻', + 'CW' => '🇨🇼', + 'CX' => '🇨🇽', + 'CY' => '🇨🇾', + 'CZ' => '🇨🇿', + 'DE' => '🇩🇪', + 'DJ' => '🇩🇯', + 'DK' => '🇩🇰', + 'DM' => '🇩🇲', + 'DO' => '🇩🇴', + 'DZ' => '🇩🇿', + 'EC' => '🇪🇨', + 'EE' => '🇪🇪', + 'EG' => '🇪🇬', + 'EH' => '🇪🇭', + 'ER' => '🇪🇷', + 'ES' => '🇪🇸', + 'ET' => '🇪🇹', + 'EU' => '🇪🇺', + 'FI' => '🇫🇮', + 'FJ' => '🇫🇯', + 'FK' => '🇫🇰', + 'FM' => '🇫🇲', + 'FO' => '🇫🇴', + 'FR' => '🇫🇷', + 'GA' => '🇬🇦', + 'GB' => '🇬🇧', + 'GD' => '🇬🇩', + 'GE' => '🇬🇪', + 'GF' => '🇬🇫', + 'GG' => '🇬🇬', + 'GH' => '🇬🇭', + 'GI' => '🇬🇮', + 'GL' => '🇬🇱', + 'GM' => '🇬🇲', + 'GN' => '🇬🇳', + 'GP' => '🇬🇵', + 'GQ' => '🇬🇶', + 'GR' => '🇬🇷', + 'GS' => '🇬🇸', + 'GT' => '🇬🇹', + 'GU' => '🇬🇺', + 'GW' => '🇬🇼', + 'GY' => '🇬🇾', + 'HK' => '🇭🇰', + 'HM' => '🇭🇲', + 'HN' => '🇭🇳', + 'HR' => '🇭🇷', + 'HT' => '🇭🇹', + 'HU' => '🇭🇺', + 'ID' => '🇮🇩', + 'IE' => '🇮🇪', + 'IL' => '🇮🇱', + 'IM' => '🇮🇲', + 'IN' => '🇮🇳', + 'IO' => '🇮🇴', + 'IQ' => '🇮🇶', + 'IR' => '🇮🇷', + 'IS' => '🇮🇸', + 'IT' => '🇮🇹', + 'JE' => '🇯🇪', + 'JM' => '🇯🇲', + 'JO' => '🇯🇴', + 'JP' => '🇯🇵', + 'KE' => '🇰🇪', + 'KG' => '🇰🇬', + 'KH' => '🇰🇭', + 'KI' => '🇰🇮', + 'KM' => '🇰🇲', + 'KN' => '🇰🇳', + 'KP' => '🇰🇵', + 'KR' => '🇰🇷', + 'KW' => '🇰🇼', + 'KY' => '🇰🇾', + 'KZ' => '🇰🇿', + 'LA' => '🇱🇦', + 'LB' => '🇱🇧', + 'LC' => '🇱🇨', + 'LI' => '🇱🇮', + 'LK' => '🇱🇰', + 'LR' => '🇱🇷', + 'LS' => '🇱🇸', + 'LT' => '🇱🇹', + 'LU' => '🇱🇺', + 'LV' => '🇱🇻', + 'LY' => '🇱🇾', + 'MA' => '🇲🇦', + 'MC' => '🇲🇨', + 'MD' => '🇲🇩', + 'ME' => '🇲🇪', + 'MF' => '🇲🇫', + 'MG' => '🇲🇬', + 'MH' => '🇲🇭', + 'MK' => '🇲🇰', + 'ML' => '🇲🇱', + 'MM' => '🇲🇲', + 'MN' => '🇲🇳', + 'MO' => '🇲🇴', + 'MP' => '🇲🇵', + 'MQ' => '🇲🇶', + 'MR' => '🇲🇷', + 'MS' => '🇲🇸', + 'MT' => '🇲🇹', + 'MU' => '🇲🇺', + 'MV' => '🇲🇻', + 'MW' => '🇲🇼', + 'MX' => '🇲🇽', + 'MY' => '🇲🇾', + 'MZ' => '🇲🇿', + 'NA' => '🇳🇦', + 'NC' => '🇳🇨', + 'NE' => '🇳🇪', + 'NF' => '🇳🇫', + 'NG' => '🇳🇬', + 'NI' => '🇳🇮', + 'NL' => '🇳🇱', + 'NO' => '🇳🇴', + 'NP' => '🇳🇵', + 'NR' => '🇳🇷', + 'NU' => '🇳🇺', + 'NZ' => '🇳🇿', + 'OM' => '🇴🇲', + 'PA' => '🇵🇦', + 'PE' => '🇵🇪', + 'PF' => '🇵🇫', + 'PG' => '🇵🇬', + 'PH' => '🇵🇭', + 'PK' => '🇵🇰', + 'PL' => '🇵🇱', + 'PM' => '🇵🇲', + 'PN' => '🇵🇳', + 'PR' => '🇵🇷', + 'PS' => '🇵🇸', + 'PT' => '🇵🇹', + 'PW' => '🇵🇼', + 'PY' => '🇵🇾', + 'QA' => '🇶🇦', + 'RE' => '🇷🇪', + 'RO' => '🇷🇴', + 'RS' => '🇷🇸', + 'RU' => '🇷🇺', + 'RW' => '🇷🇼', + 'SA' => '🇸🇦', + 'SB' => '🇸🇧', + 'SC' => '🇸🇨', + 'SD' => '🇸🇩', + 'SE' => '🇸🇪', + 'SG' => '🇸🇬', + 'SH' => '🇸🇭', + 'SI' => '🇸🇮', + 'SJ' => '🇸🇯', + 'SK' => '🇸🇰', + 'SL' => '🇸🇱', + 'SM' => '🇸🇲', + 'SN' => '🇸🇳', + 'SO' => '🇸🇴', + 'SR' => '🇸🇷', + 'SS' => '🇸🇸', + 'ST' => '🇸🇹', + 'SV' => '🇸🇻', + 'SX' => '🇸🇽', + 'SY' => '🇸🇾', + 'SZ' => '🇸🇿', + 'TC' => '🇹🇨', + 'TD' => '🇹🇩', + 'TF' => '🇹🇫', + 'TG' => '🇹🇬', + 'TH' => '🇹🇭', + 'TJ' => '🇹🇯', + 'TK' => '🇹🇰', + 'TL' => '🇹🇱', + 'TM' => '🇹🇲', + 'TN' => '🇹🇳', + 'TO' => '🇹🇴', + 'TR' => '🇹🇷', + 'TT' => '🇹🇹', + 'TV' => '🇹🇻', + 'TW' => '🇹🇼', + 'TZ' => '🇹🇿', + 'UA' => '🇺🇦', + 'UG' => '🇺🇬', + 'UM' => '🇺🇲', + 'US' => '🇺🇸', + 'UY' => '🇺🇾', + 'UZ' => '🇺🇿', + 'VA' => '🇻🇦', + 'VC' => '🇻🇨', + 'VE' => '🇻🇪', + 'VG' => '🇻🇬', + 'VI' => '🇻🇮', + 'VN' => '🇻🇳', + 'VU' => '🇻🇺', + 'WF' => '🇼🇫', + 'WS' => '🇼🇸', + 'XK' => '🇽🇰', + 'YE' => '🇾🇪', + 'YT' => '🇾🇹', + 'ZA' => '🇿🇦', + 'ZM' => '🇿🇲', + 'ZW' => '🇿🇼', + ]; + + /** + * Retrieves a flag by country code. + * + * @param string $country country alpha-2 code (ISO 3166) like US. + * @return string + */ + public static function get_by_country( string $country ): string { + return self::EMOJI_COUNTRIES_FLAGS[ $country ] ?? ''; + } + + /** + * Retrieves a flag by currency code. + * + * @param string $currency currency code (ISO 4217) like USD. + * @return string + */ + public static function get_by_currency( string $currency ): string { + $exceptions = [ + 'ANG' => '', + 'BTC' => '', + 'XAF' => '', + 'XCD' => '', + 'XOF' => '', + 'XPF' => '', + ]; + + $flag = $exceptions[ $currency ] ?? self::get_by_country( substr( $currency, 0, -1 ) ); + + return $flag; + } +} diff --git a/includes/multi-currency/Currency.php b/multi-currency/src/Currency.php similarity index 89% rename from includes/multi-currency/Currency.php rename to multi-currency/src/Currency.php index 0d9ae84da42..37b8fd17d12 100644 --- a/includes/multi-currency/Currency.php +++ b/multi-currency/src/Currency.php @@ -7,8 +7,7 @@ namespace WCPay\MultiCurrency; -use WC_Payments_Localization_Service; -use WC_Payments_Utils; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; defined( 'ABSPATH' ) || exit; @@ -67,21 +66,21 @@ class Currency implements \JsonSerializable { private $last_updated; /** - * Instance of WC_Payments_Localization_Service. + * Instance of MultiCurrencyLocalizationInterface. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ private $localization_service; /** * Constructor. * - * @param WC_Payments_Localization_Service $localization_service Localization service instance. - * @param string $code Three letter currency code. - * @param float $rate The conversion rate. - * @param int|null $last_updated The time this currency was last updated. + * @param MultiCurrencyLocalizationInterface $localization_service Localization service instance. + * @param string $code Three letter currency code. + * @param float $rate The conversion rate. + * @param int|null $last_updated The time this currency was last updated. */ - public function __construct( WC_Payments_Localization_Service $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { + public function __construct( MultiCurrencyLocalizationInterface $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { $this->localization_service = $localization_service; $this->code = $code; $this->rate = $rate; diff --git a/includes/multi-currency/CurrencySwitcherBlock.php b/multi-currency/src/CurrencySwitcherBlock.php similarity index 97% rename from includes/multi-currency/CurrencySwitcherBlock.php rename to multi-currency/src/CurrencySwitcherBlock.php index 95d4762365c..e13902c6ede 100644 --- a/includes/multi-currency/CurrencySwitcherBlock.php +++ b/multi-currency/src/CurrencySwitcherBlock.php @@ -7,9 +7,7 @@ namespace WCPay\MultiCurrency; -use WC_Payments; use function http_build_query; -use function implode; use function urldecode; defined( 'ABSPATH' ) || exit; @@ -60,7 +58,7 @@ public function init_hooks() { */ public function init_block_widget() { // Automatically load dependencies and version. - WC_Payments::register_script_with_dependencies( 'woocommerce-payments/multi-currency-switcher', 'dist/multi-currency-switcher-block' ); + $this->multi_currency->register_script_with_dependencies( 'woocommerce-payments/multi-currency-switcher', 'dist/multi-currency-switcher-block' ); register_block_type( 'woocommerce-payments/multi-currency-switcher', diff --git a/includes/multi-currency/CurrencySwitcherWidget.php b/multi-currency/src/CurrencySwitcherWidget.php similarity index 100% rename from includes/multi-currency/CurrencySwitcherWidget.php rename to multi-currency/src/CurrencySwitcherWidget.php diff --git a/includes/multi-currency/Exceptions/InvalidCurrencyException.php b/multi-currency/src/Exceptions/InvalidCurrencyException.php similarity index 71% rename from includes/multi-currency/Exceptions/InvalidCurrencyException.php rename to multi-currency/src/Exceptions/InvalidCurrencyException.php index 454fa1c7383..c3ec9046a27 100644 --- a/includes/multi-currency/Exceptions/InvalidCurrencyException.php +++ b/multi-currency/src/Exceptions/InvalidCurrencyException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use WCPay\Exceptions\Base_Exception; +use Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency is used. */ -class InvalidCurrencyException extends Base_Exception {} +class InvalidCurrencyException extends Exception {} diff --git a/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php b/multi-currency/src/Exceptions/InvalidCurrencyRateException.php similarity index 71% rename from includes/multi-currency/Exceptions/InvalidCurrencyRateException.php rename to multi-currency/src/Exceptions/InvalidCurrencyRateException.php index e80cc0cb92d..6f0b5b2c007 100644 --- a/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php +++ b/multi-currency/src/Exceptions/InvalidCurrencyRateException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use WCPay\Exceptions\Base_Exception; +use Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency rate is used. */ -class InvalidCurrencyRateException extends Base_Exception {} +class InvalidCurrencyRateException extends Exception {} diff --git a/includes/multi-currency/FrontendCurrencies.php b/multi-currency/src/FrontendCurrencies.php similarity index 94% rename from includes/multi-currency/FrontendCurrencies.php rename to multi-currency/src/FrontendCurrencies.php index da1342ac55a..065d0db24a8 100644 --- a/includes/multi-currency/FrontendCurrencies.php +++ b/multi-currency/src/FrontendCurrencies.php @@ -8,7 +8,7 @@ namespace WCPay\MultiCurrency; use WC_Order; -use WC_Payments_Localization_Service; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; defined( 'ABSPATH' ) || exit; @@ -24,9 +24,9 @@ class FrontendCurrencies { protected $multi_currency; /** - * WC_Payments_Localization_Service instance. + * MultiCurrencyLocalizationInterface instance. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ protected $localization_service; @@ -89,12 +89,12 @@ class FrontendCurrencies { /** * Constructor. * - * @param MultiCurrency $multi_currency The MultiCurrency instance. - * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. - * @param Utils $utils Utils instance. - * @param Compatibility $compatibility Compatibility instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. + * @param Utils $utils Utils instance. + * @param Compatibility $compatibility Compatibility instance. */ - public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service, Utils $utils, Compatibility $compatibility ) { + public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service, Utils $utils, Compatibility $compatibility ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; $this->utils = $utils; diff --git a/includes/multi-currency/FrontendPrices.php b/multi-currency/src/FrontendPrices.php similarity index 100% rename from includes/multi-currency/FrontendPrices.php rename to multi-currency/src/FrontendPrices.php diff --git a/includes/multi-currency/Geolocation.php b/multi-currency/src/Geolocation.php similarity index 84% rename from includes/multi-currency/Geolocation.php rename to multi-currency/src/Geolocation.php index 5bd0b48b7db..4f5da245958 100644 --- a/includes/multi-currency/Geolocation.php +++ b/multi-currency/src/Geolocation.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WC_Payments_Localization_Service; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; defined( 'ABSPATH' ) || exit; @@ -16,18 +16,18 @@ */ class Geolocation { /** - * WC_Payments_Localization_Service instance. + * MultiCurrencyLocalizationInterface instance. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ protected $localization_service; /** * Constructor. * - * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. + * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. */ - public function __construct( WC_Payments_Localization_Service $localization_service ) { + public function __construct( MultiCurrencyLocalizationInterface $localization_service ) { $this->localization_service = $localization_service; } diff --git a/includes/multi-currency/Helpers/OrderMetaHelper.md b/multi-currency/src/Helpers/OrderMetaHelper.md similarity index 100% rename from includes/multi-currency/Helpers/OrderMetaHelper.md rename to multi-currency/src/Helpers/OrderMetaHelper.md diff --git a/includes/multi-currency/Helpers/OrderMetaHelper.php b/multi-currency/src/Helpers/OrderMetaHelper.php similarity index 95% rename from includes/multi-currency/Helpers/OrderMetaHelper.php rename to multi-currency/src/Helpers/OrderMetaHelper.php index 208fefc2c26..9315557617a 100644 --- a/includes/multi-currency/Helpers/OrderMetaHelper.php +++ b/multi-currency/src/Helpers/OrderMetaHelper.php @@ -7,10 +7,9 @@ namespace WCPay\MultiCurrency\Helpers; -use WC_Payments_API_Client; -use WC_Payments_Utils; -use WCPay\Exceptions\API_Exception; -use WCPay\Logger; +use WCPay\MultiCurrency\BackendCurrencies; +use WCPay\MultiCurrency\Logger; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; defined( 'ABSPATH' ) || exit; @@ -21,10 +20,17 @@ class OrderMetaHelper { /** * Client for making requests to the WooCommerce Payments API. * - * @var WC_Payments_API_Client + * @var MultiCurrencyApiClientInterface */ private $payments_api_client; + /** + * Backend currencies object. + * + * @var BackendCurrencies + */ + private $backend_currencies; + /** * Array of errors, if any. * @@ -35,10 +41,12 @@ class OrderMetaHelper { /** * Constructor. * - * @param WC_Payments_API_Client $payments_api_client Payments API client. + * @param MultiCurrencyApiClientInterface $payments_api_client Payments API client. + * @param BackendCurrencies $backend_currencies Backend currencies object. */ - public function __construct( WC_Payments_API_Client $payments_api_client ) { + public function __construct( MultiCurrencyApiClientInterface $payments_api_client, BackendCurrencies $backend_currencies ) { $this->payments_api_client = $payments_api_client; + $this->backend_currencies = $backend_currencies; } /** @@ -227,7 +235,7 @@ public function display_meta_box_content( $order ) { // Attempt to get the intent. try { $intent_object = $this->payments_api_client->get_intent( $intent_id ); - } catch ( API_Exception $e ) { + } catch ( \Exception $e ) { // Log the error returned. Logger::error( "Error when attempting to get intent ($intent_id):\n" . $e->getMessage() ); $intent_object = null; @@ -247,7 +255,7 @@ public function display_meta_box_content( $order ) { /** * Zero decimal currencies have a different conversion rate value. */ - if ( in_array( strtolower( $order_currency ), WC_Payments_Utils::zero_decimal_currencies(), true ) ) { + if ( $this->backend_currencies->is_zero_decimal_currency( $order_currency ) ) { if ( '' !== $charge_items['charge_exchange_rate']['value'] ) { $charge_items['charge_exchange_rate']['value'] = $charge_items['charge_exchange_rate']['value'] / 100; } diff --git a/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php b/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php new file mode 100644 index 00000000000..b827c5ca3d8 --- /dev/null +++ b/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php @@ -0,0 +1,61 @@ +log( $level, $message, [ 'source' => self::LOG_FILE ] ); + } +} diff --git a/includes/multi-currency/MultiCurrency.php b/multi-currency/src/MultiCurrency.php similarity index 90% rename from includes/multi-currency/MultiCurrency.php rename to multi-currency/src/MultiCurrency.php index 6482d34fb2e..4caab08a113 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/multi-currency/src/MultiCurrency.php @@ -7,20 +7,16 @@ namespace WCPay\MultiCurrency; -use WC_Payments; -use WC_Payments_Account; -use WC_Payments_Utils; -use WC_Payments_API_Client; -use WC_Payments_Localization_Service; -use WCPay\Constants\Country_Code; -use WCPay\Constants\Currency_Code; -use WCPay\Exceptions\API_Exception; -use WCPay\Database_Cache; -use WCPay\Logger; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyException; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyRateException; use WCPay\MultiCurrency\Helpers\OrderMetaHelper; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyCacheInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WCPay\MultiCurrency\Logger; use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; +use WCPay\MultiCurrency\Utils; defined( 'ABSPATH' ) || exit; @@ -41,13 +37,6 @@ class MultiCurrency { */ public $id = 'wcpay_multi_currency'; - /** - * The single instance of the class. - * - * @var ?MultiCurrency - */ - protected static $instance = null; - /** * Static flag to show if the currencies initialization has been completed * @@ -140,32 +129,32 @@ class MultiCurrency { protected $enabled_currencies; /** - * Client for making requests to the WooCommerce Payments API + * Client for making requests to the API * - * @var WC_Payments_API_Client + * @var MultiCurrencyApiClientInterface */ private $payments_api_client; /** - * Instance of WC_Payments_Account. + * Instance of MultiCurrencyAccountInterface. * - * @var WC_Payments_Account + * @var MultiCurrencyAccountInterface */ private $payments_account; /** - * Instance of WC_Payments_Localization_Service. + * Instance of MultiCurrencyLocalizationInterface. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ private $localization_service; /** - * Instance of Database_Cache. + * Instance of MultiCurrencyCacheInterface. * - * @var Database_Cache + * @var MultiCurrencyCacheInterface */ - private $database_cache; + private $cache; /** * Tracking instance. @@ -189,35 +178,28 @@ class MultiCurrency { private $order_meta_helper; /** - * Main MultiCurrency Instance. + * Gateway context. * - * Ensures only one instance of MultiCurrency is loaded or can be loaded. - * - * @static - * @return MultiCurrency - Main instance. + * @var array */ - public static function instance() { - if ( is_null( self::$instance ) ) { - self::$instance = new self( WC_Payments::get_payments_api_client(), WC_Payments::get_account_service(), WC_Payments::get_localization_service(), WC_Payments::get_database_cache() ); - self::$instance->init_hooks(); - } - return self::$instance; - } + public $gateway_context; /** * Class constructor. * - * @param WC_Payments_API_Client $payments_api_client Payments API client. - * @param WC_Payments_Account $payments_account Payments Account instance. - * @param WC_Payments_Localization_Service $localization_service Localization Service instance. - * @param Database_Cache $database_cache Database Cache instance. - * @param Utils|null $utils Optional Utils instance. + * @param array $gateway_context Gateway context. + * @param MultiCurrencyApiClientInterface $payments_api_client Payments API client. + * @param MultiCurrencyAccountInterface $payments_account Payments Account instance. + * @param MultiCurrencyLocalizationInterface $localization_service Localization Service instance. + * @param MultiCurrencyCacheInterface $cache Cache instance. + * @param Utils|null $utils Optional Utils instance. */ - public function __construct( WC_Payments_API_Client $payments_api_client, WC_Payments_Account $payments_account, WC_Payments_Localization_Service $localization_service, Database_Cache $database_cache, Utils $utils = null ) { + public function __construct( array $gateway_context, MultiCurrencyApiClientInterface $payments_api_client, MultiCurrencyAccountInterface $payments_account, MultiCurrencyLocalizationInterface $localization_service, MultiCurrencyCacheInterface $cache, Utils $utils = null ) { + $this->gateway_context = $gateway_context; $this->payments_api_client = $payments_api_client; $this->payments_account = $payments_account; $this->localization_service = $localization_service; - $this->database_cache = $database_cache; + $this->cache = $cache; // If a Utils instance is not passed as argument, initialize it. This allows to mock it in tests. $this->utils = $utils ?? new Utils(); $this->geolocation = new Geolocation( $this->localization_service ); @@ -242,9 +224,9 @@ public function init_hooks() { add_action( 'rest_api_init', [ $this, 'init_rest_api' ] ); add_action( 'widgets_init', [ $this, 'init_widgets' ] ); - $is_frontend_request = ! is_admin() && ! defined( 'DOING_CRON' ) && ! WC()->is_rest_api_request(); + $is_frontend_request = ! is_admin() && ! defined( 'DOING_CRON' ) && ! Utils::is_admin_api_request(); - if ( $is_frontend_request || \WC_Payments_Utils::is_store_api_request() ) { + if ( $is_frontend_request || Utils::is_store_api_request() ) { // Make sure that this runs after the main init function. add_action( 'init', [ $this, 'update_selected_currency_by_url' ], 11 ); add_action( 'init', [ $this, 'update_selected_currency_by_geolocation' ], 12 ); @@ -280,19 +262,17 @@ public function init() { $this->update_manual_rate_currencies_notice_option(); } - $payment_method_compat = new PaymentMethodsCompatibility( $this, WC_Payments::get_gateway() ); - $admin_notices = new AdminNotices(); - $user_settings = new UserSettings( $this ); + $admin_notices = new AdminNotices(); + $user_settings = new UserSettings( $this ); new Analytics( $this ); $this->frontend_prices = new FrontendPrices( $this, $this->compatibility ); $this->frontend_currencies = new FrontendCurrencies( $this, $this->localization_service, $this->utils, $this->compatibility ); $this->backend_currencies = new BackendCurrencies( $this, $this->localization_service ); $this->tracking = new Tracking( $this ); - $this->order_meta_helper = new OrderMetaHelper( $this->payments_api_client ); + $this->order_meta_helper = new OrderMetaHelper( $this->payments_api_client, $this->backend_currencies ); // Init all of the hooks. - $payment_method_compat->init_hooks(); $admin_notices->init_hooks(); $user_settings->init_hooks(); $this->frontend_prices->init_hooks(); @@ -310,7 +290,7 @@ public function init() { } if ( is_admin() ) { - add_action( 'admin_init', [ __CLASS__, 'add_woo_admin_notes' ] ); + add_action( 'admin_init', [ $this, 'add_woo_admin_notes' ] ); } // Update the customer currencies option after an order status change. @@ -332,7 +312,7 @@ public function init_rest_api() { return; } - $api_controller = new RestController( \WC_Payments::create_api_client() ); + $api_controller = new RestController(); $api_controller->register_routes(); } @@ -355,19 +335,19 @@ public function init_widgets() { * @return array The new settings pages. */ public function init_settings_pages( $settings_pages ): array { - // We don't need to check if Stripe is connected for the + // We don't need to check if the payment provider is connected for the // Settings page generation on the incoming CLI and async job calls. if ( ( defined( 'WP_CLI' ) && WP_CLI ) || ( defined( 'WPCOM_JOBS' ) && WPCOM_JOBS ) ) { return $settings_pages; } - if ( $this->payments_account->is_stripe_connected() ) { + if ( $this->payments_account->is_provider_connected() ) { $settings = new Settings( $this ); $settings->init_hooks(); $settings_pages[] = $settings; } else { - $settings_onboard_cta = new SettingsOnboardCta( $this ); + $settings_onboard_cta = new SettingsOnboardCta( $this, $this->payments_account ); $settings_onboard_cta->init_hooks(); $settings_pages[] = $settings_onboard_cta; @@ -392,7 +372,7 @@ public function enqueue_admin_scripts() { $this->register_admin_scripts(); wp_enqueue_script( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); - WC_Payments_Utils::enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); + wp_enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); } /** @@ -415,7 +395,7 @@ public function add_props_to_wcpay_js_config( $config ) { */ public function clear_cache() { Logger::debug( 'Clearing the cache to force new rates to be fetched from the server.' ); - $this->database_cache->delete( Database_Cache::CURRENCIES_KEY ); + $this->cache->delete( MultiCurrencyCacheInterface::CURRENCIES_KEY ); } /** @@ -426,14 +406,14 @@ public function clear_cache() { * @return ?array */ public function get_cached_currencies() { - $cached_data = $this->database_cache->get( Database_Cache::CURRENCIES_KEY ); - // If connection to server cannot be established, or if Stripe is not connected, or if the account is rejected, return expired data or null. - if ( ! $this->payments_api_client->is_server_connected() || ! $this->payments_account->is_stripe_connected() || $this->payments_account->is_account_rejected() ) { + $cached_data = $this->cache->get( MultiCurrencyCacheInterface::CURRENCIES_KEY ); + // If connection to server cannot be established, or if payment provider is not connected, or if the account is rejected, return expired data or null. + if ( ! $this->payments_api_client->is_server_connected() || ! $this->payments_account->is_provider_connected() || $this->payments_account->is_account_rejected() ) { return $cached_data ?? null; } - return $this->database_cache->get_or_add( - Database_Cache::CURRENCIES_KEY, + return $this->cache->get_or_add( + MultiCurrencyCacheInterface::CURRENCIES_KEY, function () { try { $currency_data = $this->payments_api_client->get_currency_rates( strtolower( get_woocommerce_currency() ) ); @@ -441,7 +421,7 @@ function () { 'currencies' => $currency_data, 'updated' => time(), ]; - } catch ( API_Exception $e ) { + } catch ( \Exception $e ) { return null; } }, @@ -583,7 +563,7 @@ public function update_single_currency_settings( string $currency_code, string $ if ( ! is_numeric( $manual_rate ) || 0 >= $manual_rate ) { $message = 'Invalid manual currency rate passed to update_single_currency_settings: ' . $manual_rate; Logger::error( $message ); - throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 500 ); } update_option( 'wcpay_multi_currency_manual_rate_' . $currency_code, $manual_rate ); } @@ -621,94 +601,6 @@ public function maybe_update_customer_currencies_option( $order_id ) { update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies ); } - /** - * Sets up the available currencies, which are alphabetical by name. - * - * @return void - */ - private function initialize_available_currencies() { - // Add default store currency with a rate of 1.0. - $woocommerce_currency = get_woocommerce_currency(); - $this->available_currencies[ $woocommerce_currency ] = new Currency( $this->localization_service, $woocommerce_currency, 1.0 ); - - $available_currencies = []; - - $currencies = $this->get_account_available_currencies(); - $cache_data = $this->get_cached_currencies(); - - foreach ( $currencies as $currency_code ) { - $currency_rate = $cache_data['currencies'][ $currency_code ] ?? 1.0; - $update_time = $cache_data['updated'] ?? null; - $new_currency = new Currency( $this->localization_service, $currency_code, $currency_rate, $update_time ); - - // Add this to our list of available currencies. - $available_currencies[ $new_currency->get_name() ] = $new_currency; - } - - ksort( $available_currencies ); - - foreach ( $available_currencies as $currency ) { - $this->available_currencies[ $currency->get_code() ] = $currency; - } - } - - /** - * Sets up the enabled currencies. - * - * @return void - */ - private function initialize_enabled_currencies() { - $available_currencies = $this->get_available_currencies(); - $enabled_currency_codes = get_option( $this->id . '_enabled_currencies', [] ); - $enabled_currency_codes = is_array( $enabled_currency_codes ) ? $enabled_currency_codes : []; - $default_code = $this->get_default_currency()->get_code(); - $default = []; - $enabled_currency_codes[] = $default_code; - - // This allows to keep the alphabetical sorting by name. - $enabled_currencies = array_filter( - $available_currencies, - function ( $currency ) use ( $enabled_currency_codes ) { - return in_array( $currency->get_code(), $enabled_currency_codes, true ); - } - ); - - $this->enabled_currencies = []; - - foreach ( $enabled_currencies as $enabled_currency ) { - // Get the charm and rounding for each enabled currency and add the currencies to the object property. - $currency = clone $enabled_currency; - $charm = get_option( $this->id . '_price_charm_' . $currency->get_id(), 0.00 ); - $rounding = get_option( $this->id . '_price_rounding_' . $currency->get_id(), $currency->get_is_zero_decimal() ? '100' : '1.00' ); - $currency->set_charm( $charm ); - $currency->set_rounding( $rounding ); - - // If the currency is set to be manual, set the rate to the stored manual rate. - $type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), 'automatic' ); - if ( 'manual' === $type ) { - $manual_rate = get_option( $this->id . '_manual_rate_' . $currency->get_id(), $currency->get_rate() ); - $currency->set_rate( $manual_rate ); - } - - $this->enabled_currencies[ $currency->get_code() ] = $currency; - } - - // Set default currency to the top of the list. - $default[ $default_code ] = $this->enabled_currencies[ $default_code ]; - unset( $this->enabled_currencies[ $default_code ] ); - $this->enabled_currencies = array_merge( $default, $this->enabled_currencies ); - } - - /** - * Sets the default currency. - * - * @return void - */ - private function set_default_currency() { - $available_currencies = $this->get_available_currencies(); - $this->default_currency = $available_currencies[ get_woocommerce_currency() ] ?? null; - } - /** * Gets the currencies available. Initializes it if needed. * @@ -963,7 +855,7 @@ public function get_raw_conversion( float $amount, string $to_currency, string $ if ( 0 >= $from_currency_rate ) { $message = 'Invalid rate for from_currency in get_raw_conversion: ' . $from_currency_rate; Logger::error( $message ); - throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 500 ); } $amount = $amount * ( $to_currency_rate / $from_currency_rate ); @@ -1039,24 +931,20 @@ public function display_geolocation_currency_update_notice() { } $message = sprintf( - /* translators: %1 User's country, %2 Selected currency name, %3 Default store currency name */ - __( 'We noticed you\'re visiting from %1$s. We\'ve updated our prices to %2$s for your shopping convenience. Use %3$s instead.', 'woocommerce-payments' ), + /* translators: %1 User's country, %2 Selected currency name, %3 Default store currency name, %4 Link to switch currency */ + __( 'We noticed you\'re visiting from %1$s. We\'ve updated our prices to %2$s for your shopping convenience. Use %3$s instead.', 'woocommerce-payments' ), apply_filters( self::FILTER_PREFIX . 'override_notice_country', WC()->countries->countries[ $country ] ), apply_filters( self::FILTER_PREFIX . 'override_notice_currency_name', $current_currency->get_name() ), - $currencies[ $store_currency ] + esc_html( $currencies[ $store_currency ] ), + esc_url( '?currency=' . $store_currency ) ); $notice_id = md5( $message ); echo '

    '; } @@ -1080,14 +968,14 @@ public function set_new_customer_currency_meta( $customer_id ) { * * @return void */ - public static function add_woo_admin_notes() { + public function add_woo_admin_notes() { // Do not try to add notes on ajax requests to improve their performance. if ( wp_doing_ajax() ) { return; } if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { - NoteMultiCurrencyAvailable::set_account( WC_Payments::get_account_service() ); + NoteMultiCurrencyAvailable::set_account( $this->payments_account ); NoteMultiCurrencyAvailable::possibly_add_note(); } } @@ -1104,292 +992,138 @@ public static function remove_woo_admin_notes() { } /** - * Gets the price after adjusting it with the rounding and charm settings. - * - * @param float $price The price to be adjusted. - * @param bool $apply_charm_pricing Whether charm pricing should be applied. - * @param Currency $currency The currency to be used when adjusting. + * Checks if the merchant has enabled automatic currency switching and geolocation. * - * @return float The adjusted price. + * @return bool */ - protected function get_adjusted_price( $price, $apply_charm_pricing, $currency ): float { - $price = $this->ceil_price( $price, (float) $currency->get_rounding() ); - - if ( $apply_charm_pricing ) { - $price += (float) $currency->get_charm(); - } - - // Do not return negative prices (possible because of $currency->get_charm()). - return max( 0, $price ); + public function is_using_auto_currency_switching(): bool { + return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' ); } /** - * Ceils the price to the next number based on the rounding value. - * - * @param float $price The price to be ceiled. - * @param float $rounding The rounding option. + * Checks if the merchant has enabled the currency switcher widget. * - * @return float The ceiled price. + * @return bool */ - protected function ceil_price( float $price, float $rounding ): float { - if ( 0.00 === $rounding ) { - return $price; - } - return ceil( $price / $rounding ) * $rounding; + public function is_using_storefront_switcher(): bool { + return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' ); } /** - * Returns the currency code stored for the user or in the session. + * Gets the store settings. * - * @return string|null Currency code. + * @return array The store settings. */ - private function get_stored_currency_code() { - $user_id = get_current_user_id(); - - if ( $user_id ) { - return get_user_meta( $user_id, self::CURRENCY_META_KEY, true ); - } - - WC()->initialize_session(); - $currency_code = WC()->session->get( self::CURRENCY_SESSION_KEY ); - - return is_string( $currency_code ) ? $currency_code : null; + public function get_settings() { + return [ + $this->id . '_enable_auto_currency' => $this->is_using_auto_currency_switching(), + $this->id . '_enable_storefront_switcher' => $this->is_using_storefront_switcher(), + 'site_theme' => wp_get_theme()->get( 'Name' ), + 'date_format' => esc_attr( get_option( 'date_format', 'F j, Y' ) ), + 'time_format' => esc_attr( get_option( 'time_format', 'g:i a' ) ), + 'store_url' => esc_attr( get_page_uri( wc_get_page_id( 'shop' ) ) ), + ]; } /** - * Checks to see if the store currency has changed. If it has, this will - * also update the option containing the store currency. + * Updates the store settings * - * @return bool + * @param array $params Update requested values. + * + * @return void */ - private function check_store_currency_for_change(): bool { - $last_known_currency = get_option( $this->id . '_store_currency', false ); - $woocommerce_currency = get_woocommerce_currency(); - - // If the last known currency was not set, update the option to set it and return false. - if ( ! $last_known_currency ) { - update_option( $this->id . '_store_currency', $woocommerce_currency ); - return false; - } + public function update_settings( $params ) { + $updateable_options = [ + 'wcpay_multi_currency_enable_auto_currency', + 'wcpay_multi_currency_enable_storefront_switcher', + ]; - if ( $last_known_currency !== $woocommerce_currency ) { - update_option( $this->id . '_store_currency', $woocommerce_currency ); - return true; + foreach ( $updateable_options as $key ) { + if ( isset( $params[ $key ] ) ) { + update_option( $key, sanitize_text_field( $params[ $key ] ) ); + } } - - return false; } /** - * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display. + * Apply client order currency format and reduces the rounding precision to 2. * - * @return void + * @return void */ - private function update_manual_rate_currencies_notice_option() { - $enabled_currencies = $this->get_enabled_currencies(); - $manual_currencies = []; - - // Check enabled currencies for manual rates. - foreach ( $enabled_currencies as $currency ) { - $rate_type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), false ); - if ( 'manual' === $rate_type ) { - $manual_currencies[] = $currency->get_name(); + public function set_client_format_and_rounding_precision() { + $screen = get_current_screen(); + if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) : + $order = wc_get_order(); + if ( ! $order ) { + return; } - } + $currency = $order->get_currency(); + $currency_format_num_decimals = $this->backend_currencies->get_price_decimals( $currency ); + $currency_format_decimal_sep = $this->backend_currencies->get_price_decimal_separator( $currency ); + $currency_format_thousand_sep = $this->backend_currencies->get_price_thousand_separator( $currency ); + $currency_format = str_replace( [ '%1$s', '%2$s', ' ' ], [ '%s', '%v', ' ' ], $this->backend_currencies->get_woocommerce_price_format( $currency ) ); - if ( 0 < count( $manual_currencies ) ) { - update_option( $this->id . '_show_store_currency_changed_notice', $manual_currencies ); - } + $rounding_precision = wc_get_price_decimals() ?? wc_get_rounding_precision(); + ?> + + remove_currency_settings( $currency ); - } + public function register_script_with_dependencies( string $handler, string $script, array $additional_dependencies = [] ) { + $script_file = $script . '.js'; + $script_src_url = plugins_url( $script_file, $this->gateway_context['plugin_file_path'] ); + $script_asset_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ) . $script . '.asset.php'; + $script_asset = file_exists( $script_asset_path ) ? require $script_asset_path : [ 'dependencies' => [] ]; + $all_dependencies = array_merge( $script_asset['dependencies'], $additional_dependencies ); + + wp_register_script( + $handler, + $script_src_url, + $all_dependencies, + $this->get_file_version( $script_file ), + true + ); } /** - * Will remove a currency's settings if it is not enabled. + * Get the file modified time as a cache buster if we're in dev mode. * - * @param mixed $currency Currency object or 3 letter currency code. + * @param string $file Local path to the file. * - * @return void + * @return string */ - private function remove_currency_settings( $currency ) { - $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency ); + public function get_file_version( $file ) { + $plugin_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ); - // Bail if the currency code passed is not 3 characters, or if the currency is presently enabled. - if ( 3 !== strlen( $code ) || isset( $this->get_enabled_currencies()[ $code ] ) ) { - return; + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) { + return (string) filemtime( $plugin_path . trim( $file, '/' ) ); } - $settings = [ - 'price_charm', - 'price_rounding', - 'manual_rate', - 'exchange_rate', - ]; - - // Go through each setting and remove them. - foreach ( $settings as $setting ) { - delete_option( $this->id . '_' . $setting . '_' . strtolower( $code ) ); - } + return $this->gateway_context['plugin_version']; } /** - * Returns the currencies enabled for the Stripe account that are - * also available in WC. + * Validates the given currency code. * - * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook. + * @param string $currency_code The currency code to check validity. * - * @return array Array with the available currencies' codes. - */ - private function get_account_available_currencies(): array { - // If Stripe is not connected, return an empty array. This prevents using MC without being connected to Stripe. - if ( ! $this->payments_account->is_stripe_connected() ) { - return []; - } - - $wc_currencies = array_keys( get_woocommerce_currencies() ); - $account_currencies = $wc_currencies; - - $account = $this->payments_account->get_cached_account_data(); - $supported_currencies = $this->payments_account->get_account_customer_supported_currencies(); - if ( $account && ! empty( $supported_currencies ) ) { - $account_currencies = array_map( 'strtoupper', $supported_currencies ); - } - - /** - * Filter the available currencies for WooCommerce Multi-Currency. - * - * This filter can be used to modify the currencies available for WC Pay - * Multi-Currency. Currencies have to be added in uppercase and should - * also be available in `get_woocommerce_currencies` for them to work. - * - * @since 2.8.0 - * - * @param array $available_currencies Current available currencies. Calculated based on - * WC Pay's account currencies and WC currencies. - */ - return apply_filters( self::FILTER_PREFIX . 'available_currencies', array_intersect( $account_currencies, $wc_currencies ) ); - } - - /** - * Checks if the merchant has enabled automatic currency switching and geolocation. - * - * @return bool - */ - public function is_using_auto_currency_switching(): bool { - return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' ); - } - - /** - * Checks if the merchant has enabled the currency switcher widget. - * - * @return bool - */ - public function is_using_storefront_switcher(): bool { - return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' ); - } - - /** - * Gets the store settings. - * - * @return array The store settings. - */ - public function get_settings() { - return [ - $this->id . '_enable_auto_currency' => $this->is_using_auto_currency_switching(), - $this->id . '_enable_storefront_switcher' => $this->is_using_storefront_switcher(), - 'site_theme' => wp_get_theme()->get( 'Name' ), - 'date_format' => esc_attr( get_option( 'date_format', 'F j, Y' ) ), - 'time_format' => esc_attr( get_option( 'time_format', 'g:i a' ) ), - 'store_url' => esc_attr( get_page_uri( wc_get_page_id( 'shop' ) ) ), - ]; - } - - /** - * Updates the store settings - * - * @param array $params Update requested values. - * - * @return void - */ - public function update_settings( $params ) { - $updateable_options = [ - 'wcpay_multi_currency_enable_auto_currency', - 'wcpay_multi_currency_enable_storefront_switcher', - ]; - - foreach ( $updateable_options as $key ) { - if ( isset( $params[ $key ] ) ) { - update_option( $key, sanitize_text_field( $params[ $key ] ) ); - } - } - } - - /** - * Apply client order currency format and reduces the rounding precision to 2. - * - * @return void - */ - public function set_client_format_and_rounding_precision() { - $screen = get_current_screen(); - if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) : - $order = wc_get_order(); - if ( ! $order ) { - return; - } - $currency = $order->get_currency(); - $currency_format_num_decimals = $this->backend_currencies->get_price_decimals( $currency ); - $currency_format_decimal_sep = $this->backend_currencies->get_price_decimal_separator( $currency ); - $currency_format_thousand_sep = $this->backend_currencies->get_price_thousand_separator( $currency ); - $currency_format = str_replace( [ '%1$s', '%2$s', ' ' ], [ '%s', '%v', ' ' ], $this->backend_currencies->get_woocommerce_price_format( $currency ) ); - - $rounding_precision = wc_get_price_decimals() ?? wc_get_rounding_precision(); - ?> - - available_currencies ) @@ -1413,52 +1147,6 @@ public function possible_simulation_activation() { $this->simulate_client_currency(); } - /** - * Enables simulation of client browser currency. - * - * @return void - */ - private function simulate_client_currency() { - if ( ! $this->simulation_params['enable_auto_currency'] ) { - return; - } - - $countries = WC_Payments_Utils::supported_countries(); - - $predefined_simulation_currencies = [ - Currency_Code::UNITED_STATES_DOLLAR => $countries[ Country_Code::UNITED_STATES ], - Currency_Code::POUND_STERLING => $countries[ Country_Code::UNITED_KINGDOM ], - ]; - - $simulation_currency = Currency_Code::UNITED_STATES_DOLLAR === get_option( 'woocommerce_currency', Currency_Code::UNITED_STATES_DOLLAR ) ? Currency_Code::POUND_STERLING : Currency_Code::UNITED_STATES_DOLLAR; - $simulation_currency_name = $this->available_currencies[ $simulation_currency ]->get_name(); - $simulation_country = $predefined_simulation_currencies[ $simulation_currency ]; - - // Simulate client currency from geolocation. - add_filter( - 'wcpay_multi_currency_override_notice_currency_name', - function ( $selected_currency_name ) use ( $simulation_currency_name ) { - return $simulation_currency_name; - } - ); - - // Simulate client country from geolocation. - add_filter( - 'wcpay_multi_currency_override_notice_country', - function ( $selected_country ) use ( $simulation_country ) { - return $simulation_country; - } - ); - - // Always display the notice on simulation screen, prevent duplicate hooks. - if ( ! has_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ) ) { - add_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ); - } - - // Skip recalculating the cart to prevent infinite loop in simulation. - remove_action( 'wp_loaded', [ $this, 'recalculate_cart' ] ); - } - /** * Returns whether the simulation querystring param is set and active * @@ -1520,45 +1208,6 @@ public function get_multi_currency_onboarding_simulation_variables() { return $values; } - /** - * Adds the required querystring parameters to all urls in preview pages. - * - * @return void - */ - private function add_simulation_params_to_preview_urls() { - $params = $this->simulation_params; - add_filter( - 'wp_footer', - function () use ( $params ) { - ?> - - ceil_price( $price, (float) $currency->get_rounding() ); + + if ( $apply_charm_pricing ) { + $price += (float) $currency->get_charm(); + } + + // Do not return negative prices (possible because of $currency->get_charm()). + return max( 0, $price ); + } + + /** + * Ceils the price to the next number based on the rounding value. + * + * @param float $price The price to be ceiled. + * @param float $rounding The rounding option. + * + * @return float The ceiled price. + */ + protected function ceil_price( float $price, float $rounding ): float { + if ( 0.00 === $rounding ) { + return $price; + } + return ceil( $price / $rounding ) * $rounding; + } + + /** + * Sets up the available currencies, which are alphabetical by name. + * + * @return void + */ + private function initialize_available_currencies() { + // Add default store currency with a rate of 1.0. + $woocommerce_currency = get_woocommerce_currency(); + $this->available_currencies[ $woocommerce_currency ] = new Currency( $this->localization_service, $woocommerce_currency, 1.0 ); + + $available_currencies = []; + + $currencies = $this->get_account_available_currencies(); + $cache_data = $this->get_cached_currencies(); + + foreach ( $currencies as $currency_code ) { + $currency_rate = $cache_data['currencies'][ $currency_code ] ?? 1.0; + $update_time = $cache_data['updated'] ?? null; + $new_currency = new Currency( $this->localization_service, $currency_code, $currency_rate, $update_time ); + + // Add this to our list of available currencies. + $available_currencies[ $new_currency->get_name() ] = $new_currency; + } + + ksort( $available_currencies ); + + foreach ( $available_currencies as $currency ) { + $this->available_currencies[ $currency->get_code() ] = $currency; + } + } + + /** + * Sets up the enabled currencies. + * + * @return void + */ + private function initialize_enabled_currencies() { + $available_currencies = $this->get_available_currencies(); + $enabled_currency_codes = get_option( $this->id . '_enabled_currencies', [] ); + $enabled_currency_codes = is_array( $enabled_currency_codes ) ? $enabled_currency_codes : []; + $default_code = $this->get_default_currency()->get_code(); + $default = []; + $enabled_currency_codes[] = $default_code; + + // This allows to keep the alphabetical sorting by name. + $enabled_currencies = array_filter( + $available_currencies, + function ( $currency ) use ( $enabled_currency_codes ) { + return in_array( $currency->get_code(), $enabled_currency_codes, true ); + } + ); + + $this->enabled_currencies = []; + + foreach ( $enabled_currencies as $enabled_currency ) { + // Get the charm and rounding for each enabled currency and add the currencies to the object property. + $currency = clone $enabled_currency; + $charm = get_option( $this->id . '_price_charm_' . $currency->get_id(), 0.00 ); + $rounding = get_option( $this->id . '_price_rounding_' . $currency->get_id(), $currency->get_is_zero_decimal() ? '100' : '1.00' ); + $currency->set_charm( $charm ); + $currency->set_rounding( $rounding ); + + // If the currency is set to be manual, set the rate to the stored manual rate. + $type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), 'automatic' ); + if ( 'manual' === $type ) { + $manual_rate = get_option( $this->id . '_manual_rate_' . $currency->get_id(), $currency->get_rate() ); + $currency->set_rate( $manual_rate ); + } + + $this->enabled_currencies[ $currency->get_code() ] = $currency; + } + + // Set default currency to the top of the list. + $default[ $default_code ] = $this->enabled_currencies[ $default_code ]; + unset( $this->enabled_currencies[ $default_code ] ); + $this->enabled_currencies = array_merge( $default, $this->enabled_currencies ); + } + + /** + * Sets the default currency. + * + * @return void + */ + private function set_default_currency() { + $available_currencies = $this->get_available_currencies(); + $this->default_currency = $available_currencies[ get_woocommerce_currency() ] ?? null; + } + + /** + * Returns the currency code stored for the user or in the session. + * + * @return string|null Currency code. + */ + private function get_stored_currency_code() { + $user_id = get_current_user_id(); + + if ( $user_id ) { + return get_user_meta( $user_id, self::CURRENCY_META_KEY, true ); + } + + WC()->initialize_session(); + $currency_code = WC()->session->get( self::CURRENCY_SESSION_KEY ); + + return is_string( $currency_code ) ? $currency_code : null; + } + + /** + * Checks to see if the store currency has changed. If it has, this will + * also update the option containing the store currency. + * + * @return bool + */ + private function check_store_currency_for_change(): bool { + $last_known_currency = get_option( $this->id . '_store_currency', false ); + $woocommerce_currency = get_woocommerce_currency(); + + // If the last known currency was not set, update the option to set it and return false. + if ( ! $last_known_currency ) { + update_option( $this->id . '_store_currency', $woocommerce_currency ); + return false; + } + + if ( $last_known_currency !== $woocommerce_currency ) { + update_option( $this->id . '_store_currency', $woocommerce_currency ); + return true; + } + + return false; + } + + /** + * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display. + * + * @return void + */ + private function update_manual_rate_currencies_notice_option() { + $enabled_currencies = $this->get_enabled_currencies(); + $manual_currencies = []; + + // Check enabled currencies for manual rates. + foreach ( $enabled_currencies as $currency ) { + $rate_type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), false ); + if ( 'manual' === $rate_type ) { + $manual_currencies[] = $currency->get_name(); + } + } + + if ( 0 < count( $manual_currencies ) ) { + update_option( $this->id . '_show_store_currency_changed_notice', $manual_currencies ); + } + } + + /** + * Accepts an array of currencies that should have their settings removed. + * + * @param array $currencies Array of Currency objects or 3 letter currency codes. + * + * @return void + */ + private function remove_currencies_settings( array $currencies ) { + + foreach ( $currencies as $currency ) { + $this->remove_currency_settings( $currency ); + } + } + + /** + * Will remove a currency's settings if it is not enabled. + * + * @param mixed $currency Currency object or 3 letter currency code. + * + * @return void + */ + private function remove_currency_settings( $currency ) { + $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency ); + + // Bail if the currency code passed is not 3 characters, or if the currency is presently enabled. + if ( 3 !== strlen( $code ) || isset( $this->get_enabled_currencies()[ $code ] ) ) { + return; + } + + $settings = [ + 'price_charm', + 'price_rounding', + 'manual_rate', + 'exchange_rate', + ]; + + // Go through each setting and remove them. + foreach ( $settings as $setting ) { + delete_option( $this->id . '_' . $setting . '_' . strtolower( $code ) ); + } + } + + /** + * Returns the currencies enabled for the payment provider account that are + * also available in WC. + * + * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook. + * + * @return array Array with the available currencies' codes. + */ + private function get_account_available_currencies(): array { + // If the payment provider is not connected, return an empty array. This prevents using MC without being connected to the payment provider. + if ( ! $this->payments_account->is_provider_connected() ) { + return []; + } + + $wc_currencies = array_keys( get_woocommerce_currencies() ); + $account_currencies = $wc_currencies; + + $account = $this->payments_account->get_cached_account_data(); + $supported_currencies = $this->payments_account->get_account_customer_supported_currencies(); + if ( $account && ! empty( $supported_currencies ) ) { + $account_currencies = array_map( 'strtoupper', $supported_currencies ); + } + + /** + * Filter the available currencies for WooCommerce Multi-Currency. + * + * This filter can be used to modify the currencies available for WC Pay + * Multi-Currency. Currencies have to be added in uppercase and should + * also be available in `get_woocommerce_currencies` for them to work. + * + * @since 2.8.0 + * + * @param array $available_currencies Current available currencies. Calculated based on + * WC Pay's account currencies and WC currencies. + */ + return apply_filters( self::FILTER_PREFIX . 'available_currencies', array_intersect( $account_currencies, $wc_currencies ) ); + } + + /** + * Register the CSS and JS admin scripts. + * + * @return void + */ + private function register_admin_scripts() { + $this->register_script_with_dependencies( 'WCPAY_MULTI_CURRENCY_SETTINGS', 'dist/multi-currency', [ 'WCPAY_ADMIN_SETTINGS' ] ); + + wp_register_style( + 'WCPAY_MULTI_CURRENCY_SETTINGS', + plugins_url( 'dist/multi-currency.css', $this->gateway_context['plugin_file_path'] ), + [ 'wc-components', 'WCPAY_ADMIN_SETTINGS' ], + $this->get_file_version( 'dist/multi-currency.css' ), + 'all' + ); + } + + /** + * Enables simulation of client browser currency. + * + * @return void + */ + private function simulate_client_currency() { + if ( ! $this->simulation_params['enable_auto_currency'] ) { + return; + } + + $countries = $this->payments_account->get_supported_countries(); + + $predefined_simulation_currencies = [ + 'USD' => $countries['US'], + 'GBP' => $countries['GB'], + ]; + + $simulation_currency = 'USD' === get_option( 'woocommerce_currency', 'USD' ) ? 'GBP' : 'USD'; + $simulation_currency_name = $this->available_currencies[ $simulation_currency ]->get_name(); + $simulation_country = $predefined_simulation_currencies[ $simulation_currency ]; + + // Simulate client currency from geolocation. + add_filter( + 'wcpay_multi_currency_override_notice_currency_name', + function ( $selected_currency_name ) use ( $simulation_currency_name ) { + return $simulation_currency_name; + } + ); + + // Simulate client country from geolocation. + add_filter( + 'wcpay_multi_currency_override_notice_country', + function ( $selected_country ) use ( $simulation_country ) { + return $simulation_country; + } + ); + + // Always display the notice on simulation screen, prevent duplicate hooks. + if ( ! has_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ) ) { + add_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ); + } + + // Skip recalculating the cart to prevent infinite loop in simulation. + remove_action( 'wp_loaded', [ $this, 'recalculate_cart' ] ); + } + + /** + * Adds the required querystring parameters to all urls in preview pages. + * + * @return void + */ + private function add_simulation_params_to_preview_urls() { + $params = $this->simulation_params; + add_filter( + 'wp_footer', + function () use ( $params ) { + ?> + + is_stripe_connected() ) { + if ( is_null( self::$account ) || ! self::$account->is_provider_connected() ) { return; } @@ -80,9 +80,9 @@ public static function possibly_add_note() { /** * Sets the account service instance reference on the class. * - * @param WC_Payments_Account $account account service instance. + * @param MultiCurrencyAccountInterface $account account service instance. */ - public static function set_account( WC_Payments_Account $account ) { + public static function set_account( MultiCurrencyAccountInterface $account ) { self::$account = $account; } } diff --git a/includes/multi-currency/RestController.php b/multi-currency/src/RestController.php similarity index 92% rename from includes/multi-currency/RestController.php rename to multi-currency/src/RestController.php index 0266b5f4e76..e739654e9bd 100644 --- a/includes/multi-currency/RestController.php +++ b/multi-currency/src/RestController.php @@ -1,13 +1,13 @@ set_enabled_currencies( $enabled ); $response = $this->get_store_currencies(); } catch ( InvalidCurrencyException $e ) { - $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); + $response = new \WP_Error( $e->getCode(), $e->getMessage() ); } return rest_ensure_response( $response ); } @@ -178,7 +185,7 @@ public function get_single_currency_settings( $request ) { try { $response = WC_Payments_Multi_Currency()->get_single_currency_settings( $currency_code ); } catch ( InvalidCurrencyException $e ) { - $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); + $response = new \WP_Error( $e->getCode(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -201,8 +208,8 @@ public function update_single_currency_settings( $request ) { try { WC_Payments_Multi_Currency()->update_single_currency_settings( $currency_code, $exchange_rate_type, $price_rounding, $price_charm, $manual_rate ); $response = WC_Payments_Multi_Currency()->get_single_currency_settings( $currency_code ); - } catch ( Base_Exception $e ) { - $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); + } catch ( Exception $e ) { + $response = new \WP_Error( $e->getCode(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -229,4 +236,11 @@ public function update_settings( $request ) { WC_Payments_Multi_Currency()->update_settings( $params ); return rest_ensure_response( WC_Payments_Multi_Currency()->get_settings() ); } + + /** + * Verify access. + */ + public function check_permission() { + return current_user_can( 'manage_woocommerce' ); + } } diff --git a/includes/multi-currency/Settings.php b/multi-currency/src/Settings.php similarity index 100% rename from includes/multi-currency/Settings.php rename to multi-currency/src/Settings.php diff --git a/includes/multi-currency/SettingsOnboardCta.php b/multi-currency/src/SettingsOnboardCta.php similarity index 74% rename from includes/multi-currency/SettingsOnboardCta.php rename to multi-currency/src/SettingsOnboardCta.php index ee89ea5638c..59a2eee286f 100644 --- a/includes/multi-currency/SettingsOnboardCta.php +++ b/multi-currency/src/SettingsOnboardCta.php @@ -7,6 +7,8 @@ namespace WCPay\MultiCurrency; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; + defined( 'ABSPATH' ) || exit; /** @@ -27,15 +29,24 @@ class SettingsOnboardCta extends \WC_Settings_Page { */ private $multi_currency; + /** + * Instance of MultiCurrencyAccountInterface. + * + * @var MultiCurrencyAccountInterface + */ + private $payments_account; + /** * Constructor. * - * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param MultiCurrencyAccountInterface $payments_account Payments Account instance. */ - public function __construct( MultiCurrency $multi_currency ) { - $this->multi_currency = $multi_currency; - $this->id = $this->multi_currency->id; - $this->label = _x( 'Multi-currency', 'Settings tab label', 'woocommerce-payments' ); + public function __construct( MultiCurrency $multi_currency, MultiCurrencyAccountInterface $payments_account ) { + $this->multi_currency = $multi_currency; + $this->payments_account = $payments_account; + $this->id = $this->multi_currency->id; + $this->label = _x( 'Multi-currency', 'Settings tab label', 'woocommerce-payments' ); parent::__construct(); } @@ -53,7 +64,7 @@ public function init_hooks() { * Output the call to action button if needing to onboard. */ public function currencies_settings_onboarding_cta() { - $href = \WC_Payments_Account::get_connect_page_url(); + $href = $this->payments_account->get_provider_onboarding_page_url(); ?>

    diff --git a/includes/multi-currency/StorefrontIntegration.php b/multi-currency/src/StorefrontIntegration.php similarity index 100% rename from includes/multi-currency/StorefrontIntegration.php rename to multi-currency/src/StorefrontIntegration.php diff --git a/includes/multi-currency/Tracking.php b/multi-currency/src/Tracking.php similarity index 100% rename from includes/multi-currency/Tracking.php rename to multi-currency/src/Tracking.php diff --git a/includes/multi-currency/UserSettings.php b/multi-currency/src/UserSettings.php similarity index 100% rename from includes/multi-currency/UserSettings.php rename to multi-currency/src/UserSettings.php diff --git a/includes/multi-currency/Utils.php b/multi-currency/src/Utils.php similarity index 71% rename from includes/multi-currency/Utils.php rename to multi-currency/src/Utils.php index 64e5356a77a..1c3fd074076 100644 --- a/includes/multi-currency/Utils.php +++ b/multi-currency/src/Utils.php @@ -57,7 +57,7 @@ public function is_page_with_vars( array $pages, array $vars ): bool { * @return boolean */ public static function is_admin_api_request(): bool { - return 0 === stripos( wp_get_referer(), admin_url() ) && WC()->is_rest_api_request() && ! \WC_Payments_Utils::is_store_api_request(); + return 0 === stripos( wp_get_referer(), admin_url() ) && WC()->is_rest_api_request() && ! self::is_store_api_request(); } @@ -71,4 +71,21 @@ public static function is_admin_api_request(): bool { public static function set_customer_session_cookie( bool $set ) { WC()->session->set_customer_session_cookie( $set ); } + + /** + * Returns true if the request is a store REST API request. + * + * @return bool + */ + public static function is_store_api_request() { + if ( function_exists( 'WC' ) && method_exists( WC(), 'is_store_api_request' ) ) { + return WC()->is_store_api_request(); + } + // The logic below is sourced from `WC()->is_store_api_request()`. + if ( empty( $_SERVER['REQUEST_URI'] ) ) { + return false; + } + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + return false !== strpos( $_SERVER['REQUEST_URI'], trailingslashit( rest_get_url_prefix() ) . 'wc/store/' ); + } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6e21606d2bb..a0bf916f7a9 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -102,7 +102,7 @@ tests/* - includes/multi-currency + multi-currency/src src diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2a5164e8af3..1eb5fab3f04 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -46,13 +46,13 @@ WC_Subscriptions_Product - + \WC_Product_Addons_Helper \WC_Product_Addons_Helper - + \WC_Name_Your_Price_Helpers \WC_Name_Your_Price_Helpers diff --git a/tasks/release.js b/tasks/release.js index 92559af4ffb..72c3eccdef7 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -14,11 +14,11 @@ const targetFolder = 'release/' + pluginSlug; const filesToCopy = [ 'assets', 'dist', - 'includes', 'i18n', + 'includes', 'languages', - 'src', 'lib', + 'src', 'templates', 'vendor', 'woocommerce-payments.php', @@ -43,6 +43,10 @@ rm( 'dist/*.map' ); // copy the directories to the release folder cp( '-Rf', filesToCopy, targetFolder ); +// copy the multi-currency files +mkdir( '-p', targetFolder + '/multi-currency' ); +cp( '-R', 'multi-currency/src', targetFolder + '/multi-currency/src' ); + const output = fs.createWriteStream( releaseFolder + '/' + pluginSlug + '.zip' ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 18c11bc9553..03060196d4c 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -2,13 +2,18 @@ const { jsWithBabel: tsjPreset } = require( 'ts-jest/presets' ); module.exports = { rootDir: '../../', - moduleDirectories: [ 'node_modules', '/client' ], + moduleDirectories: [ + 'node_modules', + '/client', + '/multi-currency/client', + ], moduleNameMapper: { '^react$': '/node_modules/react', '^react-dom$': '/node_modules/react-dom', '^moment$': '/node_modules/moment', '^moment-timezone$': '/node_modules/moment-timezone', '^wcpay(.*)$': '/client$1', + '^multi-currency(.*)$': '/multi-currency/client$1', '^iti/utils$': '/node_modules/intl-tel-input/build/js/utils', '^assets(.*?)(\\?.*)?$': '/assets$1', '^@woocommerce/blocks-registry$': diff --git a/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php index 5b71b8f8e7f..5edd60cd8a6 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php @@ -6,6 +6,10 @@ */ use WCPay\MultiCurrency\Compatibility\WooCommerceSubscriptions; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyCacheInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Utils; @@ -62,7 +66,16 @@ class WCPay_Multi_Currency_WooCommerceSubscriptions_Tests extends WCPAY_UnitTest public function set_up() { parent::set_up(); - $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); + $mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); + $mock_account = $this->createMock( MultiCurrencyAccountInterface::class ); + $mock_localization = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $gateway_context = []; + + $this->mock_multi_currency = $this->getMockBuilder( MultiCurrency::class ) + ->setConstructorArgs( [ $gateway_context, $mock_api_client, $mock_account, $mock_localization, $mock_cache ] ) + ->getMock(); + $this->mock_utils = $this->createMock( Utils::class ); $this->woocommerce_subscriptions = new WooCommerceSubscriptions( $this->mock_multi_currency, $this->mock_utils ); @@ -828,18 +841,11 @@ public function test_maybe_get_explicit_format_for_subscription_total() { ->willReturn( true ); // Arrange: Set expectation and return for is_initialized and has_additional_currencies_enabled. - $this->mock_multi_currency - ->expects( $this->once() ) - ->method( 'is_initialized' ) - ->willReturn( true ); $this->mock_multi_currency ->expects( $this->once() ) ->method( 'has_additional_currencies_enabled' ) ->willReturn( true ); - // Arrange: Make sure to set our Multi-Currency instance as our mock instance. - WC_Payments_Explicit_Price_Formatter::set_multi_currency_instance( $this->mock_multi_currency ); - // Arrange/Assert: Apply the woocommerce_subscription_price_string_details filter and confirm the filter does not change the passed array. $this->assertSame( [ 1, 2, 3 ], apply_filters( 'woocommerce_subscription_price_string_details', [ 1, 2, 3 ], $mock_subscription ) ); diff --git a/tests/unit/multi-currency/notes/test-class-note-multi-currency-available-test.php b/tests/unit/multi-currency/notes/test-class-note-multi-currency-available-test.php index 5a985dd0511..e434ae9f661 100644 --- a/tests/unit/multi-currency/notes/test-class-note-multi-currency-available-test.php +++ b/tests/unit/multi-currency/notes/test-class-note-multi-currency-available-test.php @@ -6,6 +6,7 @@ */ use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; /** * Class Note_Multi_Currency_Available_Test tests. @@ -55,8 +56,8 @@ public function test_possibly_add_note_without_account() { } public function test_possibly_add_note_with_account_not_connected() { - $account_mock = $this->createMock( WC_Payments_Account::class ); - $account_mock->method( 'is_stripe_connected' )->willReturn( false ); + $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); + $account_mock->method( 'is_provider_connected' )->willReturn( false ); NoteMultiCurrencyAvailable::set_account( $account_mock ); NoteMultiCurrencyAvailable::possibly_add_note(); @@ -65,8 +66,8 @@ public function test_possibly_add_note_with_account_not_connected() { } public function test_possibly_add_note_with_connected_account() { - $account_mock = $this->createMock( WC_Payments_Account::class ); - $account_mock->method( 'is_stripe_connected' )->willReturn( true ); + $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); + $account_mock->method( 'is_provider_connected' )->willReturn( true ); NoteMultiCurrencyAvailable::set_account( $account_mock ); NoteMultiCurrencyAvailable::possibly_add_note(); diff --git a/tests/unit/multi-currency/test-class-analytics.php b/tests/unit/multi-currency/test-class-analytics.php index 00b46e2d7ef..02c719ca64b 100644 --- a/tests/unit/multi-currency/test-class-analytics.php +++ b/tests/unit/multi-currency/test-class-analytics.php @@ -1,6 +1,6 @@ create_can_manage_woocommerce_cap_override( true ); add_filter( 'user_has_cap', $cb ); - $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); + $mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); + $mock_account = $this->createMock( MultiCurrencyAccountInterface::class ); + $mock_localization = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $gateway_context = [ + 'is_dev_mode' => true, + ]; + + $this->mock_multi_currency = $this->getMockBuilder( MultiCurrency::class ) + ->setConstructorArgs( [ $gateway_context, $mock_api_client, $mock_account, $mock_localization, $mock_cache ] ) + ->getMock(); $this->mock_multi_currency->expects( $this->any() ) ->method( 'get_all_customer_currencies' ) @@ -84,7 +98,10 @@ public function set_up() { $this->analytics = new Analytics( $this->mock_multi_currency ); - $this->localization_service = new WC_Payments_Localization_Service(); + $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service->expects( $this->any() ) + ->method( 'get_currency_format' ) + ->willReturn( [ 'num_decimals' => 2 ] ); remove_filter( 'user_has_cap', $cb ); } @@ -164,7 +181,7 @@ public function test_update_order_stats_data_with_multi_currency_order_without_m public function test_update_order_stats_data_with_multi_currency_order() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 14.00 ); $order = wc_create_order(); @@ -179,7 +196,7 @@ public function test_update_order_stats_data_with_multi_currency_order() { public function test_update_order_stats_data_with_large_order() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 130500.75, 20000, 10000, 100500.75 ); $order = wc_create_order(); @@ -194,7 +211,7 @@ public function test_update_order_stats_data_with_large_order() { public function test_update_order_stats_data_with_stripe_exchange_rate() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 15.00 ); $order = wc_create_order(); @@ -587,14 +604,19 @@ private function create_can_manage_woocommerce_cap_override( bool $can_manage_wo } private function get_mock_available_currencies() { - $this->localization_service = new WC_Payments_Localization_Service(); + $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); if ( empty( $this->mock_available_currencies ) ) { + $this->mock_localization_service + ->expects( $this->any() ) + ->method( 'get_currency_format' ) + ->willReturn( [ 'num_decimals' => 2 ] ); + $this->mock_available_currencies = [ - 'GBP' => new Currency( $this->localization_service, 'GBP', 1.2 ), - 'USD' => new Currency( $this->localization_service, 'USD', 1 ), - 'EUR' => new Currency( $this->localization_service, 'EUR', 0.9 ), - 'ISK' => new Currency( $this->localization_service, 'ISK', 30.52 ), - 'NZD' => new Currency( $this->localization_service, 'NZD', 1.4 ), + 'GBP' => new Currency( $this->mock_localization_service, 'GBP', 1.2 ), + 'USD' => new Currency( $this->mock_localization_service, 'USD', 1 ), + 'EUR' => new Currency( $this->mock_localization_service, 'EUR', 0.9 ), + 'ISK' => new Currency( $this->mock_localization_service, 'ISK', 30.52 ), + 'NZD' => new Currency( $this->mock_localization_service, 'NZD', 1.4 ), ]; } diff --git a/tests/unit/multi-currency/test-class-backend-currencies.php b/tests/unit/multi-currency/test-class-backend-currencies.php index 530efc71b7d..8b333c6f189 100644 --- a/tests/unit/multi-currency/test-class-backend-currencies.php +++ b/tests/unit/multi-currency/test-class-backend-currencies.php @@ -6,6 +6,7 @@ */ use WCPay\MultiCurrency\BackendCurrencies; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; /** @@ -13,9 +14,9 @@ */ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { /** - * Mock WC_Payments_Localization_Service. + * Mock MultiCurrencyLocalizationInterface. * - * @var WC_Payments_Localization_Service|PHPUnit_Framework_MockObject_MockObject + * @var MultiCurrencyLocalizationInterface|PHPUnit_Framework_MockObject_MockObject */ private $mock_localization_service; @@ -36,7 +37,7 @@ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); + $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); // Mock admin part. diff --git a/tests/unit/multi-currency/test-class-compatibility.php b/tests/unit/multi-currency/test-class-compatibility.php index cefb478ca50..ae4cc75dc31 100644 --- a/tests/unit/multi-currency/test-class-compatibility.php +++ b/tests/unit/multi-currency/test-class-compatibility.php @@ -7,6 +7,7 @@ use WCPay\MultiCurrency\Compatibility; use WCPay\MultiCurrency\Currency; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Utils; @@ -36,11 +37,11 @@ class WCPay_Multi_Currency_Compatibility_Tests extends WCPAY_UnitTestCase { private $mock_utils; /** - * WC_Payments_Localization_Service. + * MultiCurrencyLocalizationInterface. * - * @var WC_Payments_Localization_Service + * @var MultiCurrencyLocalizationInterface */ - private $localization_service; + private $mock_localization_service; /** * Pre-test setup @@ -48,10 +49,15 @@ class WCPay_Multi_Currency_Compatibility_Tests extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); - $this->mock_utils = $this->createMock( Utils::class ); - $this->compatibility = new Compatibility( $this->mock_multi_currency, $this->mock_utils ); - $this->localization_service = new WC_Payments_Localization_Service(); + $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); + $this->mock_utils = $this->createMock( Utils::class ); + $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service + ->method( 'get_currency_format' ) + ->with( 'USD' ) + ->willReturn( [ 'num_decimals' => 2 ] ); + + $this->compatibility = new Compatibility( $this->mock_multi_currency, $this->mock_utils ); } public function test_init_compatibility_classes_does_not_add_classes_if_one_enabled_currencies() { @@ -108,7 +114,7 @@ public function test_filter_woocommerce_order_query_with_order_in_default_curren $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -132,7 +138,7 @@ public function test_filter_woocommerce_order_query_with_order_with_no_exchange_ $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -153,7 +159,7 @@ public function test_filter_woocommerce_order_query_with_no_meta() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -177,7 +183,7 @@ public function test_filter_woocommerce_order_query() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) diff --git a/tests/unit/multi-currency/test-class-country-flags.php b/tests/unit/multi-currency/test-class-country-flags.php index 0ebe63fb8ef..cd3adff8feb 100644 --- a/tests/unit/multi-currency/test-class-country-flags.php +++ b/tests/unit/multi-currency/test-class-country-flags.php @@ -5,7 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\CountryFlags; /** @@ -13,7 +12,7 @@ */ class Country_Flags_Test extends WCPAY_UnitTestCase { public function test_get_by_country_returns_emoji_flag() { - $this->assertEquals( CountryFlags::get_by_country( Country_Code::UNITED_STATES ), '🇺🇸' ); + $this->assertEquals( CountryFlags::get_by_country( 'US' ), '🇺🇸' ); } public function test_get_by_country_returns_empty_string() { diff --git a/tests/unit/multi-currency/test-class-currency-switcher-block.php b/tests/unit/multi-currency/test-class-currency-switcher-block.php index f716f6dbe75..2ac0a69fa11 100644 --- a/tests/unit/multi-currency/test-class-currency-switcher-block.php +++ b/tests/unit/multi-currency/test-class-currency-switcher-block.php @@ -10,6 +10,7 @@ use WCPay\MultiCurrency\Currency; use WCPay\MultiCurrency\CurrencySwitcherBlock; use WCPay\MultiCurrency\MultiCurrency; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; /** * CurrencySwitcherBlock unit tests. @@ -37,23 +38,25 @@ class WCPay_Multi_Currency_Currency_Switcher_Block_Tests extends WCPAY_UnitTestC protected $mock_currencies; /** - * WC_Payments_Localization_Service. - * - * @var WC_Payments_Localization_Service + * @var MockObject\MultiCurrencyLocalizationInterface */ - private $localization_service; + private $mock_localization_service; public function set_up() { parent::set_up(); - $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); - $this->mock_compatibility = $this->createMock( Compatibility::class ); - $this->localization_service = new WC_Payments_Localization_Service(); - $this->mock_currencies = [ - new Currency( $this->localization_service, 'USD', 1 ), - new Currency( $this->localization_service, 'CAD', 1.206823 ), - new Currency( $this->localization_service, 'GBP', 0.708099 ), - new Currency( $this->localization_service, 'EUR', 0.826381 ), + $this->mock_multi_currency = $this->createMock( MultiCurrency::class ); + $this->mock_compatibility = $this->createMock( Compatibility::class ); + $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service + ->method( 'get_currency_format' ) + ->willReturn( [ 'num_decimals' => 2 ] ); + + $this->mock_currencies = [ + new Currency( $this->mock_localization_service, 'USD', 1 ), + new Currency( $this->mock_localization_service, 'CAD', 1.206823 ), + new Currency( $this->mock_localization_service, 'GBP', 0.708099 ), + new Currency( $this->mock_localization_service, 'EUR', 0.826381 ), ]; $this->currency_switcher_block = new CurrencySwitcherBlock( @@ -219,8 +222,8 @@ public function test_render_currency_option_will_escape_output() { ->method( 'get_enabled_currencies' ) ->willReturn( [ - new Currency( $this->localization_service, 'USD' ), - new Currency( $this->localization_service, $currency_code, 1 ), + new Currency( $this->mock_localization_service, 'USD' ), + new Currency( $this->mock_localization_service, $currency_code, 1 ), ] ); @@ -275,7 +278,7 @@ public function test_widget_does_not_render_on_single_currency() { $this->mock_multi_currency ->expects( $this->once() ) ->method( 'get_enabled_currencies' ) - ->willReturn( [ new Currency( $this->localization_service, 'USD' ) ] ); + ->willReturn( [ new Currency( $this->mock_localization_service, 'USD' ) ] ); // Act/Assert: Confirm that when calling the renger method nothing is returned. $this->assertSame( '', $this->currency_switcher_block->render_block_widget( [], '' ) ); diff --git a/tests/unit/multi-currency/test-class-frontend-prices.php b/tests/unit/multi-currency/test-class-frontend-prices.php index c38d343dcc9..13d6b4bb34d 100644 --- a/tests/unit/multi-currency/test-class-frontend-prices.php +++ b/tests/unit/multi-currency/test-class-frontend-prices.php @@ -5,8 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\Constants\Country_Code; - /** * WCPay\MultiCurrency\FrontendPrices unit tests. */ @@ -216,7 +214,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); + WC()->customer->set_location( 'US', 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; @@ -260,7 +258,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); + WC()->customer->set_location( 'US', 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; diff --git a/tests/unit/multi-currency/test-class-geolocation.php b/tests/unit/multi-currency/test-class-geolocation.php index b66543dbf67..92b0b50b166 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -5,8 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\Constants\Country_Code; - /** * WCPay\MultiCurrency\Geolocation unit tests. */ @@ -51,10 +49,10 @@ public function test_get_country_by_customer_location_returns_geolocation_countr add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); - $this->assertSame( Country_Code::CANADA, $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( 'CA', $this->geolocation->get_country_by_customer_location() ); } public function test_get_country_by_customer_location_returns_default_country_when_no_geolocation() { @@ -68,20 +66,20 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return Country_Code::BRAZIL; + return 'BR'; } ); - $this->assertSame( Country_Code::BRAZIL, $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( 'BR', $this->geolocation->get_country_by_customer_location() ); } public function test_get_currency_by_customer_location_returns_geolocation_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -89,7 +87,7 @@ function () { } public function test_get_currency_by_customer_location_returns_default_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::BRAZIL )->willReturn( [ 'currency_code' => 'BRL' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'BR' )->willReturn( [ 'currency_code' => 'BRL' ] ); add_filter( 'woocommerce_geolocate_ip', @@ -100,7 +98,7 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return Country_Code::BRAZIL; + return 'BR'; } ); diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index 023fd92868c..70487b835ee 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -5,11 +5,12 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\Utils; -use WCPay\Database_Cache; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyException; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyRateException; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; +use WCPay\MultiCurrency\Interfaces\MultiCurrencyCacheInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Settings; use WCPay\MultiCurrency\SettingsOnboardCta; @@ -65,14 +66,14 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { /** * Mock of the API client. * - * @var WC_Payments_API_Client + * @var MultiCurrencyApiClientInterface */ private $mock_api_client; /** - * Mock of the WC_Payments_Account. + * Mock of the MultiCurrencyAccountInterface. * - * @var WC_Payments_Account + * @var MultiCurrencyAccountInterface */ private $mock_account; @@ -84,11 +85,11 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { private $localization_service; /** - * Mock of Database_Cache. + * Mock of MultiCurrencyCacheInterface. * - * @var Database_Cache; + * @var MultiCurrencyCacheInterface; */ - private $mock_database_cache; + private $mock_cache; /** * Mock of Utils. @@ -456,7 +457,7 @@ public function test_update_selected_currency_by_geolocation_does_not_set_sessio add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -473,7 +474,7 @@ public function test_update_selected_currency_by_geolocation_updates_session_whe add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -488,7 +489,7 @@ public function test_update_selected_currency_by_geolocation_displays_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -509,7 +510,7 @@ public function test_update_selected_currency_by_geolocation_does_not_update_if_ add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -534,7 +535,7 @@ public function test_display_geolocation_currency_update_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::CANADA; + return 'CA'; } ); @@ -544,11 +545,11 @@ function () { } public function test_display_geolocation_currency_update_notice_does_not_display_if_using_default_currency() { - WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, Country_Code::UNITED_STATES ); + WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, 'US' ); add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::UNITED_STATES; + return 'US'; } ); @@ -562,7 +563,7 @@ public function test_display_geolocation_currency_update_notice_does_not_display add_filter( 'woocommerce_geolocate_ip', function () { - return Country_Code::UNITED_STATES; + return 'US'; } ); @@ -721,13 +722,13 @@ public function test_get_raw_conversion_throws_exception_on_invalid_from_rate() public function test_get_cached_currencies_with_no_server_connection() { // Need to create a new instance of MultiCurrency with a different $mock_api_client // Because the mock return value of 'is_server_connected' cannot be overridden. - $mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + $mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); $mock_api_client->method( 'is_server_connected' )->willReturn( false ); $this->init_multi_currency( $mock_api_client ); - $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, @@ -736,7 +737,7 @@ public function test_get_cached_currencies_with_no_server_connection() { } public function test_get_cached_currencies_with_account_rejected() { - $this->mock_database_cache + $this->mock_cache ->expects( $this->once() ) ->method( 'get' ) ->willReturn( null ); @@ -746,7 +747,7 @@ public function test_get_cached_currencies_with_account_rejected() { ->method( 'is_account_rejected' ) ->willReturn( true ); - $this->mock_database_cache + $this->mock_cache ->expects( $this->never() ) ->method( 'get_or_add' ); @@ -758,11 +759,11 @@ public function test_get_cached_currencies_with_account_rejected() { public function test_get_cached_currencies_fetches_from_server() { $get_or_add_call_count = 1; - $mock_database_cache = $this->createMock( Database_Cache::class ); - $mock_database_cache + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $mock_cache ->expects( $this->exactly( 2 ) ) ->method( 'get_or_add' ) - ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturnCallback( function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { if ( 1 === $get_or_add_call_count ) { @@ -776,7 +777,7 @@ function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { } ); - $this->init_multi_currency( null, true, null, $mock_database_cache ); + $this->init_multi_currency( null, true, null, $mock_cache ); $currency_from = strtolower( get_woocommerce_currency() ); $currencies_to = get_woocommerce_currencies(); @@ -886,7 +887,7 @@ public function test_enabled_currencies_option_as_string_does_not_fatal() { public function test_get_cached_currencies_with_no_stripe_connection() { $this->init_multi_currency( null, false ); - $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, $this->multi_currency->get_cached_currencies() @@ -1051,14 +1052,14 @@ public function test_get_all_customer_currencies() { $mock_orders[] = $this->add_mock_order_with_currency_meta( 'EUR' ); $mock_orders[] = $this->add_mock_order_with_currency_meta( 'USD' ); - $mock_database_cache = $this->createMock( Database_Cache::class ); - $mock_database_cache + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $mock_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_database_cache ); + $this->init_multi_currency( null, true, null, $mock_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1073,14 +1074,14 @@ public function test_get_all_customer_currencies_with_option_data() { $mock_option_data = [ 'GBP', 'EUR', 'USD' ]; update_option( MultiCurrency::CUSTOMER_CURRENCIES_KEY, $mock_option_data ); - $mock_database_cache = $this->createMock( Database_Cache::class ); - $mock_database_cache + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $mock_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_database_cache ); + $this->init_multi_currency( null, true, null, $mock_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1103,14 +1104,14 @@ public function test_get_all_customer_currencies_with_invalid_option_data( $opti $mock_orders[] = $this->add_mock_order_with_currency_meta( 'EUR' ); $mock_orders[] = $this->add_mock_order_with_currency_meta( 'USD' ); - $mock_database_cache = $this->createMock( Database_Cache::class ); - $mock_database_cache + $mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $mock_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_database_cache ); + $this->init_multi_currency( null, true, null, $mock_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1414,31 +1415,32 @@ private function remove_currency_settings_mock( $currency_code, $settings ) { } } - private function init_multi_currency( $mock_api_client = null, $wcpay_account_connected = true, $mock_account = null, $mock_database_cache = null ) { - $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + private function init_multi_currency( $mock_api_client = null, $wcpay_account_connected = true, $mock_account = null, $mock_cache = null ) { + $this->mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); - $this->mock_account = $mock_account ?? $this->createMock( WC_Payments_Account::class ); - $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account = $mock_account ?? $this->createMock( MultiCurrencyAccountInterface::class ); + $this->mock_account->method( 'is_provider_connected' )->willReturn( $wcpay_account_connected ); $this->mock_api_client->method( 'is_server_connected' )->willReturn( true ); - $this->mock_database_cache = $this->createMock( Database_Cache::class ); - $this->mock_database_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); + $this->mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); + $this->mock_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); $this->mock_utils = $this->createMock( Utils::class ); + $gateway_context = [ + 'is_dev_mode' => true, + ]; $this->multi_currency = new MultiCurrency( + $gateway_context, $mock_api_client ?? $this->mock_api_client, $this->mock_account, $this->localization_service, - $mock_database_cache ?? $this->mock_database_cache, + $mock_cache ?? $this->mock_cache, $this->mock_utils ); $this->multi_currency->init_widgets(); $this->multi_currency->init(); - - // Fix an issue in WPCOM tests. - WC_Payments_Explicit_Price_Formatter::set_multi_currency_instance( $this->multi_currency ); } private function add_mock_order_with_currency_meta( $currency ) { diff --git a/tests/unit/multi-currency/test-class-rest-controller.php b/tests/unit/multi-currency/test-class-rest-controller.php index 49ac60ad1be..dfe581cde25 100644 --- a/tests/unit/multi-currency/test-class-rest-controller.php +++ b/tests/unit/multi-currency/test-class-rest-controller.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; use WCPay\MultiCurrency\RestController; /** @@ -33,7 +34,7 @@ public function set_up() { // Set the user so that we can pass the authentication. wp_set_current_user( 1 ); - $mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )->disableOriginalConstructor()->getMock(); + $mock_api_client = $this->getMockBuilder( MultiCurrencyApiClientInterface::class )->disableOriginalConstructor()->getMock(); $this->controller = new RestController( $mock_api_client ); } @@ -82,9 +83,8 @@ public function test_update_enabled_currencies_throws_exception_on_unavailable_c $error_currencies = [ 'EUR', 'GBP', 'banana' ]; // Arrange: Set expected result. - $error_code = 'wcpay_multi_currency_invalid_currency'; $error_message = 'Invalid currency passed to set_enabled_currencies: ' . implode( ', ', $error_currencies ); - $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( 500, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/update-enabled-currencies' ); @@ -129,9 +129,8 @@ public function test_get_single_currency_settings() { public function test_get_single_currency_settings_throws_exception_on_unavailable_currency() { // Arrange: Set expected result. - $error_code = 'wcpay_multi_currency_invalid_currency'; $error_message = 'Invalid currency passed to get_single_currency_settings: AAA'; - $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( 500, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'GET', self::ROUTE . '/currencies/AAA' ); @@ -191,9 +190,8 @@ public function test_update_single_currency_settings() { public function test_update_single_currency_settings_throws_exception_on_unavailable_currency() { // Arrange: Set expected result. - $error_code = 'wcpay_multi_currency_invalid_currency'; $error_message = 'Invalid currency passed to update_single_currency_settings: AAA'; - $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( 500, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/currencies/AAA' ); @@ -220,9 +218,8 @@ public function test_update_single_currency_settings_throws_exception_on_unavail */ public function test_update_single_currency_settings_throws_exception_on_invalid_currency_rate( $manual_rate ) { // Arrange: Set expected result. - $error_code = 'wcpay_multi_currency_invalid_currency_rate'; $error_message = 'Invalid manual currency rate passed to update_single_currency_settings: ' . $manual_rate; - $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( 500, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/currencies/USD' ); diff --git a/tests/unit/multi-currency/test-class-settings.php b/tests/unit/multi-currency/test-class-settings.php index 1ccbd373a0c..272a691910b 100644 --- a/tests/unit/multi-currency/test-class-settings.php +++ b/tests/unit/multi-currency/test-class-settings.php @@ -5,8 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\MultiCurrency\Currency; - /** * WCPay\MultiCurrency\Settings unit tests. */ diff --git a/tests/unit/multi-currency/test-class-utils.php b/tests/unit/multi-currency/test-class-utils.php index 1f3c9cce762..553df2a0cc1 100644 --- a/tests/unit/multi-currency/test-class-utils.php +++ b/tests/unit/multi-currency/test-class-utils.php @@ -60,8 +60,7 @@ public function test_is_admin_api_request_returns_true() { public function test_is_admin_api_request_returns_false_with_store_api() { $_SERVER['HTTP_REFERER'] = 'http://example.org/wp-admin/'; - $_REQUEST['rest_route'] = '/wc/store/v1/checkout'; - $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ); + $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ) . 'wc/store/v1/checkout'; $this->assertFalse( $this->utils->is_admin_api_request() ); diff --git a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php b/tests/unit/test-class-wc-payments-currency-manager.php similarity index 84% rename from tests/unit/multi-currency/test-class-payment-methods-compatibility.php rename to tests/unit/test-class-wc-payments-currency-manager.php index bdbbb767cde..6f13664ec75 100644 --- a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php +++ b/tests/unit/test-class-wc-payments-currency-manager.php @@ -1,14 +1,14 @@ multi_currency_mock = $this - ->getMockBuilder( WCPay\MultiCurrency\MultiCurrency::class ) + $this->multi_currency_mock = $this->getMockBuilder( WCPay\MultiCurrency\MultiCurrency::class ) ->disableOriginalConstructor() ->setMethods( [ @@ -68,8 +67,16 @@ public function set_up() { ->getMock(); $this->gateway_mock->method( 'get_account_country' )->willReturn( 'US' ); - $this->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); - $this->payment_methods_compatibility->init_hooks(); + $this->currency_manager = $this->getMockBuilder( \WCPay\WC_Payments_Currency_Manager::class ) + ->setConstructorArgs( [ $this->gateway_mock ] ) + ->setMethods( [ 'get_multi_currency_instance' ] ) + ->getMock(); + + // Mocking get_multi_currency_instance to return the multi_currency_mock. + $this->currency_manager->method( 'get_multi_currency_instance' ) + ->willReturn( $this->multi_currency_mock ); + + $this->currency_manager->init_hooks(); $this->localization_service = new WC_Payments_Localization_Service(); } @@ -79,7 +86,7 @@ public function test_it_should_not_update_available_currencies_when_enabled_paym $this->gateway_mock->expects( $this->atLeastOnce() )->method( 'get_upe_enabled_payment_method_ids' )->willReturn( [ 'card' ] ); $this->gateway_mock->expects( $this->atLeastOnce() )->method( 'get_account_domestic_currency' )->willReturn( 'USD' ); - $this->payment_methods_compatibility->add_missing_currencies(); + $this->currency_manager->maybe_add_missing_currencies(); } public function test_it_should_not_update_available_currencies_when_not_needed() { @@ -107,7 +114,7 @@ public function test_it_should_not_update_available_currencies_when_not_needed() ); $this->multi_currency_mock->expects( $this->never() )->method( 'set_enabled_currencies' ); - $this->payment_methods_compatibility->add_missing_currencies(); + $this->currency_manager->maybe_add_missing_currencies(); } public function test_it_should_update_available_currencies_when_needed() { @@ -147,7 +154,7 @@ public function test_it_should_update_available_currencies_when_needed() { ) ); - $this->payment_methods_compatibility->add_missing_currencies(); + $this->currency_manager->maybe_add_missing_currencies(); } public function test_it_should_not_update_available_currencies_with_bnpl_methods() { @@ -173,7 +180,7 @@ public function test_it_should_not_update_available_currencies_with_bnpl_methods ); $this->multi_currency_mock->expects( $this->never() )->method( 'set_enabled_currencies' ); - $this->payment_methods_compatibility->add_missing_currencies(); + $this->currency_manager->maybe_add_missing_currencies(); } public function test_it_should_update_available_currencies_with_bnpl_methods() { @@ -207,6 +214,6 @@ public function test_it_should_update_available_currencies_with_bnpl_methods() { ) ); - $this->payment_methods_compatibility->add_missing_currencies(); + $this->currency_manager->maybe_add_missing_currencies(); } } 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 9c32f3afdb5..b01ce3092d2 100644 --- a/tests/unit/test-class-wc-payments-explicit-price-formatter.php +++ b/tests/unit/test-class-wc-payments-explicit-price-formatter.php @@ -230,7 +230,7 @@ private function init_multi_currency( $mock_api_client = null, $wcpay_account_co $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_account->method( 'is_provider_connected' )->willReturn( $wcpay_account_connected ); $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); @@ -248,7 +248,10 @@ private function init_multi_currency( $mock_api_client = null, $wcpay_account_co $this->mock_database_cache = $this->createMock( Database_Cache::class ); $this->mock_database_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); - $this->multi_currency = new MultiCurrency( $mock_api_client ?? $this->mock_api_client, $this->mock_account, $this->mock_localization_service, $this->mock_database_cache ); + $gateway_context = [ + 'is_dev_mode' => true, + ]; + $this->multi_currency = new MultiCurrency( $gateway_context, $mock_api_client ?? $this->mock_api_client, $this->mock_account, $this->mock_localization_service, $this->mock_database_cache ); $this->multi_currency->init(); WC_Payments_Explicit_Price_Formatter::set_multi_currency_instance( $this->multi_currency ); diff --git a/tsconfig.json b/tsconfig.json index 3b9efabf45e..f7f56cddd52 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,9 @@ "paths": { "assets/*": [ "../assets/*" ], "wcpay/*": [ "./*" ], + "multi-currency/*": [ "../multi-currency/client/*" ], "iti/utils": [ "../node_modules/intl-tel-input/build/js/utils" ], - "react": ["../node_modules/@types/react"] + "react": [ "../node_modules/@types/react" ] }, "types": [ "node", diff --git a/webpack/shared.js b/webpack/shared.js index 7ef039967cd..55c249694ea 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -26,11 +26,11 @@ module.exports = { 'subscription-edit-page': './client/subscription-edit-page.js', tos: './client/tos/index.js', 'payment-gateways': './client/payment-gateways/index.js', - 'multi-currency': './client/multi-currency/index.js', + 'multi-currency': './multi-currency/client/index.js', 'multi-currency-switcher-block': - './client/multi-currency/blocks/currency-switcher.js', + './multi-currency/client/blocks/currency-switcher.js', 'multi-currency-analytics': - './client/multi-currency-analytics/index.js', + './multi-currency/client/analytics/index.js', order: './client/order/index.js', 'subscriptions-empty-state': './client/subscriptions-empty-state/index.js', @@ -113,9 +113,18 @@ module.exports = { }, resolve: { extensions: [ '.ts', '.tsx', '.json', '.js', '.jsx' ], - modules: [ path.join( process.cwd(), 'client' ), 'node_modules' ], + modules: [ + path.join( process.cwd(), 'client' ), + path.join( process.cwd(), 'multi-currency', 'client' ), + 'node_modules', + ], alias: { assets: path.resolve( process.cwd(), 'assets' ), + 'multi-currency': path.resolve( + process.cwd(), + 'multi-currency', + 'client' + ), wcpay: path.resolve( process.cwd(), 'client' ), iti: path.resolve( process.cwd(), From 513f2dc3a92a590df412479f2e30842a72ed421a Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 16 Sep 2024 11:10:15 +0200 Subject: [PATCH 12/40] chore: WC "tested up to" version bump (#9443) --- changelog/chore-v8.3.0-wc-version-bump | 5 +++++ woocommerce-payments.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/chore-v8.3.0-wc-version-bump diff --git a/changelog/chore-v8.3.0-wc-version-bump b/changelog/chore-v8.3.0-wc-version-bump new file mode 100644 index 00000000000..51e236bf0e5 --- /dev/null +++ b/changelog/chore-v8.3.0-wc-version-bump @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: chore: update WC "tested up to" version + + diff --git a/woocommerce-payments.php b/woocommerce-payments.php index dde0e058b69..c079725904c 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -8,7 +8,7 @@ * Text Domain: woocommerce-payments * Domain Path: /languages * WC requires at least: 7.6 - * WC tested up to: 9.2.0 + * WC tested up to: 9.3.1 * Requires at least: 6.0 * Requires PHP: 7.3 * Version: 8.2.1 From 0b8812653a7ab412a48e3b929ee9737d7393075d Mon Sep 17 00:00:00 2001 From: Vlad Olaru Date: Mon, 16 Sep 2024 13:24:26 +0300 Subject: [PATCH 13/40] Improve our job scheduling service for more resilience (#9435) Co-authored-by: Taha Paksu <3295+tpaksu@users.noreply.github.com> --- ...action-scheduler-service-schedule-job-init | 4 + composer.json | 2 +- composer.lock | 232 +++++++++--------- ...s-wc-payments-action-scheduler-service.php | 63 ++++- ...c-payments-webhook-reliability-service.php | 10 +- .../unit/test-class-compatibility-service.php | 1 - 6 files changed, 177 insertions(+), 135 deletions(-) create mode 100644 changelog/fix-9434-action-scheduler-service-schedule-job-init diff --git a/changelog/fix-9434-action-scheduler-service-schedule-job-init b/changelog/fix-9434-action-scheduler-service-schedule-job-init new file mode 100644 index 00000000000..f5e6dd9a615 --- /dev/null +++ b/changelog/fix-9434-action-scheduler-service-schedule-job-init @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Prevent failures and notices related to trying to schedule AS jobs before init. diff --git a/composer.json b/composer.json index 74a8c2d1aee..497530e36e1 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "composer/installers": "1.10.0", "phpunit/phpunit": "9.5.14", "woocommerce/woocommerce-sniffs": "1.0.0", - "woocommerce/action-scheduler": "3.1.6", + "woocommerce/action-scheduler": "3.8.2", "kalessil/production-dependencies-guard": "dev-master", "vimeo/psalm": "4.13.1", "php-stubs/wordpress-stubs": "5.9.6", diff --git a/composer.lock b/composer.lock index 4572fed2932..2128df15f90 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ef9fe43c4fffc996cf70034d89fea352", + "content-hash": "595a21dda8d9c0943a32df05723b0e28", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -58,16 +58,16 @@ }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.4.4", + "version": "v0.4.5", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "18ea3a92f910f7afa9641dadb956b95b75ce6e0f" + "reference": "7d5b8485ebe5984774375468ae52efe5c2849369" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/18ea3a92f910f7afa9641dadb956b95b75ce6e0f", - "reference": "18ea3a92f910f7afa9641dadb956b95b75ce6e0f", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/7d5b8485ebe5984774375468ae52efe5c2849369", + "reference": "7d5b8485ebe5984774375468ae52efe5c2849369", "shasum": "" }, "require": { @@ -108,22 +108,22 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.4.4" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.4.5" }, - "time": "2024-08-29T08:39:06+00:00" + "time": "2024-09-05T12:38:36+00:00" }, { "name": "automattic/jetpack-assets", - "version": "v2.3.5", + "version": "v2.3.8", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-assets.git", - "reference": "e869f7a7780da9b0c1ff9612701fcffde83cda12" + "reference": "3ddaff78c82ed7663b61961356585061dbb3410a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/e869f7a7780da9b0c1ff9612701fcffde83cda12", - "reference": "e869f7a7780da9b0c1ff9612701fcffde83cda12", + "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/3ddaff78c82ed7663b61961356585061dbb3410a", + "reference": "3ddaff78c82ed7663b61961356585061dbb3410a", "shasum": "" }, "require": { @@ -165,9 +165,9 @@ ], "description": "Asset management utilities for Jetpack ecosystem packages", "support": { - "source": "https://github.com/Automattic/jetpack-assets/tree/v2.3.5" + "source": "https://github.com/Automattic/jetpack-assets/tree/v2.3.8" }, - "time": "2024-08-29T08:39:36+00:00" + "time": "2024-09-10T11:21:54+00:00" }, { "name": "automattic/jetpack-autoloader", @@ -648,16 +648,16 @@ }, { "name": "automattic/jetpack-status", - "version": "v3.3.4", + "version": "v3.3.5", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-status.git", - "reference": "cf023b164ded674d66998b5b5870a3b6cf26679a" + "reference": "69d5d8a8f31adf2b297a539bcddd9a9162d1320b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/cf023b164ded674d66998b5b5870a3b6cf26679a", - "reference": "cf023b164ded674d66998b5b5870a3b6cf26679a", + "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/69d5d8a8f31adf2b297a539bcddd9a9162d1320b", + "reference": "69d5d8a8f31adf2b297a539bcddd9a9162d1320b", "shasum": "" }, "require": { @@ -703,9 +703,9 @@ ], "description": "Used to retrieve information about the current status of Jetpack and the site overall.", "support": { - "source": "https://github.com/Automattic/jetpack-status/tree/v3.3.4" + "source": "https://github.com/Automattic/jetpack-status/tree/v3.3.5" }, - "time": "2024-08-23T14:28:43+00:00" + "time": "2024-09-10T17:55:40+00:00" }, { "name": "automattic/jetpack-sync", @@ -938,6 +938,13 @@ "reference": "81d809a476e87c260492d4cc0413818d85e123cc", "shasum": "" }, + "archive": { + "exclude": [ + "!/build", + "*.zip", + "node_modules" + ] + }, "require": { "composer/installers": "~1.2", "php": "^7.1" @@ -952,13 +959,6 @@ "extra": { "phpcodesniffer-search-depth": 2 }, - "archive": { - "exclude": [ - "!/build", - "*.zip", - "node_modules" - ] - }, "scripts": { "phpcs": [ "bin/phpcs.sh" @@ -2370,16 +2370,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -2415,9 +2415,9 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nikic/php-parser", @@ -3344,16 +3344,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.0", + "version": "1.30.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", "shasum": "" }, "require": { @@ -3385,9 +3385,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" }, - "time": "2024-08-29T09:54:52+00:00" + "time": "2024-09-07T20:13:05+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5335,16 +5335,16 @@ }, { "name": "symfony/console", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f" + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f", - "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f", + "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", "shasum": "" }, "require": { @@ -5414,7 +5414,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.42" + "source": "https://github.com/symfony/console/tree/v5.4.43" }, "funding": [ { @@ -5430,7 +5430,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:21:55+00:00" + "time": "2024-08-13T16:31:56+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5501,16 +5501,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0724c51fa067b198e36506d2864e09a52180998a" + "reference": "ae25a9145a900764158d439653d5630191155ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0724c51fa067b198e36506d2864e09a52180998a", - "reference": "0724c51fa067b198e36506d2864e09a52180998a", + "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", + "reference": "ae25a9145a900764158d439653d5630191155ca0", "shasum": "" }, "require": { @@ -5544,7 +5544,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.42" + "source": "https://github.com/symfony/finder/tree/v5.4.43" }, "funding": [ { @@ -5560,24 +5560,24 @@ "type": "tidelift" } ], - "time": "2024-07-22T08:53:29+00:00" + "time": "2024-08-13T14:03:51+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -5623,7 +5623,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -5639,24 +5639,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -5701,7 +5701,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -5717,24 +5717,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -5782,7 +5782,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -5798,24 +5798,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -5862,7 +5862,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -5878,24 +5878,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5938,7 +5938,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -5954,24 +5954,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -6018,7 +6018,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -6034,7 +6034,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", @@ -6183,16 +6183,16 @@ }, { "name": "symfony/string", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "909cec913edea162a3b2836788228ad45fcab337" + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/909cec913edea162a3b2836788228ad45fcab337", - "reference": "909cec913edea162a3b2836788228ad45fcab337", + "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", "shasum": "" }, "require": { @@ -6249,7 +6249,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.42" + "source": "https://github.com/symfony/string/tree/v5.4.43" }, "funding": [ { @@ -6265,20 +6265,20 @@ "type": "tidelift" } ], - "time": "2024-07-20T18:38:32+00:00" + "time": "2024-08-01T10:24:28+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.40", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83" + "reference": "62f96e1cfd4cf518882a36bfedcf1fe4093c1299" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/81cad0ceab3d61fe14fe941ff18a230ac9c80f83", - "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83", + "url": "https://api.github.com/repos/symfony/yaml/zipball/62f96e1cfd4cf518882a36bfedcf1fe4093c1299", + "reference": "62f96e1cfd4cf518882a36bfedcf1fe4093c1299", "shasum": "" }, "require": { @@ -6324,7 +6324,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.40" + "source": "https://github.com/symfony/yaml/tree/v5.4.43" }, "funding": [ { @@ -6340,7 +6340,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-08-11T17:40:32+00:00" }, { "name": "theseer/tokenizer", @@ -6664,22 +6664,26 @@ }, { "name": "woocommerce/action-scheduler", - "version": "3.1.6", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", - "reference": "275d0ba54b1c263dfc62688de2fa9a25a373edf8" + "reference": "2bc91d88fdbc2c07ab899cbb56b983e11e62cf69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/275d0ba54b1c263dfc62688de2fa9a25a373edf8", - "reference": "275d0ba54b1c263dfc62688de2fa9a25a373edf8", + "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/2bc91d88fdbc2c07ab899cbb56b983e11e62cf69", + "reference": "2bc91d88fdbc2c07ab899cbb56b983e11e62cf69", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "phpunit/phpunit": "^5.6", - "woocommerce/woocommerce-sniffs": "0.0.8", - "wp-cli/wp-cli": "~1.5.1" + "phpunit/phpunit": "^7.5", + "woocommerce/woocommerce-sniffs": "0.1.0", + "wp-cli/wp-cli": "~2.5.0", + "yoast/phpunit-polyfills": "^2.0" }, "type": "wordpress-plugin", "extra": { @@ -6697,9 +6701,9 @@ "homepage": "https://actionscheduler.org/", "support": { "issues": "https://github.com/woocommerce/action-scheduler/issues", - "source": "https://github.com/woocommerce/action-scheduler/tree/master" + "source": "https://github.com/woocommerce/action-scheduler/tree/3.8.2" }, - "time": "2020-05-12T16:22:33+00:00" + "time": "2024-09-12T23:12:58+00:00" }, { "name": "woocommerce/qit-cli", @@ -6972,5 +6976,5 @@ "platform-overrides": { "php": "7.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.0.0" } diff --git a/includes/class-wc-payments-action-scheduler-service.php b/includes/class-wc-payments-action-scheduler-service.php index f9057870e44..df2f4f57bd9 100644 --- a/includes/class-wc-payments-action-scheduler-service.php +++ b/includes/class-wc-payments-action-scheduler-service.php @@ -147,23 +147,39 @@ private function track_order( $order_id, $is_update = false ) { } /** - * Schedule an action scheduler job. Also unschedules (replaces) any previous instances of the same job. + * Schedule an action scheduler job. + * + * Also, unschedules (replaces) any previous instances of the same job. * This prevents duplicate jobs, for example when multiple events fire as part of the order update process. - * The `as_unschedule_action` function will only replace a job which has the same $hook, $args AND $group. + * We will only replace a job which has the same $hook, $args AND $group. * - * @param int $timestamp - When the job will run. - * @param string $hook - The hook to trigger. - * @param array $args - An array containing the arguments to be passed to the hook. - * @param string $group - The AS group the action will be created under. + * @param int $timestamp When the job will run. + * @param string $hook The hook to trigger. + * @param array $args Optional. An array containing the arguments to be passed to the hook. + * Defaults to an empty array. + * @param string $group Optional. The AS group the action will be created under. + * Defaults to 'woocommerce_payments'. * * @return void */ public function schedule_job( int $timestamp, string $hook, array $args = [], string $group = self::GROUP_ID ) { - // Unschedule any previously scheduled instances of this particular job. - as_unschedule_action( $hook, $args, $group ); - - // Schedule the job. - as_schedule_single_action( $timestamp, $hook, $args, $group ); + // The `action_scheduler_init` hook was introduced in ActionScheduler 3.5.5 (WooCommerce 7.9.0). + if ( version_compare( WC()->version, '7.9.0', '>=' ) ) { + // If the ActionScheduler is already initialized, schedule the job. + if ( did_action( 'action_scheduler_init' ) ) { + $this->schedule_action_and_prevent_duplicates( $timestamp, $hook, $args, $group ); + } else { + // The ActionScheduler is not initialized yet; we need to schedule the job when it fires the init hook. + add_action( + 'action_scheduler_init', + function () use ( $timestamp, $hook, $args, $group ) { + $this->schedule_action_and_prevent_duplicates( $timestamp, $hook, $args, $group ); + } + ); + } + } else { + $this->schedule_action_and_prevent_duplicates( $timestamp, $hook, $args, $group ); + } } /** @@ -184,4 +200,29 @@ public function pending_action_exists( string $hook ): bool { return ( is_countable( $actions ) ? count( $actions ) : 0 ) > 0; } + + /** + * Schedule an action while unscheduling any scheduled actions that are exactly the same. + * + * We will look for scheduled actions with the same name, args and group when unscheduling. + * + * @param int $timestamp When the action will run. + * @param string $action The action name to schedule. + * @param array $args Optional. An array containing the arguments to be passed to the action. + * Defaults to an empty array. + * @param string $group Optional. The ActionScheduler group the action will be created under. + * Defaults to 'woocommerce_payments'. + * + * @return void + */ + private function schedule_action_and_prevent_duplicates( int $timestamp, string $action, array $args = [], string $group = self::GROUP_ID ) { + // Unschedule any previously scheduled actions with the same name, args, and group combination. + // It is more efficient/performant to check if the action is already scheduled before unscheduling it. + // @see https://github.com/Automattic/woocommerce-payments/issues/6662. + if ( as_has_scheduled_action( $action, $args, $group ) ) { + as_unschedule_action( $action, $args, $group ); + } + + as_schedule_single_action( $timestamp, $action, $args, $group ); + } } diff --git a/includes/class-wc-payments-webhook-reliability-service.php b/includes/class-wc-payments-webhook-reliability-service.php index e50a403750a..227bc486814 100644 --- a/includes/class-wc-payments-webhook-reliability-service.php +++ b/includes/class-wc-payments-webhook-reliability-service.php @@ -151,7 +151,7 @@ public function process_event( string $event_id ) { */ private function schedule_process_event( string $event_id ) { $this->action_scheduler_service->schedule_job( time(), self::WEBHOOK_PROCESS_EVENT_ACTION, [ 'event_id' => $event_id ] ); - Logger::info( 'Successfully schedule a job to processing event: ' . $event_id ); + Logger::info( 'Successfully scheduled a job to process event: ' . $event_id ); } /** @@ -162,14 +162,8 @@ private function schedule_process_event( string $event_id ) { * @return void */ private function schedule_fetch_events() { - // If the ActionScheduler is not initialized, we will get a fatal error when trying to schedule. - if ( ! ActionScheduler::is_initialized() ) { - Logger::info( 'Skipping scheduling a job to fetch failed events from the server: ActionScheduler is not initialized.' ); - return; - } - $this->action_scheduler_service->schedule_job( time(), self::WEBHOOK_FETCH_EVENTS_ACTION ); - Logger::info( 'Successfully schedule a job to fetch failed events from the server.' ); + Logger::info( 'Successfully scheduled a job to fetch failed events from the server.' ); } /** diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php index 18ac477dd8b..6217b1ce710 100644 --- a/tests/unit/test-class-compatibility-service.php +++ b/tests/unit/test-class-compatibility-service.php @@ -142,7 +142,6 @@ public function test_update_compatibility_data_adds_scheduled_job() { } public function test_update_compatibility_data_adds_a_single_scheduled_job() { - // Arrange: Clear all previously scheduled compatibility update jobs. as_unschedule_all_actions( Compatibility_Service::UPDATE_COMPATIBILITY_DATA ); From a9955a104367ac0974ae8cfccda8371a23cc0990 Mon Sep 17 00:00:00 2001 From: Daniel Mallory Date: Mon, 16 Sep 2024 16:07:53 +0100 Subject: [PATCH 14/40] Fixes to enable using the new KYC for progressive onboarding. (#9436) Co-authored-by: Vlad Olaru Co-authored-by: Vlad Olaru --- changelog/dev-9393-embedded-kyc-po | 4 +++ client/onboarding/kyc/index.tsx | 16 +++++++++-- client/onboarding/steps/embedded-kyc.tsx | 12 ++++++++- client/onboarding/tracking.ts | 9 +++++++ client/onboarding/utils.ts | 5 +--- includes/admin/class-wc-payments-admin.php | 27 ++++++++++++++++++- ...wc-rest-payments-onboarding-controller.php | 17 ++++-------- includes/class-wc-payments-account.php | 20 +++++++------- .../class-wc-payments-onboarding-service.php | 15 ++--------- .../class-wc-payments-api-client.php | 16 +++++------ 10 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 changelog/dev-9393-embedded-kyc-po diff --git a/changelog/dev-9393-embedded-kyc-po b/changelog/dev-9393-embedded-kyc-po new file mode 100644 index 00000000000..07d9ad4bf15 --- /dev/null +++ b/changelog/dev-9393-embedded-kyc-po @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Updates to the Embedded KYC to ensure compatibility with Progressive Onboarding diff --git a/client/onboarding/kyc/index.tsx b/client/onboarding/kyc/index.tsx index d2e9a4b5140..ba19b9145d0 100644 --- a/client/onboarding/kyc/index.tsx +++ b/client/onboarding/kyc/index.tsx @@ -13,11 +13,18 @@ import { OnboardingContextProvider } from 'onboarding/context'; import EmbeddedKyc from 'onboarding/steps/embedded-kyc'; import strings from 'onboarding/strings'; import { getConnectUrl } from 'utils'; +import { trackKycExit } from 'wcpay/onboarding/tracking'; const OnboardingKycPage: React.FC = () => { + const urlParams = new URLSearchParams( window.location.search ); + const collectPayoutRequirements = !! urlParams.get( + 'collect_payout_requirements' + ); + const handleExit = () => { - const urlParams = new URLSearchParams( window.location.search ); + trackKycExit(); + // Let the connect logic determine where the merchant should end up. window.location.href = getConnectUrl( { source: @@ -67,7 +74,12 @@ const OnboardingKycPage: React.FC = () => {

    - +
    diff --git a/client/onboarding/steps/embedded-kyc.tsx b/client/onboarding/steps/embedded-kyc.tsx index dd41c11b335..e01ed82ca90 100644 --- a/client/onboarding/steps/embedded-kyc.tsx +++ b/client/onboarding/steps/embedded-kyc.tsx @@ -28,10 +28,14 @@ import { getConnectUrl, getOverviewUrl } from 'wcpay/utils'; interface Props { continueKyc?: boolean; + collectPayoutRequirements?: boolean; } // TODO: extract this logic and move it to a generic component to be used for all embedded components, not just onboarding. -const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { +const EmbeddedKyc: React.FC< Props > = ( { + continueKyc = false, + collectPayoutRequirements = false, +} ) => { const { data } = useOnboardingContext(); const [ locale, setLocale ] = useState( '' ); const [ publishableKey, setPublishableKey ] = useState( '' ); @@ -188,6 +192,12 @@ const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { ) } onExit={ handleOnExit } + collectionOptions={ { + fields: collectPayoutRequirements + ? 'eventually_due' + : 'currently_due', + futureRequirements: 'omit', + } } /> ) } diff --git a/client/onboarding/tracking.ts b/client/onboarding/tracking.ts index e4d9ec557f7..1250e4c5a16 100644 --- a/client/onboarding/tracking.ts +++ b/client/onboarding/tracking.ts @@ -57,6 +57,15 @@ export const trackRedirected = ( isPoEligible: boolean ): void => { } ); }; +export const trackKycExit = (): void => { + const urlParams = new URLSearchParams( window.location.search ); + + recordEvent( 'wcpay_onboarding_kyc_exit', { + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown', + } ); +}; + export const trackAccountReset = (): void => recordEvent( 'wcpay_onboarding_flow_reset' ); diff --git a/client/onboarding/utils.ts b/client/onboarding/utils.ts index efab0971875..a95c3e298ef 100644 --- a/client/onboarding/utils.ts +++ b/client/onboarding/utils.ts @@ -60,18 +60,15 @@ export const getBusinessTypes = (): Country[] => { * * @param data The form data. * @param isPoEligible Whether the user is eligible for a PO account. - * @param collectPayoutRequirements Whether to collect payout requirements. */ export const createAccountSession = async ( data: OnboardingFields, - isPoEligible: boolean, - collectPayoutRequirements = false + isPoEligible: boolean ): Promise< AccountKycSession > => { return await apiFetch< AccountKycSession >( { path: addQueryArgs( `${ NAMESPACE }/onboarding/kyc/session`, { self_assessment: fromDotNotation( data ), progressive: isPoEligible, - collect_payout_requirements: collectPayoutRequirements, } ), method: 'GET', } ); diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index de577e4bcad..9bad5a0ac13 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -348,7 +348,7 @@ public function add_payments_menu() { remove_submenu_page( 'wc-admin&path=/payments/connect', 'wc-admin&path=/payments/onboarding' ); } - // Register /payments/onboarding/kyc only when we have a Stripe account, but the Stripe KYC is not finished (details not submitted). + // We handle how we register this page slightly differently depending on if details are submitted or not. if ( WC_Payments_Features::is_embedded_kyc_enabled() && $this->account->is_stripe_connected() && ! $this->account->is_details_submitted() ) { wc_admin_register_page( [ @@ -359,6 +359,7 @@ public function add_payments_menu() { 'capability' => 'manage_woocommerce', 'nav_args' => [ 'parent' => 'wc-payments', + 'order' => 50, ], ] ); @@ -366,6 +367,25 @@ public function add_payments_menu() { } if ( $should_render_full_menu ) { + // Only register if details are submitted and the account is PO. + if ( WC_Payments_Features::is_embedded_kyc_enabled() + && $this->account->is_stripe_connected() + && $this->account->is_details_submitted() + && $this->account->is_progressive_onboarding_in_progress() + ) { + $this->admin_child_pages['wc-payments-onboarding-kyc'] = [ + 'id' => 'wc-payments-onboarding-kyc', + 'title' => __( 'Continue onboarding', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/onboarding/kyc', + 'capability' => 'manage_woocommerce', + 'nav_args' => [ + 'parent' => 'wc-payments', + 'order' => 50, + ], + ]; + } + if ( $this->account->is_card_present_eligible() && $this->account->has_card_readers_available() ) { $this->admin_child_pages['wc-payments-card-readers'] = [ 'id' => 'wc-payments-card-readers', @@ -415,6 +435,11 @@ public function add_payments_menu() { wc_admin_register_page( $admin_child_page ); } + // Remove the "Continue onboarding" submenu item, if it exists. + if ( in_array( 'wc-payments-onboarding-kyc', array_keys( $this->admin_child_pages ), true ) ) { + remove_submenu_page( 'wc-admin&path=/payments/overview', 'wc-admin&path=/payments/onboarding/kyc' ); + } + wc_admin_connect_page( [ 'id' => 'woocommerce-settings-payments-woocommerce-payments', diff --git a/includes/admin/class-wc-rest-payments-onboarding-controller.php b/includes/admin/class-wc-rest-payments-onboarding-controller.php index da65bf5770c..3124e67d61a 100644 --- a/includes/admin/class-wc-rest-payments-onboarding-controller.php +++ b/includes/admin/class-wc-rest-payments-onboarding-controller.php @@ -56,17 +56,12 @@ public function register_routes() { 'callback' => [ $this, 'get_embedded_kyc_session' ], 'permission_callback' => [ $this, 'check_permission' ], 'args' => [ - 'progressive' => [ + 'progressive' => [ 'required' => false, 'description' => 'Whether the session is for progressive onboarding.', 'type' => 'string', ], - 'collect_payout_requirements' => [ - 'required' => false, - 'description' => 'Whether the session is for collecting payout requirements.', - 'type' => 'string', - ], - 'self_assessment' => [ + 'self_assessment' => [ 'required' => false, 'description' => 'The self-assessment data.', 'type' => 'object', @@ -204,14 +199,12 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_embedded_kyc_session( WP_REST_Request $request ) { - $self_assessment_data = ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : []; - $progressive = ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' ); - $collect_payout_requirements = ! empty( $request->get_param( 'collect_payout_requirements' ) ) && 'true' === $request->get_param( 'collect_payout_requirements' ); + $self_assessment_data = ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : []; + $progressive = ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' ); $account_session = $this->onboarding_service->create_embedded_kyc_session( $self_assessment_data, - $progressive, - $collect_payout_requirements + $progressive ); if ( $account_session ) { diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 33f829ee0de..bde6316122a 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -1537,10 +1537,11 @@ public function maybe_handle_onboarding() { $create_test_drive_account ? 'test_drive' : ( $should_onboard_in_test_mode ? 'test' : 'live' ), $wcpay_connect_param, [ - 'promo' => ! empty( $incentive_id ) ? $incentive_id : false, - 'progressive' => $progressive ? 'true' : false, - 'source' => $onboarding_source, - 'from' => WC_Payments_Onboarding_Service::FROM_STRIPE, + 'promo' => ! empty( $incentive_id ) ? $incentive_id : false, + 'progressive' => $progressive ? 'true' : false, + 'collect_payout_requirements' => $collect_payout_requirements ? 'true' : false, + 'source' => $onboarding_source, + 'from' => WC_Payments_Onboarding_Service::FROM_STRIPE, ] ); @@ -1873,11 +1874,12 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne WC_Payments_Onboarding_Service::set_onboarding_eligibility_modal_dismissed(); } - // If we are in the middle of an embedded onboarding, go to the KYC page. - // In this case, we don't need to generate a return URL from Stripe, and we - // can rely on the JS logic to generate the session. - // Currently under feature flag. - if ( WC_Payments_Features::is_embedded_kyc_enabled() && $this->onboarding_service->is_embedded_kyc_in_progress() ) { + /* + * If we are in the middle of an embedded onboarding, or this is an attempt to finalize PO, go to the KYC page. + * In this case, we don't need to generate a return URL from Stripe, and we can rely on the JS logic to generate the session. + * Currently under feature flag. + */ + if ( WC_Payments_Features::is_embedded_kyc_enabled() && ( $this->onboarding_service->is_embedded_kyc_in_progress() || $collect_payout_requirements ) ) { // We want to carry over the connect link from value because with embedded KYC // there is no interim step for the user. $additional_args['from'] = WC_Payments_Onboarding_Service::get_from(); diff --git a/includes/class-wc-payments-onboarding-service.php b/includes/class-wc-payments-onboarding-service.php index e4332f42ab9..8bf8f686255 100644 --- a/includes/class-wc-payments-onboarding-service.php +++ b/includes/class-wc-payments-onboarding-service.php @@ -157,13 +157,12 @@ function () use ( $locale ) { * * @param array $self_assessment_data Self assessment data. * @param boolean $progressive Whether the onboarding is progressive. - * @param boolean $collect_payout_requirements Whether to collect payout requirements. * * @return array Session data. * * @throws API_Exception */ - public function create_embedded_kyc_session( array $self_assessment_data, bool $progressive = false, bool $collect_payout_requirements = false ): array { + public function create_embedded_kyc_session( array $self_assessment_data, bool $progressive = false ): array { if ( ! $this->payments_api_client->is_server_connected() ) { return []; } @@ -172,15 +171,6 @@ public function create_embedded_kyc_session( array $self_assessment_data, bool $ // Make sure the onboarding test mode DB flag is set. self::set_test_mode( 'live' !== $setup_mode ); - if ( ! $collect_payout_requirements ) { - // Clear onboarding related account options if this is an initial onboarding attempt. - self::clear_account_options(); - } else { - // Since we assume user has already either gotten here from the eligibility modal, - // or has already dismissed it, we should set the modal as dismissed so it doesn't display again. - self::set_onboarding_eligibility_modal_dismissed(); - } - $site_data = [ 'site_username' => wp_get_current_user()->user_login, 'site_locale' => get_locale(), @@ -196,8 +186,7 @@ public function create_embedded_kyc_session( array $self_assessment_data, bool $ array_filter( $user_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. array_filter( $account_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. $actioned_notes, - $progressive, - $collect_payout_requirements + $progressive ); } catch ( API_Exception $e ) { // If we fail to create the session, return an empty array. diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index 94d5dd6087b..13b25e07dd6 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -980,23 +980,21 @@ public function get_onboarding_data( bool $live_account, string $return_url, arr * @param array $account_data Account data to be prefilled. * @param array $actioned_notes Actioned notes to be sent. * @param bool $progressive Whether progressive onboarding should be enabled for this onboarding. - * @param bool $collect_payout_requirements Whether we need to collect payout requirements. * * @return array * * @throws API_Exception */ - public function initialize_onboarding_embedded_kyc( bool $live_account, array $site_data = [], array $user_data = [], array $account_data = [], array $actioned_notes = [], bool $progressive = false, bool $collect_payout_requirements = false ): array { + public function initialize_onboarding_embedded_kyc( bool $live_account, array $site_data = [], array $user_data = [], array $account_data = [], array $actioned_notes = [], bool $progressive = false ): array { $request_args = apply_filters( 'wc_payments_get_onboarding_data_args', [ - 'site_data' => $site_data, - 'user_data' => $user_data, - 'account_data' => $account_data, - 'actioned_notes' => $actioned_notes, - 'create_live_account' => $live_account, - 'progressive' => $progressive, - 'collect_payout_requirements' => $collect_payout_requirements, + 'site_data' => $site_data, + 'user_data' => $user_data, + 'account_data' => $account_data, + 'actioned_notes' => $actioned_notes, + 'create_live_account' => $live_account, + 'progressive' => $progressive, ] ); From 682cdd723a7c1d9e04f1cbe9ad5a27027df67ddb Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 18 Sep 2024 10:14:54 -0300 Subject: [PATCH 15/40] Sending RGBA colors to Stripe Appearance API (#9453) --- changelog/fix-dark-mode-stripe-appearance | 4 ++++ client/checkout/upe-styles/index.js | 5 ++--- client/checkout/upe-styles/test/index.js | 18 ++++++++-------- client/checkout/upe-styles/test/utils.js | 26 ----------------------- client/checkout/upe-styles/utils.js | 20 ----------------- 5 files changed, 15 insertions(+), 58 deletions(-) create mode 100644 changelog/fix-dark-mode-stripe-appearance diff --git a/changelog/fix-dark-mode-stripe-appearance b/changelog/fix-dark-mode-stripe-appearance new file mode 100644 index 00000000000..4b0d6614cd3 --- /dev/null +++ b/changelog/fix-dark-mode-stripe-appearance @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed CC form input fields appearance when using RGBA diff --git a/client/checkout/upe-styles/index.js b/client/checkout/upe-styles/index.js index 1e924f41a42..73f84596924 100644 --- a/client/checkout/upe-styles/index.js +++ b/client/checkout/upe-styles/index.js @@ -5,7 +5,6 @@ import { upeRestrictedProperties } from './upe-styles'; import { generateHoverRules, generateOutlineStyle, - maybeConvertRGBAtoRGB, dashedToCamelCase, isColorLight, getBackgroundColor, @@ -380,8 +379,8 @@ export const getFieldStyles = ( for ( let i = 0; i < styles.length; i++ ) { const camelCase = dashedToCamelCase( styles[ i ] ); if ( validProperties.includes( camelCase ) ) { - filteredStyles[ camelCase ] = maybeConvertRGBAtoRGB( - styles.getPropertyValue( styles[ i ] ) + filteredStyles[ camelCase ] = styles.getPropertyValue( + styles[ i ] ); } } diff --git a/client/checkout/upe-styles/test/index.js b/client/checkout/upe-styles/test/index.js index 47467adeb21..fc63030ddb0 100644 --- a/client/checkout/upe-styles/test/index.js +++ b/client/checkout/upe-styles/test/index.js @@ -44,7 +44,7 @@ describe( 'Getting styles for automated theming', () => { '.Input' ); expect( fieldStyles ).toEqual( { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', @@ -134,7 +134,7 @@ describe( 'Getting styles for automated theming', () => { theme: 'stripe', rules: { '.Input': { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', @@ -143,7 +143,7 @@ describe( 'Getting styles for automated theming', () => { padding: '10px', }, '.Input--invalid': { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', @@ -159,24 +159,24 @@ describe( 'Getting styles for automated theming', () => { padding: '10px', }, '.Tab': { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', }, '.Tab:hover': { - backgroundColor: 'rgb(237, 237, 237)', - color: 'rgb(0, 0, 0)', + backgroundColor: 'rgba(18, 18, 18, 0)', + color: 'rgb(255, 255, 255)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', }, '.Tab--selected': { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', outline: '1px solid rgb(150, 88, 138)', }, '.TabIcon:hover': { - color: 'rgb(0, 0, 0)', + color: 'rgb(255, 255, 255)', }, '.TabIcon--selected': { color: 'rgb(109, 109, 109)', @@ -207,7 +207,7 @@ describe( 'Getting styles for automated theming', () => { padding: '10px', }, '.Button': { - backgroundColor: 'rgb(255, 255, 255)', + backgroundColor: 'rgba(0, 0, 0, 0)', color: 'rgb(109, 109, 109)', fontFamily: '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', diff --git a/client/checkout/upe-styles/test/utils.js b/client/checkout/upe-styles/test/utils.js index f886d46ff9c..c8b8398f3cf 100644 --- a/client/checkout/upe-styles/test/utils.js +++ b/client/checkout/upe-styles/test/utils.js @@ -100,32 +100,6 @@ describe( 'UPE Utilities to generate UPE styles', () => { } ); } ); - test( 'maybeConvertRGBAtoRGB returns valid colors', () => { - const hex = '#ffffff'; - const color = 'red'; - const rgb = 'rgb(1, 2, 3)'; - const rgbNoSpaces = 'rgb(1,2,3)'; - const rgba = 'rgba(1, 2, 3, 1)'; - const rgbaNoSpaces = 'rgba(1,2,3,1)'; - const shadow = 'rgb(1,2,3) 0px 1px 1px 0px'; - const shadowTransparent = 'rgba(1,2,3,1) 0px 1px 1px 0px'; - const pixel = '0px'; - - expect( upeUtils.maybeConvertRGBAtoRGB( hex ) ).toEqual( hex ); - expect( upeUtils.maybeConvertRGBAtoRGB( color ) ).toEqual( color ); - expect( upeUtils.maybeConvertRGBAtoRGB( rgb ) ).toEqual( rgb ); - expect( upeUtils.maybeConvertRGBAtoRGB( rgbNoSpaces ) ).toEqual( - rgbNoSpaces - ); - expect( upeUtils.maybeConvertRGBAtoRGB( rgba ) ).toEqual( rgb ); - expect( upeUtils.maybeConvertRGBAtoRGB( rgbaNoSpaces ) ).toEqual( rgb ); - expect( upeUtils.maybeConvertRGBAtoRGB( shadow ) ).toEqual( shadow ); - expect( upeUtils.maybeConvertRGBAtoRGB( shadowTransparent ) ).toEqual( - shadowTransparent - ); - expect( upeUtils.maybeConvertRGBAtoRGB( pixel ) ).toEqual( pixel ); - } ); - test( 'isColorLight returns valid brightness values', () => { const white = '#ffffff'; const black = '#000000'; diff --git a/client/checkout/upe-styles/utils.js b/client/checkout/upe-styles/utils.js index 19714f6e96a..d8d5cbfe81a 100644 --- a/client/checkout/upe-styles/utils.js +++ b/client/checkout/upe-styles/utils.js @@ -102,26 +102,6 @@ export const dashedToCamelCase = ( string ) => { } ); }; -/** - * Converts rgba to rgb format, since Stripe Appearances API does not accept rgba format for background. - * - * @param {string} color CSS color value. - * @return {string} Accepted CSS color value. - */ -export const maybeConvertRGBAtoRGB = ( color ) => { - const colorParts = color.match( - /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0?(\.\d+)?|1?(\.0+)?)\s*\)$/ - ); - if ( colorParts ) { - const alpha = colorParts[ 4 ] || 1; - const newColorParts = colorParts.slice( 1, 4 ).map( ( part ) => { - return Math.round( part * alpha + 255 * ( 1 - alpha ) ); - } ); - color = `rgb(${ newColorParts.join( ', ' ) })`; - } - return color; -}; - /** * Searches through array of CSS selectors and returns first visible background color. * From 02d9b865f9469afc9c83ed5d0d6717568616f0ed Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:57:15 +1000 Subject: [PATCH 16/40] Migrate e2e tests for dispute response flows to Playwright (#9423) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- ...spute-challenge-accept-flows-to-playwright | 5 + .../merchant-disputes-respond.spec.ts | 440 ++++++++++++++++++ tests/e2e-pw/utils/helpers.ts | 15 + tests/e2e-pw/utils/merchant-navigation.ts | 9 + ...hant-disputes-save-draft-challenge.spec.js | 137 ------ .../merchant-disputes-submit-losing.spec.js | 89 ---- .../merchant-disputes-submit-winning.spec.js | 142 ------ ...utes-view-details-via-order-notice.spec.js | 67 --- 8 files changed, 469 insertions(+), 435 deletions(-) create mode 100644 changelog/e2e-migrate-dispute-challenge-accept-flows-to-playwright create mode 100644 tests/e2e-pw/specs/merchant/merchant-disputes-respond.spec.ts delete mode 100644 tests/e2e/specs/wcpay/merchant/merchant-disputes-save-draft-challenge.spec.js delete mode 100644 tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-losing.spec.js delete mode 100644 tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-winning.spec.js delete mode 100644 tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js diff --git a/changelog/e2e-migrate-dispute-challenge-accept-flows-to-playwright b/changelog/e2e-migrate-dispute-challenge-accept-flows-to-playwright new file mode 100644 index 00000000000..2b4dce08f0e --- /dev/null +++ b/changelog/e2e-migrate-dispute-challenge-accept-flows-to-playwright @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Not user-facing: migrate dispute response flow e2e tests to playwright + + diff --git a/tests/e2e-pw/specs/merchant/merchant-disputes-respond.spec.ts b/tests/e2e-pw/specs/merchant/merchant-disputes-respond.spec.ts new file mode 100644 index 00000000000..e80bd0bb372 --- /dev/null +++ b/tests/e2e-pw/specs/merchant/merchant-disputes-respond.spec.ts @@ -0,0 +1,440 @@ +/** + * External dependencies + */ +import { test, expect, Page, Browser } from '@playwright/test'; + +/** + * Internal dependencies + */ +import * as shopper from '../../utils/shopper'; +import { config } from '../../config/default'; +import { getAnonymousShopper, getMerchant } from '../../utils/helpers'; +import { goToOrder, goToPaymentDetails } from '../../utils/merchant-navigation'; + +/** + * Navigates to the payment details page for a given disputed order. + */ +async function goToPaymentDetailsForOrder( + /** The merchant page object. */ + merchantPage: Page, + /** The ID of the disputed order. */ + orderId: string +): Promise< string > { + const paymentDetailsLink = await test.step( + 'Navigate to the payment details page', + async () => { + await goToOrder( merchantPage, orderId ); + + // Get the order payment intent ID. + const paymentIntentId = await merchantPage + .locator( '#order_data' ) + .getByRole( 'link', { + name: /pi_/, + } ) + .innerText(); + + await goToPaymentDetails( merchantPage, paymentIntentId ); + + // Store the current URL for later use. + const currentUrl = merchantPage.url(); + return currentUrl; + } + ); + + return paymentDetailsLink; +} + +async function createDisputedOrder( browser: Browser ) { + const { shopperPage } = await getAnonymousShopper( browser ); + + const orderId = await test.step( + 'Place an order as shopper, to be automatically disputed', + async () => { + await shopperPage.goto( '/cart/' ); + await shopper.addCartProduct( shopperPage ); + + await shopperPage.goto( '/checkout/' ); + await shopper.fillBillingAddress( + shopperPage, + config.addresses.customer.billing + ); + await shopper.fillCardDetails( + shopperPage, + config.cards[ 'disputed-fraudulent' ] + ); + await shopper.placeOrder( shopperPage ); + + // Get the order ID + const orderIdField = shopperPage.locator( + '.woocommerce-order-overview__order.order > strong' + ); + return orderIdField.innerText(); + } + ); + + return orderId; +} + +test.describe( 'Disputes > Respond to a dispute', () => { + // Allow all tests within this describe block to run in parallel. + test.describe.configure( { mode: 'parallel' } ); + + test( + 'Accept a dispute', + { + tag: '@critical', + }, + async ( { browser } ) => { + const { merchantPage } = await getMerchant( browser ); + + const orderId = await createDisputedOrder( browser ); + + await goToPaymentDetails( merchantPage, orderId ); + + await test.step( + 'Click the dispute accept button to open the accept dispute modal', + async () => { + // View the modal. + await merchantPage + .getByRole( 'button', { + name: 'Accept dispute', + } ) + .click(); + } + ); + + await test.step( + 'Click the accept dispute button to accept the dispute', + async () => { + await merchantPage + .getByTestId( 'accept-dispute-button' ) + .click(); + } + ); + + await test.step( + 'Wait for the accept request to resolve and observe the lost dispute status', + async () => { + expect( + merchantPage.getByText( 'Disputed: Lost' ) + ).toBeVisible(); + + // Check the dispute details footer + expect( + merchantPage.getByText( + 'This dispute was accepted and lost' + ) + ).toBeVisible(); + } + ); + + await test.step( + 'Confirm dispute action buttons are not present anymore since the dispute has been accepted', + async () => { + await expect( + merchantPage.getByTestId( 'challenge-dispute-button' ) + ).not.toBeVisible(); + await expect( + merchantPage.getByTestId( 'accept-dispute-button' ) + ).not.toBeVisible(); + } + ); + } + ); + + test( + 'Challenge a dispute with winning evidence', + { + tag: '@critical', + }, + async ( { browser } ) => { + const { merchantPage } = await getMerchant( browser ); + + const orderId = await createDisputedOrder( browser ); + + const paymentDetailsLink = await goToPaymentDetailsForOrder( + merchantPage, + orderId + ); + + await test.step( + 'Click the challenge dispute button to navigate to the challenge dispute page', + async () => { + await merchantPage + .getByRole( 'button', { + name: 'Challenge dispute', + } ) + .click(); + } + ); + + await test.step( 'Select the product type', async () => { + await merchantPage + .getByTestId( 'dispute-challenge-product-type-selector' ) + .selectOption( 'physical_product' ); + } ); + + await test.step( + 'Confirm the expected challenge form sections are visible', + async () => { + await expect( + merchantPage.getByText( 'General evidence', { + exact: true, + } ) + ).toBeVisible(); + + await expect( + merchantPage.getByText( 'Shipping information', { + exact: true, + } ) + ).toBeVisible(); + + await expect( + merchantPage + .getByText( 'Additional details', { + exact: true, + } ) + .first() + ).toBeVisible(); + } + ); + + await test.step( + 'Fill in the additional details field with the `winning_evidence` text', + async () => { + await merchantPage + .getByLabel( 'Additional details' ) + .fill( 'winning_evidence' ); + } + ); + + await test.step( + 'Submit the evidence and accept the dialog', + async () => { + // Prepare to accept the dialog before clicking the submit button + merchantPage.on( 'dialog', ( dialog ) => dialog.accept() ); + + // Click the submit button + await merchantPage + .getByRole( 'button', { + name: 'Submit evidence', + } ) + .click(); + + // Wait for the dispute list page to load. + await expect( + merchantPage + .getByRole( 'heading', { + name: 'Disputes', + } ) + .first() + ).toBeVisible(); + } + ); + + await test.step( + 'Navigate to the payment details screen and confirm the dispute status is Won', + async () => { + await merchantPage.goto( paymentDetailsLink ); + + await expect( + merchantPage.getByText( 'Disputed: Won', { + exact: true, + } ) + ).toBeVisible(); + + await expect( + merchantPage.getByText( + 'Good news! You won this dispute' + ) + ).toBeVisible(); + } + ); + + await test.step( + 'Confirm dispute action buttons are not present anymore since the dispute has been accepted', + async () => { + await expect( + merchantPage.getByTestId( 'challenge-dispute-button' ) + ).not.toBeVisible(); + await expect( + merchantPage.getByTestId( 'accept-dispute-button' ) + ).not.toBeVisible(); + } + ); + } + ); + + test( + 'Challenge a dispute with losing evidence', + { + tag: '@critical', + }, + async ( { browser } ) => { + const { merchantPage } = await getMerchant( browser ); + + const orderId = await createDisputedOrder( browser ); + + const paymentDetailsLink = await goToPaymentDetailsForOrder( + merchantPage, + orderId + ); + + await test.step( + 'Click the challenge dispute button to navigate to the challenge dispute page', + async () => { + await merchantPage + .getByRole( 'button', { + name: 'Challenge dispute', + } ) + .click(); + } + ); + + await test.step( 'Select the product type', async () => { + await merchantPage + .getByTestId( 'dispute-challenge-product-type-selector' ) + .selectOption( 'physical_product' ); + } ); + + await test.step( + 'Fill in the additional details field with the `losing_evidence` text', + async () => { + await merchantPage + .getByLabel( 'Additional details', { + exact: true, + } ) + .fill( 'losing_evidence' ); + } + ); + + await test.step( + 'Submit the evidence and accept the dialog', + async () => { + // Prepare to accept the dialog before clicking the submit button + merchantPage.on( 'dialog', ( dialog ) => dialog.accept() ); + + // Click the submit button + await merchantPage + .getByRole( 'button', { + name: 'Submit evidence', + } ) + .click(); + + // Wait for the dispute list page to load. + await expect( + merchantPage + .getByRole( 'heading', { + name: 'Disputes', + } ) + .first() + ).toBeVisible(); + } + ); + + await test.step( + 'Navigate to the payment details screen and confirm the dispute status is Lost', + async () => { + await merchantPage.goto( paymentDetailsLink ); + + await expect( + merchantPage.getByText( 'Disputed: Lost', { + exact: true, + } ) + ).toBeVisible(); + + await expect( + merchantPage.getByText( 'This dispute was lost' ) + ).toBeVisible(); + } + ); + + await test.step( + 'Confirm dispute action buttons are not present anymore since the dispute has been accepted', + async () => { + await expect( + merchantPage.getByTestId( 'challenge-dispute-button' ) + ).not.toBeVisible(); + await expect( + merchantPage.getByTestId( 'accept-dispute-button' ) + ).not.toBeVisible(); + } + ); + } + ); + + test( 'Save a dispute challenge without submitting evidence', async ( { + browser, + } ) => { + const { merchantPage } = await getMerchant( browser ); + + const orderId = await createDisputedOrder( browser ); + + const paymentDetailsLink = await goToPaymentDetailsForOrder( + merchantPage, + orderId + ); + + await test.step( + 'Click the challenge dispute button to navigate to the challenge dispute page', + async () => { + await merchantPage + .getByRole( 'button', { + name: 'Challenge dispute', + } ) + .click(); + } + ); + + await test.step( 'Select the product type', async () => { + await merchantPage + .getByTestId( 'dispute-challenge-product-type-selector' ) + .selectOption( 'offline_service' ); + + await expect( + merchantPage.getByTestId( + 'dispute-challenge-product-type-selector' + ) + ).toHaveValue( 'offline_service' ); + } ); + + await test.step( 'Save the dispute challenge for later', async () => { + await merchantPage + .getByRole( 'button', { + name: 'Save for later', + } ) + .click(); + + // Wait for the redirect to the dispute list page. + await expect( + merchantPage + .getByRole( 'heading', { + name: 'Disputes', + } ) + .first() + ).toBeVisible(); + } ); + + await test.step( + 'Navigate to the payment details screen and click the challenge dispute button', + async () => { + await merchantPage.goto( paymentDetailsLink ); + + await merchantPage + .getByTestId( 'challenge-dispute-button' ) + .click(); + } + ); + + await test.step( + 'Verify the previously selected challenge product type is saved', + async () => { + await expect( + merchantPage.getByTestId( + 'dispute-challenge-product-type-selector' + ) + ).toHaveValue( 'offline_service' ); + } + ); + } ); +} ); diff --git a/tests/e2e-pw/utils/helpers.ts b/tests/e2e-pw/utils/helpers.ts index 3112e819f68..8e0c6881300 100644 --- a/tests/e2e-pw/utils/helpers.ts +++ b/tests/e2e-pw/utils/helpers.ts @@ -78,3 +78,18 @@ export const getShopper = async ( const shopperPage = await shopperContext.newPage(); return { shopperPage, shopperContext }; }; + +/** + * Returns an anonymous shopper page and context. + * Emulates a new shopper who has not been authenticated and has no previous state, e.g. cart, order, etc. + */ +export const getAnonymousShopper = async ( + browser: Browser +): Promise< { + shopperPage: Page; + shopperContext: BrowserContext; +} > => { + const shopperContext = await browser.newContext(); + const shopperPage = await shopperContext.newPage(); + return { shopperPage, shopperContext }; +}; diff --git a/tests/e2e-pw/utils/merchant-navigation.ts b/tests/e2e-pw/utils/merchant-navigation.ts index b91c31ba097..dc87f6c1faf 100644 --- a/tests/e2e-pw/utils/merchant-navigation.ts +++ b/tests/e2e-pw/utils/merchant-navigation.ts @@ -8,6 +8,15 @@ export const goToOrder = async ( page: Page, orderId: string ) => { await page.goto( `/wp-admin/post.php?post=${ orderId }&action=edit` ); }; +export const goToPaymentDetails = async ( + page: Page, + paymentIntentId: string +) => { + await page.goto( + `/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Ftransactions%2Fdetails&id=${ paymentIntentId }` + ); +}; + export const goToWooPaymentsSettings = async ( page: Page ) => { await page.goto( '/wp-admin/admin.php?page=wc-settings&tab=checkout§ion=woocommerce_payments' diff --git a/tests/e2e/specs/wcpay/merchant/merchant-disputes-save-draft-challenge.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-disputes-save-draft-challenge.spec.js deleted file mode 100644 index 5ff98fe002f..00000000000 --- a/tests/e2e/specs/wcpay/merchant/merchant-disputes-save-draft-challenge.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -// disputes save disputes for editing -/** - * External dependencies - */ -import config from 'config'; -const { merchant, shopper, evalAndClick } = require( '@woocommerce/e2e-utils' ); - -/** - * Internal dependencies - */ -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; -import { uiLoaded } from '../../../utils'; - -describe( 'Disputes > Merchant can save and resume draft dispute challenge', () => { - let orderId; - let paymentDetailsLink; - - beforeAll( async () => { - await page.goto( config.get( 'url' ), { waitUntil: 'networkidle0' } ); - - // Place an order with a dispute credit card - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - const card = config.get( 'cards.disputed-unreceived' ); - await fillCardDetails( page, card ); - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - - // Get the order ID so we can open it in the merchant view - const orderIdField = await page.$( - '.woocommerce-order-overview__order.order > strong' - ); - orderId = await orderIdField.evaluate( ( el ) => el.innerText ); - - await merchant.login(); - await merchant.goToOrder( orderId ); - - // Get the payment details link from the order page. - paymentDetailsLink = await page.$eval( - 'p.order_number > a', - ( anchor ) => anchor.getAttribute( 'href' ) - ); - - // Open the payment details page and wait for it to load. - await Promise.all( [ - page.goto( paymentDetailsLink, { - waitUntil: 'networkidle0', - } ), - uiLoaded(), - ] ); - - // Verify we see the dispute details on the transaction details page. - await expect( page ).toMatchElement( '.dispute-notice', { - text: 'The cardholder claims the product was not received', - } ); - } ); - - afterAll( async () => { - await merchant.logout(); - } ); - - it( 'should be able to save a draft dispute challenge and resume', async () => { - // Click the challenge dispute button. - await evalAndClick( '[data-testid="challenge-dispute-button"]' ); - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - await uiLoaded(); - - await page.waitForSelector( - 'div.wcpay-dispute-evidence .components-flex.components-card__header', - { - timeout: 10000, - } - ); - - // Verify we're on the challenge dispute page - await expect( page ).toMatchElement( - 'div.wcpay-dispute-evidence .components-flex.components-card__header', - { - text: 'Challenge dispute', - } - ); - - await page.waitForSelector( - '[data-testid="dispute-challenge-product-type-selector"]', - { - timeout: 10000, - } - ); - - // Select the product type - await expect( page ).toSelect( - '[data-testid="dispute-challenge-product-type-selector"]', - 'offline_service' - ); - - await page.waitForSelector( - 'div.wcpay-dispute-evidence button.components-button.is-secondary', - { - timeout: 10000, - } - ); - - await expect( page ).toClick( - 'div.wcpay-dispute-evidence button.components-button.is-secondary', - { - text: 'Save for later', - } - ); - - // The merchant will be redirected to the dispute list page here, wait for it to load. - await uiLoaded(); - - // Open the payment details page again and wait for it to load. - await Promise.all( [ - page.goto( paymentDetailsLink, { - waitUntil: 'networkidle0', - } ), - uiLoaded(), - ] ); - - // Click the challenge dispute button. - await evalAndClick( '[data-testid="challenge-dispute-button"]' ); - await Promise.all( [ - page.waitForNavigation( { waitUntil: 'networkidle0' } ), - uiLoaded(), - ] ); - - // Verify the previously selected Product type was saved - await expect( page ).toMatchElement( - '[data-testid="dispute-challenge-product-type-selector"]', - { - text: 'Offline service', - } - ); - } ); -} ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-losing.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-losing.spec.js deleted file mode 100644 index a1e97d030c7..00000000000 --- a/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-losing.spec.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; -const { merchant, shopper, evalAndClick } = require( '@woocommerce/e2e-utils' ); - -/** - * Internal dependencies - */ -import { uiLoaded } from '../../../utils'; -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; - -let orderId; - -describe( 'Disputes > Submit losing dispute', () => { - beforeAll( async () => { - await page.goto( config.get( 'url' ), { waitUntil: 'networkidle0' } ); - - // Place an order to dispute later - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - const card = config.get( 'cards.disputed-unreceived' ); - await fillCardDetails( page, card ); - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - - // Get the order ID - const orderIdField = await page.$( - '.woocommerce-order-overview__order.order > strong' - ); - orderId = await orderIdField.evaluate( ( el ) => el.innerText ); - - await merchant.login(); - await merchant.goToOrder( orderId ); - - // Get the payment details link from the order page. - const paymentDetailsLink = await page.$eval( - 'p.order_number > a', - ( anchor ) => anchor.getAttribute( 'href' ) - ); - - // Open the payment details page and wait for it to load. - await Promise.all( [ - page.goto( paymentDetailsLink, { - waitUntil: 'networkidle0', - } ), - uiLoaded(), - ] ); - - // Verify we see the dispute details on the transaction details page. - await expect( page ).toMatchElement( '.dispute-notice', { - text: 'The cardholder claims the product was not received', - } ); - } ); - - afterAll( async () => { - await merchant.logout(); - } ); - - it( 'should process and confirm a losing dispute', async () => { - // Open the accept dispute modal. - await evalAndClick( '[data-testid="open-accept-dispute-modal-button"' ); - await uiLoaded(); - // Click the accept dispute button. - await evalAndClick( '[data-testid="accept-dispute-button"]' ); - // Wait for the accept POST request to resolve and the status chip to update with the new status. - await expect( page ).toMatchElement( '.chip', { - text: 'Disputed: Lost', - timeout: 10000, - } ); - - // Check the dispute details footer - await expect( page ).toMatchElement( - '.transaction-details-dispute-footer *', - { - text: 'This dispute was accepted and lost', - } - ); - - // Confirm buttons are not present anymore since a dispute has been accepted. - await expect( page ).not.toMatchElement( - '[data-testid="challenge-dispute-button"]' - ); - await expect( page ).not.toMatchElement( - '[data-testid="open-accept-dispute-modal-button"]' - ); - } ); -} ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-winning.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-winning.spec.js deleted file mode 100644 index 3ccd1105869..00000000000 --- a/tests/e2e/specs/wcpay/merchant/merchant-disputes-submit-winning.spec.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; -/** - * Internal dependencies - */ -import { merchantWCP, uiLoaded } from '../../../utils'; -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; - -const { - merchant, - shopper, - evalAndClick, - uiUnblocked, -} = require( '@woocommerce/e2e-utils' ); - -let orderId; - -describe( 'Disputes > Submit winning dispute', () => { - let paymentDetailsLink; - - beforeAll( async () => { - await page.goto( config.get( 'url' ), { waitUntil: 'networkidle0' } ); - - // Place an order to dispute later - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - const card = config.get( 'cards.disputed-fraudulent' ); - await fillCardDetails( page, card ); - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - - // Get the order ID - const orderIdField = await page.$( - '.woocommerce-order-overview__order.order > strong' - ); - orderId = await orderIdField.evaluate( ( el ) => el.innerText ); - - await merchant.login(); - await merchant.goToOrder( orderId ); - - // Get the payment details link from the order page. - paymentDetailsLink = await page.$eval( - 'p.order_number > a', - ( anchor ) => anchor.getAttribute( 'href' ) - ); - - // Open the payment details page and wait for it to load. - await Promise.all( [ - page.goto( paymentDetailsLink, { - waitUntil: 'networkidle0', - } ), - uiLoaded(), - ] ); - - // Verify we see the dispute details on the transaction details page. - await expect( page ).toMatchElement( '.dispute-notice', { - text: 'The cardholder claims this is an unauthorized transaction', - } ); - } ); - - afterAll( async () => { - page.removeAllListeners( 'dialog' ); - page.on( 'dialog', async function ( dialog ) { - try { - await dialog.accept(); - } catch ( err ) {} - } ); - await merchant.logout(); - } ); - - it( 'should process and confirm a winning dispute', async () => { - // Click the challenge dispute button. - await evalAndClick( '[data-testid="challenge-dispute-button"]' ); - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - await uiLoaded(); - - // Select product type - await expect( page ).toSelect( - '[data-testid="dispute-challenge-product-type-selector"]', - 'physical_product' - ); - - // Verify the content blocks are present - await expect( page ).toMatchElement( - 'div.wcpay-dispute-evidence .components-card__header', - { - text: 'General evidence', - } - ); - await expect( page ).toMatchElement( - 'div.wcpay-dispute-evidence .components-card__header', - { - text: 'Shipping information', - } - ); - await expect( page ).toMatchElement( - 'div.wcpay-dispute-evidence .components-card__header', - { - text: 'Additional details', - } - ); - - // Fill Additional Details field with required text in order to win dispute - await expect( - page - ).toFill( - 'div.wcpay-dispute-evidence #inspector-textarea-control-3', - 'winning_evidence', - { delay: 20 } - ); - - // Submit the evidence and accept the dialog - await Promise.all( [ - page.removeAllListeners( 'dialog' ), - evalAndClick( - 'div.wcpay-dispute-evidence .components-card__footer > div > button.components-button.is-primary' - ), - page.on( 'dialog', async ( dialog ) => { - await dialog.accept(); - } ), - uiUnblocked(), - page.waitForNavigation( { waitUntil: 'networkidle0' } ), - uiLoaded(), - ] ); - - // If webhooks are not received, the dispute status won't be updated in the dispute list page resulting in test failure. - // Workaround - Open payment details page again and check dispute's status. - await merchantWCP.openPaymentDetails( paymentDetailsLink ); - - // Confirm dispute status is Won. - await page.waitForSelector( '.transaction-details-dispute-footer' ); - await expect( page ).toMatchElement( - '.transaction-details-dispute-footer', - { - text: 'Good news! You won this dispute', - } - ); - } ); -} ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js deleted file mode 100644 index af0dbc047db..00000000000 --- a/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; - -/** - * Internal dependencies - */ -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; - -const { merchant, shopper } = require( '@woocommerce/e2e-utils' ); - -describe( 'Disputes > View dispute details via disputed order notice', () => { - beforeAll( async () => { - await page.goto( config.get( 'url' ), { waitUntil: 'networkidle0' } ); - - // Place an order to dispute later - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - const card = config.get( 'cards.disputed-fraudulent' ); - await fillCardDetails( page, card ); - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - - // Get the order ID - const orderIdField = await page.$( - '.woocommerce-order-overview__order.order > strong' - ); - const orderId = await orderIdField.evaluate( ( el ) => el.innerText ); - - await merchant.login(); - await merchant.goToOrder( orderId ); - } ); - - afterAll( async () => { - await merchant.logout(); - } ); - - it( 'should navigate to dispute details when disputed order notice button clicked', async () => { - // If WC < 7.9, return early since the order dispute notice is not present. - const orderPaymentDetailsContainer = await page.$( - '#wcpay-order-payment-details-container' - ); - if ( ! orderPaymentDetailsContainer ) { - // eslint-disable-next-line no-console - console.log( - 'Skipping test since the order dispute notice is not present in WC < 7.9' - ); - return; - } - - // Click the order dispute notice. - await expect( page ).toClick( '[type="button"]', { - text: 'Respond now', - } ); - - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - - // Verify we see the dispute details on the transaction details page. - await expect( page ).toMatchElement( '.dispute-notice', { - text: 'The cardholder claims this is an unauthorized transaction', - } ); - } ); -} ); From c83f5de040a7b846bb5f15ac87009b98bf9cae44 Mon Sep 17 00:00:00 2001 From: Alefe Souza Date: Wed, 18 Sep 2024 21:32:34 -0300 Subject: [PATCH 17/40] Fix WooPay pre-checking place order bug when buying a subscription (#9450) --- ...opay-pre-checking-save-my-info-place-order | 4 + .../hooks/use-selected-payment-method.js | 73 +++++++++---------- 2 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 changelog/fix-woopay-pre-checking-save-my-info-place-order diff --git a/changelog/fix-woopay-pre-checking-save-my-info-place-order b/changelog/fix-woopay-pre-checking-save-my-info-place-order new file mode 100644 index 00000000000..4e98d24ab30 --- /dev/null +++ b/changelog/fix-woopay-pre-checking-save-my-info-place-order @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix WooPay pre-checking place order bug when buying a subscription. diff --git a/client/components/woopay/hooks/use-selected-payment-method.js b/client/components/woopay/hooks/use-selected-payment-method.js index 608fe247ffb..f94289715f5 100644 --- a/client/components/woopay/hooks/use-selected-payment-method.js +++ b/client/components/woopay/hooks/use-selected-payment-method.js @@ -2,40 +2,22 @@ * External dependencies */ import { useEffect, useState } from 'react'; +import { useSelect } from '@wordpress/data'; +import { PAYMENT_STORE_KEY } from '@woocommerce/block-data'; // eslint-disable-line import/no-unresolved -const getWCPayRadioButtonStatus = ( isBlocksCheckout ) => - isBlocksCheckout - ? document.querySelector( - '#radio-control-wc-payment-method-options-woocommerce_payments' - )?.checked - : document.querySelector( '#payment_method_woocommerce_payments' ) - ?.checked; - -const getNewPaymentTokenRadioButtonStatus = ( isBlocksCheckout ) => - isBlocksCheckout - ? document.querySelector( - '#radio-control-wc-payment-method-options-woocommerce_payments' - )?.checked - : document.querySelector( '#wc-woocommerce_payments-payment-token-new' ) - ?.checked || - ! document.querySelector( - '[type=radio][name="wc-woocommerce_payments-payment-token"]' - ); +const getWCPayRadioButtonStatus = () => { + return document.querySelector( '#payment_method_woocommerce_payments' ) + ?.checked; +}; -const getPaymentMethods = ( isBlocksCheckout ) => { - if ( isBlocksCheckout ) { - // For blocks checkout there is no common selector to find all the payment methods including the - // saved tokens. Thus need to concate them here to make a whole list. - return [ - ...document.querySelectorAll( - '[type=radio][name="radio-control-wc-payment-method-options"]' - ), - ...document.querySelectorAll( - '[type=radio][name="radio-control-wc-payment-method-saved-tokens"]' - ), - ]; - } - // for classic checkout +const getNewPaymentTokenRadioButtonStatus = () => + document.querySelector( '#wc-woocommerce_payments-payment-token-new' ) + ?.checked || + ! document.querySelector( + '[type=radio][name="wc-woocommerce_payments-payment-token"]' + ); + +const getPaymentMethods = () => { return document.querySelectorAll( '[type=radio][name="payment_method"]' ); }; @@ -51,15 +33,28 @@ const getPaymentTokens = ( isBlocksCheckout ) => { // hook for checking if WCPay is selected. const useSelectedPaymentMethod = ( isBlocksCheckout ) => { + // For blocks checkout, we use the store to get the active payment method. + const { isWCPayChosenOnBlocksCheckout } = useSelect( ( select ) => { + const store = select( PAYMENT_STORE_KEY ); + return { + isWCPayChosenOnBlocksCheckout: + store.getActivePaymentMethod() === 'woocommerce_payments', + }; + } ); + const [ isWCPayChosen, setIsWCPayChosen ] = useState( - getWCPayRadioButtonStatus( isBlocksCheckout ) + ! isBlocksCheckout && getWCPayRadioButtonStatus() ); const [ isNewPaymentTokenChosen, setNewPaymentTokenChosen ] = useState( - getNewPaymentTokenRadioButtonStatus( isBlocksCheckout ) + ! isBlocksCheckout && getNewPaymentTokenRadioButtonStatus() ); useEffect( () => { + if ( isBlocksCheckout ) { + return; + } + // hides the `Save payment information to my account for future purchases` checkbox. const hideCheckbox = () => { const checkbox = document.querySelector( @@ -87,7 +82,7 @@ const useSelectedPaymentMethod = ( isBlocksCheckout ) => { ); }; - const paymentMethods = getPaymentMethods( isBlocksCheckout ); + const paymentMethods = getPaymentMethods(); paymentMethods.forEach( ( paymentMethod ) => { paymentMethod.addEventListener( 'change', updateIsWCPayChosen ); @@ -119,8 +114,12 @@ const useSelectedPaymentMethod = ( isBlocksCheckout ) => { }, [ isBlocksCheckout ] ); return { - isWCPayChosen, - isNewPaymentTokenChosen, + isWCPayChosen: isBlocksCheckout + ? isWCPayChosenOnBlocksCheckout + : isWCPayChosen, + isNewPaymentTokenChosen: isBlocksCheckout + ? isWCPayChosenOnBlocksCheckout + : isNewPaymentTokenChosen, }; }; From 49328e8a0dcb00366f03cf110e3947402e62230a Mon Sep 17 00:00:00 2001 From: Daniel Mallory Date: Thu, 19 Sep 2024 09:51:16 +0100 Subject: [PATCH 18/40] Removing feature flag for Embedded KYC (#9445) Co-authored-by: oaratovskyi --- changelog/dev-remove-feature-flag | 4 +++ client/globals.d.ts | 1 - client/index.js | 32 ++++++++----------- client/onboarding/index.tsx | 11 ++----- includes/admin/class-wc-payments-admin.php | 5 ++- includes/class-wc-payments-account.php | 3 +- includes/class-wc-payments-features.php | 11 ------- tests/unit/test-class-wc-payments-account.php | 3 -- .../unit/test-class-wc-payments-features.php | 18 ----------- 9 files changed, 23 insertions(+), 65 deletions(-) create mode 100644 changelog/dev-remove-feature-flag diff --git a/changelog/dev-remove-feature-flag b/changelog/dev-remove-feature-flag new file mode 100644 index 00000000000..43083b44120 --- /dev/null +++ b/changelog/dev-remove-feature-flag @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove feature flag to make embedded KYC enabled by default diff --git a/client/globals.d.ts b/client/globals.d.ts index 44d94baa01b..f00b521943a 100644 --- a/client/globals.d.ts +++ b/client/globals.d.ts @@ -16,7 +16,6 @@ declare global { paymentTimeline: boolean; isDisputeIssuerEvidenceEnabled: boolean; isPaymentOverviewWidgetEnabled?: boolean; - isEmbeddedKycEnabled?: boolean; }; fraudServices: unknown[]; testMode: boolean; diff --git a/client/index.js b/client/index.js index eb444c1f9de..11083acffac 100644 --- a/client/index.js +++ b/client/index.js @@ -70,25 +70,19 @@ addFilter( capability: 'manage_woocommerce', } ); - // Currently under feature flag. - if ( - wcpaySettings && - wcpaySettings.featureFlags.isEmbeddedKycEnabled - ) { - pages.push( { - container: OnboardingKycPage, - path: '/payments/onboarding/kyc', - wpOpenMenu: menuID, - breadcrumbs: [ - rootLink, - __( 'Continue onboarding', 'woocommerce-payments' ), - ], - navArgs: { - id: 'wc-payments-continue-onboarding', - }, - capability: 'manage_woocommerce', - } ); - } + pages.push( { + container: OnboardingKycPage, + path: '/payments/onboarding/kyc', + wpOpenMenu: menuID, + breadcrumbs: [ + rootLink, + __( 'Continue onboarding', 'woocommerce-payments' ), + ], + navArgs: { + id: 'wc-payments-continue-onboarding', + }, + capability: 'manage_woocommerce', + } ); pages.push( { container: OverviewPage, diff --git a/client/onboarding/index.tsx b/client/onboarding/index.tsx index 1dbb237f7cd..b301a0c980e 100644 --- a/client/onboarding/index.tsx +++ b/client/onboarding/index.tsx @@ -18,7 +18,6 @@ import StoreDetails from './steps/store-details'; import { trackStarted } from './tracking'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; -import LoadingStep from 'wcpay/onboarding/steps/loading'; const OnboardingStepper = () => { const handleExit = () => { @@ -48,13 +47,9 @@ const OnboardingStepper = () => { - { wcpaySettings?.featureFlags?.isEmbeddedKycEnabled ? ( - - - - ) : ( - - ) } + + + ); }; diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 9bad5a0ac13..9c53cb48597 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -349,7 +349,7 @@ public function add_payments_menu() { } // We handle how we register this page slightly differently depending on if details are submitted or not. - if ( WC_Payments_Features::is_embedded_kyc_enabled() && $this->account->is_stripe_connected() && ! $this->account->is_details_submitted() ) { + if ( $this->account->is_stripe_connected() && ! $this->account->is_details_submitted() ) { wc_admin_register_page( [ 'id' => 'wc-payments-onboarding-kyc', @@ -368,8 +368,7 @@ public function add_payments_menu() { if ( $should_render_full_menu ) { // Only register if details are submitted and the account is PO. - if ( WC_Payments_Features::is_embedded_kyc_enabled() - && $this->account->is_stripe_connected() + if ( $this->account->is_stripe_connected() && $this->account->is_details_submitted() && $this->account->is_progressive_onboarding_in_progress() ) { diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index bde6316122a..e6b3c94e0fa 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -1877,9 +1877,8 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne /* * If we are in the middle of an embedded onboarding, or this is an attempt to finalize PO, go to the KYC page. * In this case, we don't need to generate a return URL from Stripe, and we can rely on the JS logic to generate the session. - * Currently under feature flag. */ - if ( WC_Payments_Features::is_embedded_kyc_enabled() && ( $this->onboarding_service->is_embedded_kyc_in_progress() || $collect_payout_requirements ) ) { + if ( $this->onboarding_service->is_embedded_kyc_in_progress() || $collect_payout_requirements ) { // We want to carry over the connect link from value because with embedded KYC // there is no interim step for the user. $additional_args['from'] = WC_Payments_Onboarding_Service::get_from(); diff --git a/includes/class-wc-payments-features.php b/includes/class-wc-payments-features.php index 8df7d249206..161cd8e3935 100644 --- a/includes/class-wc-payments-features.php +++ b/includes/class-wc-payments-features.php @@ -32,7 +32,6 @@ class WC_Payments_Features { const TOKENIZED_CART_PRB_FLAG_NAME = '_wcpay_feature_tokenized_cart_prb'; const PAYMENT_OVERVIEW_WIDGET_FLAG_NAME = '_wcpay_feature_payment_overview_widget'; const WOOPAY_GLOBAL_THEME_SUPPORT_FLAG_NAME = '_wcpay_feature_woopay_global_theme_support'; - const EMBEDDED_KYC_FLAG_NAME = '_wcpay_feature_embedded_kyc'; /** * Indicates whether card payments are enabled for this (Stripe) account. @@ -76,15 +75,6 @@ public static function is_customer_multi_currency_enabled() { return '1' === get_option( '_wcpay_feature_customer_multi_currency', '1' ); } - /** - * Checks whether Embedded KYC is enabled. - * - * @return bool - */ - public static function is_embedded_kyc_enabled(): bool { - return '1' === get_option( self::EMBEDDED_KYC_FLAG_NAME, '0' ); - } - /** * Checks whether WCPay Subscriptions is enabled. * @@ -397,7 +387,6 @@ public static function to_array() { 'isDisputeIssuerEvidenceEnabled' => self::is_dispute_issuer_evidence_enabled(), 'isPaymentOverviewWidgetEnabled' => self::is_payment_overview_widget_ui_enabled(), 'isStripeEceEnabled' => self::is_stripe_ece_enabled(), - 'isEmbeddedKycEnabled' => self::is_embedded_kyc_enabled(), ] ); } diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index 7627fe92102..990242ec57b 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -95,7 +95,6 @@ public function tear_down() { unset( $_GET ); unset( $_REQUEST ); parent::tear_down(); - delete_option( '_wcpay_feature_embedded_kyc' ); } public function test_filters_registered_properly() { @@ -843,8 +842,6 @@ public function test_maybe_handle_onboarding_init_embedded_kyc() { ->expects( $this->never() ) ->method( 'redirect_to_onboarding_wizard' ); - update_option( '_wcpay_feature_embedded_kyc', '1' ); - // If embedded KYC is in progress, we expect different URL. $this->mock_onboarding_service ->expects( $this->once() ) diff --git a/tests/unit/test-class-wc-payments-features.php b/tests/unit/test-class-wc-payments-features.php index 3f2e41c9a99..fac4d7b37bc 100644 --- a/tests/unit/test-class-wc-payments-features.php +++ b/tests/unit/test-class-wc-payments-features.php @@ -30,7 +30,6 @@ class WC_Payments_Features_Test extends WCPAY_UnitTestCase { '_wcpay_feature_documents' => 'documents', '_wcpay_feature_auth_and_capture' => 'isAuthAndCaptureEnabled', '_wcpay_feature_stripe_ece' => 'isStripeEceEnabled', - '_wcpay_feature_embedded_kyc' => 'isEmbeddedKycEnabled', ]; public function set_up() { @@ -302,23 +301,6 @@ public function test_is_frt_review_feature_active_returns_false_when_flag_is_not $this->assertFalse( WC_Payments_Features::is_frt_review_feature_active() ); } - public function test_is_embedded_kyc_enabled_returns_true() { - $this->set_feature_flag_option( WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, '1' ); - - $this->assertTrue( WC_Payments_Features::is_embedded_kyc_enabled() ); - } - - public function test_is_embedded_kyc_enabled_returns_false_when_flag_is_false() { - $this->set_feature_flag_option( WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, '0' ); - - $this->assertFalse( WC_Payments_Features::is_embedded_kyc_enabled() ); - $this->assertArrayNotHasKey( 'isEmbeddedKycEnabled', WC_Payments_Features::to_array() ); - } - - public function test_is_embedded_kyc_enabled_returns_false_when_flag_is_not_set() { - $this->assertFalse( WC_Payments_Features::is_embedded_kyc_enabled() ); - } - private function setup_enabled_flags( array $enabled_flags ) { foreach ( array_keys( self::FLAG_OPTION_NAME_TO_FRONTEND_KEY_MAPPING ) as $flag ) { add_filter( From 26c54dd8defd6e9da98747c3b1800e548d7051df Mon Sep 17 00:00:00 2001 From: Daniel Mallory Date: Thu, 19 Sep 2024 11:06:38 +0100 Subject: [PATCH 19/40] Design updates for Embedded Components (#9381) Co-authored-by: oaratovskyi Co-authored-by: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com> Co-authored-by: Vlad Olaru --- assets/images/woo-logo.svg | 13 +++++++++++++ changelog/dev-i4-design-updates | 4 ++++ client/onboarding/kyc/appearance.ts | 2 +- client/onboarding/kyc/index.tsx | 9 ++++----- client/onboarding/step.tsx | 11 +++-------- client/onboarding/strings.tsx | 1 + client/onboarding/style.scss | 21 +++++++++++++++++++++ client/stylesheets/abstracts/_colors.scss | 1 + 8 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 assets/images/woo-logo.svg create mode 100644 changelog/dev-i4-design-updates diff --git a/assets/images/woo-logo.svg b/assets/images/woo-logo.svg new file mode 100644 index 00000000000..21cd27114b3 --- /dev/null +++ b/assets/images/woo-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/changelog/dev-i4-design-updates b/changelog/dev-i4-design-updates new file mode 100644 index 00000000000..fb516d6f40c --- /dev/null +++ b/changelog/dev-i4-design-updates @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Some minor styling updates on the Onboarding form. diff --git a/client/onboarding/kyc/appearance.ts b/client/onboarding/kyc/appearance.ts index db0260c302f..19cad65efff 100644 --- a/client/onboarding/kyc/appearance.ts +++ b/client/onboarding/kyc/appearance.ts @@ -5,7 +5,7 @@ */ export default { variables: { - colorPrimary: '#3C2861', + colorPrimary: '#7F54B3', colorBackground: '#FFFFFF', buttonPrimaryColorBackground: '#3858E9', buttonPrimaryColorBorder: '#3858E9', diff --git a/client/onboarding/kyc/index.tsx b/client/onboarding/kyc/index.tsx index ba19b9145d0..1a617bc4f37 100644 --- a/client/onboarding/kyc/index.tsx +++ b/client/onboarding/kyc/index.tsx @@ -2,12 +2,11 @@ * External dependencies */ import React, { useEffect } from 'react'; -import { closeSmall, Icon } from '@wordpress/icons'; /** * Internal dependencies */ -import Logo from 'assets/images/woopayments.svg'; +import WooLogo from 'assets/images/woo-logo.svg'; import Page from 'components/page'; import { OnboardingContextProvider } from 'onboarding/context'; import EmbeddedKyc from 'onboarding/steps/embedded-kyc'; @@ -60,8 +59,8 @@ const OnboardingKycPage: React.FC = () => { { strings.back } WooPayments
    diff --git a/client/onboarding/step.tsx b/client/onboarding/step.tsx index e827a06c107..ad353fc0996 100644 --- a/client/onboarding/step.tsx +++ b/client/onboarding/step.tsx @@ -2,7 +2,6 @@ * External dependencies */ import React from 'react'; -import { Icon, closeSmall } from '@wordpress/icons'; import ChevronLeft from 'gridicons/dist/chevron-left'; /** @@ -12,7 +11,7 @@ import { useStepperContext } from 'components/stepper'; import { OnboardingSteps } from './types'; import { useTrackAbandoned } from './tracking'; import strings from './strings'; -import Logo from 'assets/images/woopayments.svg'; +import WooLogo from 'assets/images/woo-logo.svg'; import './style.scss'; interface Props { @@ -41,17 +40,13 @@ const Step: React.FC< Props > = ( { name, children, showHeading = true } ) => { { strings.back } - WooPayments + Woo
    diff --git a/client/onboarding/strings.tsx b/client/onboarding/strings.tsx index 432e4aae21d..4792d923042 100644 --- a/client/onboarding/strings.tsx +++ b/client/onboarding/strings.tsx @@ -159,4 +159,5 @@ export default { }, continue: __( 'Continue', 'woocommerce-payments' ), back: __( 'Back', 'woocommerce-payments' ), + cancel: __( 'Cancel', 'woocommerce-payments' ), }; diff --git a/client/onboarding/style.scss b/client/onboarding/style.scss index eb0c84771f6..4ffcbdfc44b 100644 --- a/client/onboarding/style.scss +++ b/client/onboarding/style.scss @@ -14,13 +14,20 @@ body.wcpay-onboarding__body { right: 0; height: 80px; padding-top: 8px; + padding-left: 8px; + padding-right: 8px; display: grid; grid-template-columns: 102px 1fr 102px; align-items: stretch; background-color: #fff; + border-bottom: 1px solid $gray-300; z-index: 10; &-button { + color: var( + --wp-components-color-accent, + $gutenberg-blueberry + ); cursor: pointer; background-color: transparent; border: none; @@ -30,6 +37,10 @@ body.wcpay-onboarding__body { font-size: 14px; .gridicons-chevron-left { + fill: var( + --wp-components-color-accent, + $gutenberg-blueberry + ); margin-right: 2px; } @@ -86,6 +97,16 @@ body.wcpay-onboarding__body { width: 100%; height: 40px; // Matching the updated WP Component. We can remove this when we update Components version. margin-top: $gap-large; + background: var( + --wp-components-color-accent, + $gutenberg-blueberry + ); // override the MOX CTA to use Gutenberg Blueberry. + &:hover { + background: var( + --wp-components-color-accent, + darken( $gutenberg-blueberry, 10% ) + ); // 10% darker Gutenberg Blueberry on hover. + } } } diff --git a/client/stylesheets/abstracts/_colors.scss b/client/stylesheets/abstracts/_colors.scss index fe2338dc439..b72d0167d62 100644 --- a/client/stylesheets/abstracts/_colors.scss +++ b/client/stylesheets/abstracts/_colors.scss @@ -69,6 +69,7 @@ $wp-green-100: #001c05; // Missing from dependencies $gutenberg-blue: #007cba; +$gutenberg-blueberry: #3858e9; // Accent color $components-color-accent: var( From c2c8fce9fb80e8878ff09d6fe489e2dfe48edd76 Mon Sep 17 00:00:00 2001 From: Rafael Zaleski Date: Thu, 19 Sep 2024 09:16:31 -0300 Subject: [PATCH 20/40] Default express checkout button label to "Only icon" (#9429) --- changelog/fix-9332-default-express-buttons-only-icon | 4 ++++ .../general-payment-request-button-settings.js | 2 +- client/settings/express-checkout-settings/test/index.js | 2 +- .../test/payment-request-settings.test.js | 6 +++--- .../express-checkout-settings/test/woopay-settings.test.js | 2 +- .../tokenized-payment-request/test/payment-request.test.js | 2 +- includes/class-wc-payment-gateway-wcpay.php | 2 +- .../test-class-wc-rest-payments-settings-controller.php | 2 +- ...est-class-wc-payments-express-checkout-button-helper.php | 2 +- ...est-class-wc-payments-payment-request-button-handler.php | 4 ++-- tests/unit/test-class-wc-payments-woopay-button-handler.php | 2 +- 11 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 changelog/fix-9332-default-express-buttons-only-icon diff --git a/changelog/fix-9332-default-express-buttons-only-icon b/changelog/fix-9332-default-express-buttons-only-icon new file mode 100644 index 00000000000..3687f844239 --- /dev/null +++ b/changelog/fix-9332-default-express-buttons-only-icon @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Default express checkout button label to "Only icon". diff --git a/client/settings/express-checkout-settings/general-payment-request-button-settings.js b/client/settings/express-checkout-settings/general-payment-request-button-settings.js index f6e401ff9fb..d20402e6f38 100644 --- a/client/settings/express-checkout-settings/general-payment-request-button-settings.js +++ b/client/settings/express-checkout-settings/general-payment-request-button-settings.js @@ -199,7 +199,7 @@ const GeneralPaymentRequestButtonSettings = ( { type } ) => { className="payment-method-settings__cta-selection" label={ __( 'Call to action', 'woocommerce-payments' ) } help={ __( - 'Select a button label that fits best wit the flow of purchase or payment experience on your store.', + 'Select a button label that fits best with the flow of purchase or payment experience on your store.', 'woocommerce-payments' ) } hideLabelFromVision diff --git a/client/settings/express-checkout-settings/test/index.js b/client/settings/express-checkout-settings/test/index.js index dd1c5149ea1..4e182f79fd3 100644 --- a/client/settings/express-checkout-settings/test/index.js +++ b/client/settings/express-checkout-settings/test/index.js @@ -23,7 +23,7 @@ jest.mock( '../../../data', () => ( { useWooPayEnabledSettings: jest.fn().mockReturnValue( [ true, jest.fn() ] ), useWooPayCustomMessage: jest.fn().mockReturnValue( [ 'test', jest.fn() ] ), useWooPayStoreLogo: jest.fn().mockReturnValue( [ 'test', jest.fn() ] ), - usePaymentRequestButtonType: jest.fn().mockReturnValue( [ 'buy' ] ), + usePaymentRequestButtonType: jest.fn().mockReturnValue( [ 'default' ] ), usePaymentRequestButtonSize: jest.fn().mockReturnValue( [ 'small' ] ), usePaymentRequestButtonTheme: jest.fn().mockReturnValue( [ 'dark' ] ), usePaymentRequestButtonBorderRadius: jest.fn().mockReturnValue( [ 4 ] ), diff --git a/client/settings/express-checkout-settings/test/payment-request-settings.test.js b/client/settings/express-checkout-settings/test/payment-request-settings.test.js index 4bc3a0b6e12..129d657444e 100644 --- a/client/settings/express-checkout-settings/test/payment-request-settings.test.js +++ b/client/settings/express-checkout-settings/test/payment-request-settings.test.js @@ -23,7 +23,7 @@ import { jest.mock( '../../../data', () => ( { usePaymentRequestEnabledSettings: jest.fn(), usePaymentRequestLocations: jest.fn(), - usePaymentRequestButtonType: jest.fn().mockReturnValue( [ 'buy' ] ), + usePaymentRequestButtonType: jest.fn().mockReturnValue( [ 'default' ] ), usePaymentRequestButtonBorderRadius: jest.fn().mockReturnValue( [ 4 ] ), usePaymentRequestButtonSize: jest.fn().mockReturnValue( [ 'small' ] ), usePaymentRequestButtonTheme: jest.fn().mockReturnValue( [ 'dark' ] ), @@ -148,7 +148,7 @@ describe( 'PaymentRequestSettings', () => { screen.getByRole( 'combobox', { name: 'Call to action', } ) - ).toHaveValue( 'buy' ); + ).toHaveValue( 'default' ); expect( screen.getByLabelText( 'Small (40 px)' ) ).toBeChecked(); expect( screen.getByLabelText( /Dark/ ) ).toBeChecked(); } ); @@ -189,7 +189,7 @@ describe( 'PaymentRequestSettings', () => { const setButtonThemeMock = jest.fn(); usePaymentRequestButtonType.mockReturnValue( [ - 'buy', + 'default', setButtonTypeMock, ] ); usePaymentRequestButtonSize.mockReturnValue( [ diff --git a/client/settings/express-checkout-settings/test/woopay-settings.test.js b/client/settings/express-checkout-settings/test/woopay-settings.test.js index 2fcab112599..d7ce1018146 100644 --- a/client/settings/express-checkout-settings/test/woopay-settings.test.js +++ b/client/settings/express-checkout-settings/test/woopay-settings.test.js @@ -93,7 +93,7 @@ describe( 'WooPaySettings', () => { ); usePaymentRequestButtonType.mockReturnValue( - getMockPaymentRequestButtonType( [ 'buy' ], jest.fn() ) + getMockPaymentRequestButtonType( [ 'default' ], jest.fn() ) ); usePaymentRequestButtonSize.mockReturnValue( diff --git a/client/tokenized-payment-request/test/payment-request.test.js b/client/tokenized-payment-request/test/payment-request.test.js index fcb5c84808e..617384976e5 100644 --- a/client/tokenized-payment-request/test/payment-request.test.js +++ b/client/tokenized-payment-request/test/payment-request.test.js @@ -71,7 +71,7 @@ describe( 'WooPaymentsPaymentRequest', () => { currency_code: 'usd', }, total_label: 'wcpay.test (via WooCommerce)', - button: { type: 'buy', theme: 'dark', height: '48' }, + button: { type: 'default', theme: 'dark', height: '48' }, }; wcpayApi = { getStripe: () => ( { diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index c46a9b70e62..60776e2f017 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -401,7 +401,7 @@ public function __construct( 'title' => __( 'Button type', 'woocommerce-payments' ), 'type' => 'select', 'description' => __( 'Select the button type you would like to show.', 'woocommerce-payments' ), - 'default' => 'buy', + 'default' => 'default', 'desc_tip' => true, 'options' => [ 'default' => __( 'Only icon', 'woocommerce-payments' ), diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index d84f4b680ba..95458b2e6ec 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -550,7 +550,7 @@ public function test_update_settings_saves_payment_request_button_size() { } public function test_update_settings_saves_payment_request_button_type() { - $this->assertEquals( 'buy', $this->gateway->get_option( 'payment_request_button_type' ) ); + $this->assertEquals( 'default', $this->gateway->get_option( 'payment_request_button_type' ) ); $request = new WP_REST_Request(); $request->set_param( 'payment_request_button_type', 'book' ); diff --git a/tests/unit/test-class-wc-payments-express-checkout-button-helper.php b/tests/unit/test-class-wc-payments-express-checkout-button-helper.php index 4c388fd7404..00176f20d07 100644 --- a/tests/unit/test-class-wc-payments-express-checkout-button-helper.php +++ b/tests/unit/test-class-wc-payments-express-checkout-button-helper.php @@ -190,7 +190,7 @@ private function make_wcpay_gateway() { public function test_common_get_button_settings() { $this->assertEquals( [ - 'type' => 'buy', + 'type' => 'default', 'theme' => 'dark', 'height' => '48', 'radius' => '', diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index 07df12a9885..117f254f5ff 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -802,11 +802,11 @@ public function test_get_button_settings() { $this->assertEquals( [ - 'type' => 'buy', + 'type' => 'default', 'theme' => 'dark', 'height' => '48', 'locale' => 'en', - 'branded_type' => 'long', + 'branded_type' => 'short', 'radius' => '', ], $this->pr->get_button_settings() diff --git a/tests/unit/test-class-wc-payments-woopay-button-handler.php b/tests/unit/test-class-wc-payments-woopay-button-handler.php index 746552f783e..82b33b7a683 100644 --- a/tests/unit/test-class-wc-payments-woopay-button-handler.php +++ b/tests/unit/test-class-wc-payments-woopay-button-handler.php @@ -521,7 +521,7 @@ public function test_get_button_settings() { $this->assertEquals( [ - 'type' => 'buy', + 'type' => 'default', 'theme' => 'dark', 'height' => '48', 'size' => 'medium', From f330e3fcd39ff4ec51682d2ec26132fa4e4b19d0 Mon Sep 17 00:00:00 2001 From: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:27:29 +0300 Subject: [PATCH 21/40] Fix progressive onboarding broken e2e test (#9466) Co-authored-by: oaratovskyi --- changelog/dev-fix-po-test | 4 ++++ .../merchant/merchant-progressive-onboarding.spec.js | 12 ++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 changelog/dev-fix-po-test diff --git a/changelog/dev-fix-po-test b/changelog/dev-fix-po-test new file mode 100644 index 00000000000..396d1a7a0e1 --- /dev/null +++ b/changelog/dev-fix-po-test @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix progressive onboarding e2e test diff --git a/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js index 783e1551ec1..7ba10374cb6 100644 --- a/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js +++ b/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js @@ -88,13 +88,9 @@ describe( 'Admin merchant progressive onboarding', () => { } ); - // Loading screen before redirect to Stripe. - await expect( page ).toMatchElement( 'h1.stepper__heading', { - text: 'One last step! Verify your identity with our partner', - } ); - - // Merchant is redirected away to payments/connect again (because of force fisconnected option) - // todo at some point test real Stripe KYC - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); + // Check that Stripe Embedded KYC iframe is loaded. + await page.waitForSelector( + 'iframe[data-testid="stripe-connect-ui-layer-stripe-connect-account-onboarding"]' + ); } ); } ); From 30a1d966e598782961f3e35c3b3ac5ac67c1b412 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Thu, 19 Sep 2024 08:41:40 -0400 Subject: [PATCH 22/40] Disable save changes button unless settings have changed (#9386) --- ...-9305-settings-disable-save-changes-button | 4 ++ .../csv-export-modal/test/index.test.tsx | 1 + client/data/settings/hooks.js | 2 + client/data/settings/reducer.js | 7 ++++ client/data/settings/selectors.js | 4 ++ client/data/settings/test/hooks.js | 1 + client/data/settings/test/reducer.js | 22 +++++++++++ client/disputes/test/index.tsx | 1 + .../cards/order-items-threshold.tsx | 11 +++++- .../cards/purchase-price-threshold.tsx | 11 +++++- .../cards/test/address-mismatch.test.tsx | 1 + .../cards/test/avs-mismatch.test.tsx | 2 + .../cards/test/cvc-verification.test.tsx | 2 + .../test/international-ip-address.test.tsx | 2 + .../cards/test/ip-address-mismatch.test.tsx | 1 + .../cards/test/order-items-threshold.test.tsx | 1 + .../test/purchase-price-threshold.test.tsx | 1 + .../advanced-settings/context.ts | 1 + .../advanced-settings/index.tsx | 6 ++- .../advanced-settings/rule-toggle.tsx | 2 + .../test/__snapshots__/index.test.tsx.snap | 2 + .../test/allow-countries-notice.test.tsx | 1 + .../advanced-settings/test/index.test.tsx | 38 +++++++++++++++++++ .../test/rule-toggle.test.tsx | 3 ++ .../settings/fraud-protection/interfaces.ts | 1 + .../fraud-protection/test/index.test.tsx | 2 + .../settings/save-settings-section/index.js | 4 +- .../save-settings-section/test/index.test.js | 11 ++++++ client/transactions/test/index.tsx | 1 + .../multi-currency/store-settings/index.js | 8 +++- .../test/__snapshots__/index.test.js.snap | 1 + .../store-settings/test/index.test.js | 13 ++++--- .../client/settings/single-currency/index.js | 34 ++++++++++------- .../test/__snapshots__/index.test.js.snap | 1 + 34 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 changelog/update-9305-settings-disable-save-changes-button diff --git a/changelog/update-9305-settings-disable-save-changes-button b/changelog/update-9305-settings-disable-save-changes-button new file mode 100644 index 00000000000..53f56de4dd3 --- /dev/null +++ b/changelog/update-9305-settings-disable-save-changes-button @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Disable save changes button until a setting has changed. diff --git a/client/components/csv-export-modal/test/index.test.tsx b/client/components/csv-export-modal/test/index.test.tsx index 3254e197156..731abf9f136 100644 --- a/client/components/csv-export-modal/test/index.test.tsx +++ b/client/components/csv-export-modal/test/index.test.tsx @@ -44,6 +44,7 @@ describe( 'RefundModal', () => { mockUseSettings.mockReturnValue( { isLoading: false, + isDirty: false, isSaving: false, saveSettings: ( a ) => a, } ); diff --git a/client/data/settings/hooks.js b/client/data/settings/hooks.js index a1ea500b84d..873cc22a1ea 100644 --- a/client/data/settings/hooks.js +++ b/client/data/settings/hooks.js @@ -328,6 +328,7 @@ export const useSettings = () => { const isSaving = useSelect( ( select ) => select( STORE_NAME ).isSavingSettings() ); + const isDirty = useSelect( ( select ) => select( STORE_NAME ).isDirty() ); const isLoading = useSelect( ( select ) => { select( STORE_NAME ).getSettings(); @@ -342,6 +343,7 @@ export const useSettings = () => { isLoading, saveSettings, isSaving, + isDirty, }; }; diff --git a/client/data/settings/reducer.js b/client/data/settings/reducer.js index 60298c1d9e9..69d9019d1f9 100644 --- a/client/data/settings/reducer.js +++ b/client/data/settings/reducer.js @@ -6,6 +6,7 @@ import ACTION_TYPES from './action-types'; const defaultState = { + isDirty: false, isSaving: false, savingError: null, data: {}, @@ -20,12 +21,14 @@ export const receiveSettings = ( return { ...state, data: action.data, + isDirty: false, }; case ACTION_TYPES.SET_SETTINGS_VALUES: return { ...state, savingError: null, + isDirty: true, data: { ...state.data, ...action.payload, @@ -35,6 +38,8 @@ export const receiveSettings = ( case ACTION_TYPES.SET_IS_SAVING_SETTINGS: return { ...state, + isDirty: + action.isSaving || action.error ? state.isDirty : false, isSaving: action.isSaving, savingError: action.error, }; @@ -42,6 +47,7 @@ export const receiveSettings = ( case ACTION_TYPES.SET_SELECTED_PAYMENT_METHOD: return { ...state, + isDirty: true, data: { ...state.data, enabled_payment_method_ids: state.data.enabled_payment_method_ids.concat( @@ -53,6 +59,7 @@ export const receiveSettings = ( case ACTION_TYPES.SET_UNSELECTED_PAYMENT_METHOD: return { ...state, + isDirty: true, data: { ...state.data, enabled_payment_method_ids: state.data.enabled_payment_method_ids.filter( diff --git a/client/data/settings/selectors.js b/client/data/settings/selectors.js index b8d059f8e41..16cd1b4ec55 100644 --- a/client/data/settings/selectors.js +++ b/client/data/settings/selectors.js @@ -49,6 +49,10 @@ export const isSavingSettings = ( state ) => { return getSettingsState( state ).isSaving || false; }; +export const isDirty = ( state ) => { + return getSettingsState( state ).isDirty || false; +}; + export const getAccountStatementDescriptor = ( state ) => { return getSettings( state ).account_statement_descriptor || ''; }; diff --git a/client/data/settings/test/hooks.js b/client/data/settings/test/hooks.js index 3748b09bc29..ef6500d9b7a 100644 --- a/client/data/settings/test/hooks.js +++ b/client/data/settings/test/hooks.js @@ -177,6 +177,7 @@ describe( 'Settings hooks tests', () => { hasFinishedResolution: jest.fn(), isResolving: jest.fn(), isSavingSettings: jest.fn(), + isDirty: jest.fn(), }; } ); diff --git a/client/data/settings/test/reducer.js b/client/data/settings/test/reducer.js index 7cd4f4ef7c3..159a8eb12e4 100644 --- a/client/data/settings/test/reducer.js +++ b/client/data/settings/test/reducer.js @@ -28,6 +28,7 @@ describe( 'Settings reducer tests', () => { isSaving: false, data: {}, savingError: null, + isDirty: false, } ); } ); @@ -60,6 +61,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves fields other than `data` unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { baz: 'quux', @@ -74,6 +76,7 @@ describe( 'Settings reducer tests', () => { const state = reducer( oldState, updateSettings( newSettings ) ); expect( state ).toEqual( { + isDirty: false, foo: 'bar', data: { quuz: 'corge', @@ -101,6 +104,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', isSaving: false, savingError: {}, @@ -112,6 +116,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: false, foo: 'bar', savingError: null, isSaving: true, @@ -137,6 +142,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { is_manual_capture_enabled: false, @@ -151,6 +157,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, savingError: null, foo: 'bar', data: { @@ -181,6 +188,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { account_statement_descriptor: 'Statement', @@ -195,6 +203,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', savingError: null, data: { @@ -224,6 +233,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { is_payment_request_enabled: false, @@ -238,6 +248,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', savingError: null, data: { @@ -271,6 +282,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { payment_request_enabled_locations: initPaymentRequestState, @@ -285,6 +297,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', data: { payment_request_enabled_locations: enableAllpaymentRequestState, @@ -350,6 +363,7 @@ describe( 'Settings reducer tests', () => { 'leaves other fields unchanged `%j`', ( setting ) => { const oldState = { + isDirty: false, foo: 'bar', data: { [ setting.stateKey ]: setting.settingValue, @@ -364,6 +378,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', savingError: null, data: { @@ -378,6 +393,7 @@ describe( 'Settings reducer tests', () => { describe( 'SET_IS_WOOPAY_ENABLED', () => { test( 'toggles `data.is_woopay_enabled`', () => { const oldState = { + isDirty: true, data: { is_woopay_enabled: false, }, @@ -391,6 +407,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { is_woopay_enabled: false, @@ -402,6 +419,7 @@ describe( 'Settings reducer tests', () => { const state = reducer( oldState, updateIsWooPayEnabled( true ) ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', savingError: null, data: { @@ -430,6 +448,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { woopay_custom_message: '', @@ -444,6 +463,7 @@ describe( 'Settings reducer tests', () => { ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', data: { woopay_custom_message: 'test', @@ -469,6 +489,7 @@ describe( 'Settings reducer tests', () => { test( 'leaves other fields unchanged', () => { const oldState = { + isDirty: false, foo: 'bar', data: { woopay_store_logo: '', @@ -480,6 +501,7 @@ describe( 'Settings reducer tests', () => { const state = reducer( oldState, updateWooPayStoreLogo( 'test' ) ); expect( state ).toEqual( { + isDirty: true, foo: 'bar', data: { woopay_store_logo: 'test', diff --git a/client/disputes/test/index.tsx b/client/disputes/test/index.tsx index 9233f46817f..1409bfc852d 100644 --- a/client/disputes/test/index.tsx +++ b/client/disputes/test/index.tsx @@ -176,6 +176,7 @@ describe( 'Disputes list', () => { isLoading: false, isSaving: false, saveSettings: ( a ) => a, + isDirty: false, } ); global.wcpaySettings = { diff --git a/client/settings/fraud-protection/advanced-settings/cards/order-items-threshold.tsx b/client/settings/fraud-protection/advanced-settings/cards/order-items-threshold.tsx index 2dd0e96ea8b..31b4c7be8b6 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/order-items-threshold.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/order-items-threshold.tsx @@ -37,6 +37,7 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp protectionSettingsUI, setProtectionSettingsUI, setProtectionSettingsChanged, + setIsDirty, } = useContext( FraudPreventionSettingsContext ); const settingUI = useMemo( @@ -97,7 +98,10 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp placeholder={ '0' } value={ minItemsCount } type="number" - onChange={ setMinItemsCount } + onChange={ ( value ) => { + setMinItemsCount( value ); + setIsDirty( true ); + } } onKeyDown={ ( e ) => /^[+-.,e]$/m.test( e.key ) && e.preventDefault() } @@ -121,7 +125,10 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp placeholder={ '0' } type="number" value={ maxItemsCount } - onChange={ setMaxItemsCount } + onChange={ ( value ) => { + setMaxItemsCount( value ); + setIsDirty( true ); + } } onKeyDown={ ( e ) => /^[+-.,e]$/m.test( e.key ) && e.preventDefault() } diff --git a/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx b/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx index d99b3b7ca4b..59bd205a45c 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx @@ -56,6 +56,7 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo protectionSettingsUI, setProtectionSettingsUI, setProtectionSettingsChanged, + setIsDirty, } = useContext( FraudPreventionSettingsContext ); const settingUI = useMemo( @@ -111,7 +112,10 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo prefix={ currencySymbol } placeholder={ '0.00' } value={ minAmount.toString() } - onChange={ ( val ) => setMinAmount( Number( val ) ) } + onChange={ ( val ) => { + setMinAmount( Number( val ) ); + setIsDirty( true ); + } } help={ __( 'Leave blank for no limit', 'woocommerce-payments' @@ -130,7 +134,10 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo prefix={ currencySymbol } placeholder={ '0.00' } value={ maxAmount.toString() } - onChange={ ( val ) => setMaxAmount( Number( val ) ) } + onChange={ ( val ) => { + setMaxAmount( Number( val ) ); + setIsDirty( true ); + } } help={ __( 'Leave blank for no limit', 'woocommerce-payments' diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/address-mismatch.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/address-mismatch.test.tsx index 09c4133ae55..8162cd07f1b 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/address-mismatch.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/address-mismatch.test.tsx @@ -34,6 +34,7 @@ describe( 'Address mismatch card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; test( 'renders correctly', () => { settings.address_mismatch.enabled = false; diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/avs-mismatch.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/avs-mismatch.test.tsx index dd917d3ca2d..b2ae2de8e27 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/avs-mismatch.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/avs-mismatch.test.tsx @@ -42,6 +42,7 @@ describe( 'AVS mismatch card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; const { container } = render( @@ -70,6 +71,7 @@ describe( 'AVS mismatch card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; const { container } = render( diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/cvc-verification.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/cvc-verification.test.tsx index e1b1e76d4aa..dfbbbc47ad5 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/cvc-verification.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/cvc-verification.test.tsx @@ -42,6 +42,7 @@ describe( 'CVC verification card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; const { container } = render( @@ -73,6 +74,7 @@ describe( 'CVC verification card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; const { container } = render( diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/international-ip-address.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/international-ip-address.test.tsx index f8e99540685..6a36f94b4ee 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/international-ip-address.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/international-ip-address.test.tsx @@ -43,6 +43,8 @@ describe( 'International IP address card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + isDirty: false, + setIsDirty: jest.fn(), }; global.wcSettings = { admin: { diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/ip-address-mismatch.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/ip-address-mismatch.test.tsx index 8c091a59fdc..b102364ad75 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/ip-address-mismatch.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/ip-address-mismatch.test.tsx @@ -40,6 +40,7 @@ describe( 'International billing address card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; global.wcSettings = { admin: { diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/order-items-threshold.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/order-items-threshold.test.tsx index 3762aa9db47..268845ba776 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/order-items-threshold.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/order-items-threshold.test.tsx @@ -39,6 +39,7 @@ describe( 'Order items threshold card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; test( 'renders correctly', () => { const { container } = render( diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/purchase-price-threshold.test.tsx b/client/settings/fraud-protection/advanced-settings/cards/test/purchase-price-threshold.test.tsx index 5399a7d2ba0..23f12a04d51 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/purchase-price-threshold.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/test/purchase-price-threshold.test.tsx @@ -67,6 +67,7 @@ describe( 'Purchase price threshold card', () => { setProtectionSettingsUI: setSettings, protectionSettingsChanged: false, setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; test( 'renders correctly', () => { const { container } = render( diff --git a/client/settings/fraud-protection/advanced-settings/context.ts b/client/settings/fraud-protection/advanced-settings/context.ts index ade59fc45ce..aa708fa36d0 100644 --- a/client/settings/fraud-protection/advanced-settings/context.ts +++ b/client/settings/fraud-protection/advanced-settings/context.ts @@ -9,6 +9,7 @@ const FraudPreventionSettingsContext = createContext( { setProtectionSettingsUI: () => null, protectionSettingsChanged: false, setProtectionSettingsChanged: () => false, + setIsDirty: () => null, } as FraudPreventionSettingsContextType ); export default FraudPreventionSettingsContext; diff --git a/client/settings/fraud-protection/advanced-settings/index.tsx b/client/settings/fraud-protection/advanced-settings/index.tsx index f18b70a624d..881a09fdb90 100644 --- a/client/settings/fraud-protection/advanced-settings/index.tsx +++ b/client/settings/fraud-protection/advanced-settings/index.tsx @@ -102,6 +102,8 @@ const SaveFraudProtectionSettingsButton: React.FC = ( { children } ) => { }; const FraudProtectionAdvancedSettingsPage: React.FC = () => { + const [ isDirty, setIsDirty ] = useState( false ); + const { saveSettings, isLoading, isSaving } = useSettings() as SettingsHook; const cardObserver = useRef< IntersectionObserver >(); @@ -327,7 +329,8 @@ const FraudProtectionAdvancedSettingsPage: React.FC = () => { disabled={ isSaving || isLoading || - 'error' === advancedFraudProtectionSettings + 'error' === advancedFraudProtectionSettings || + ! isDirty } > { __( 'Save Changes', 'woocommerce-payments' ) } @@ -341,6 +344,7 @@ const FraudProtectionAdvancedSettingsPage: React.FC = () => { setProtectionSettingsUI, protectionSettingsChanged, setProtectionSettingsChanged, + setIsDirty, } } > diff --git a/client/settings/fraud-protection/advanced-settings/rule-toggle.tsx b/client/settings/fraud-protection/advanced-settings/rule-toggle.tsx index ef18e7cd9b5..85e32790595 100644 --- a/client/settings/fraud-protection/advanced-settings/rule-toggle.tsx +++ b/client/settings/fraud-protection/advanced-settings/rule-toggle.tsx @@ -58,6 +58,7 @@ const FraudProtectionRuleToggle: React.FC< FraudProtectionRuleToggleProps > = ( protectionSettingsUI, setProtectionSettingsUI, setProtectionSettingsChanged, + setIsDirty, } = useContext( FraudPreventionSettingsContext ); const { isFRTReviewFeatureActive } = wcpaySettings; @@ -100,6 +101,7 @@ const FraudProtectionRuleToggle: React.FC< FraudProtectionRuleToggleProps > = ( const handleToggleChange = () => { setToggleState( ( value ) => ! value ); + setIsDirty( true ); }; if ( ! protectionSettingsUI ) { diff --git a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/index.test.tsx.snap b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/index.test.tsx.snap index d0180ec7b4d..dddbf129bdb 100644 --- a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/index.test.tsx.snap +++ b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/index.test.tsx.snap @@ -4743,6 +4743,7 @@ exports[`Advanced fraud protection settings renders correctly 1`] = `
    ); + const avsThresholdToggle = await container.findByLabelText( + 'Block transactions for mismatched AVS' + ); + avsThresholdToggle.click(); + avsThresholdToggle.click(); const [ saveButton ] = await container.findAllByText( 'Save Changes' ); saveButton.click(); expect( mockUseSettings().saveSettings.mock.calls.length ).toBe( 0 ); @@ -285,6 +295,7 @@ describe( 'Advanced fraud protection settings', () => { saveSettings: jest.fn(), isSaving: false, isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ defaultSettings, @@ -298,6 +309,12 @@ describe( 'Advanced fraud protection settings', () => { ); + + const avsThresholdToggle = await container.findByLabelText( + 'Block transactions for mismatched AVS' + ); + avsThresholdToggle.click(); + avsThresholdToggle.click(); const [ saveButton ] = await container.findAllByText( 'Save Changes' ); saveButton.click(); await waitFor( () => { @@ -345,6 +362,7 @@ describe( 'Advanced fraud protection settings', () => { isSaving: false, saveSettings: jest.fn(), isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ defaultSettings, @@ -358,6 +376,12 @@ describe( 'Advanced fraud protection settings', () => { ); + + const avsThresholdToggle = await container.findByLabelText( + 'Block transactions for mismatched AVS' + ); + avsThresholdToggle.click(); + avsThresholdToggle.click(); const [ saveButton ] = await container.findAllByText( 'Save Changes' ); saveButton.click(); await waitFor( () => { @@ -409,6 +433,7 @@ describe( 'Advanced fraud protection settings', () => { saveSettings: jest.fn(), isSaving: false, isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ defaultSettings, @@ -422,6 +447,12 @@ describe( 'Advanced fraud protection settings', () => { ); + + const avsThresholdToggle = await container.findByLabelText( + 'Block transactions for mismatched AVS' + ); + avsThresholdToggle.click(); + avsThresholdToggle.click(); const [ saveButton ] = await container.findAllByText( 'Save Changes' ); saveButton.click(); await waitFor( () => { @@ -456,6 +487,7 @@ describe( 'Advanced fraud protection settings', () => { isSaving: false, saveSettings: jest.fn(), isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ defaultSettings, @@ -469,7 +501,13 @@ describe( 'Advanced fraud protection settings', () => { ); + const avsThresholdToggle = await container.findByLabelText( + 'Block transactions for mismatched AVS' + ); + avsThresholdToggle.click(); + avsThresholdToggle.click(); const [ saveButton ] = await container.findAllByText( 'Save Changes' ); + saveButton.click(); await waitFor( () => { expect( mockUseSettings().saveSettings.mock.calls.length ).toBe( diff --git a/client/settings/fraud-protection/advanced-settings/test/rule-toggle.test.tsx b/client/settings/fraud-protection/advanced-settings/test/rule-toggle.test.tsx index f22eebbf13c..d2476015479 100644 --- a/client/settings/fraud-protection/advanced-settings/test/rule-toggle.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/test/rule-toggle.test.tsx @@ -26,6 +26,7 @@ interface mockContext { protectionSettingsChanged: boolean; setProtectionSettingsUI: jest.Mock; setProtectionSettingsChanged: jest.Mock; + setIsDirty: jest.Mock; } describe( 'Fraud protection rule toggle tests', () => { @@ -43,6 +44,7 @@ describe( 'Fraud protection rule toggle tests', () => { protectionSettingsChanged: false, setProtectionSettingsUI: jest.fn(), setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; beforeEach( () => { @@ -56,6 +58,7 @@ describe( 'Fraud protection rule toggle tests', () => { protectionSettingsChanged: false, setProtectionSettingsUI: jest.fn(), setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; } ); diff --git a/client/settings/fraud-protection/interfaces.ts b/client/settings/fraud-protection/interfaces.ts index 17fcf415457..7ad040bad87 100644 --- a/client/settings/fraud-protection/interfaces.ts +++ b/client/settings/fraud-protection/interfaces.ts @@ -36,6 +36,7 @@ export interface FraudPreventionSettingsContextType { setProtectionSettingsUI: ( settings: ProtectionSettingsUI ) => void; protectionSettingsChanged: boolean; setProtectionSettingsChanged: Dispatch< SetStateAction< boolean > >; + setIsDirty: Dispatch< SetStateAction< boolean > >; } export interface FraudProtectionSettingsSingleCheck { diff --git a/client/settings/fraud-protection/test/index.test.tsx b/client/settings/fraud-protection/test/index.test.tsx index e7650195cde..1b78011aad4 100644 --- a/client/settings/fraud-protection/test/index.test.tsx +++ b/client/settings/fraud-protection/test/index.test.tsx @@ -50,6 +50,7 @@ const mockUseSettings = useSettings as jest.MockedFunction< () => { settings: any; isLoading: boolean; + isDirty: boolean; saveSettings: () => void; isSaving: boolean; } @@ -71,6 +72,7 @@ describe( 'FraudProtection', () => { mockUseSettings.mockReturnValue( { settings: {}, isSaving: false, + isDirty: false, saveSettings: jest.fn(), isLoading: false, } ); diff --git a/client/settings/save-settings-section/index.js b/client/settings/save-settings-section/index.js index 28083c94348..17f90da54de 100644 --- a/client/settings/save-settings-section/index.js +++ b/client/settings/save-settings-section/index.js @@ -16,7 +16,7 @@ import './style.scss'; import WooPayDisableFeedback from '../woopay-disable-feedback'; const SaveSettingsSection = ( { disabled = false } ) => { - const { saveSettings, isSaving, isLoading } = useSettings(); + const { saveSettings, isSaving, isLoading, isDirty } = useSettings(); const settings = useGetSettings(); // Keep the inital value of is_payment_request_enabled @@ -111,7 +111,7 @@ const SaveSettingsSection = ( { disabled = false } ) => {