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.txt b/changelog.txt index a0ab5a31932..4f1bdeb437b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ *** WooPayments Changelog *** += 8.2.2 - 2024-09-24 = +* Fix - Fix WooPay pre-checking place order bug when buying a subscription. + = 8.2.1 - 2024-09-13 = * Fix - Create div container element with JS dynamically. diff --git a/changelog/9363-manual-capture-disable-payment-methods b/changelog/9363-manual-capture-disable-payment-methods new file mode 100644 index 00000000000..5d21010f61b --- /dev/null +++ b/changelog/9363-manual-capture-disable-payment-methods @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Update payment methods when manual capture is enabled. diff --git a/changelog/add-8522-utc-list-page-csv b/changelog/add-8522-utc-list-page-csv new file mode 100644 index 00000000000..c3d00989f0c --- /dev/null +++ b/changelog/add-8522-utc-list-page-csv @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add UTC to the date time column header of transactions list page. diff --git a/changelog/add-9245-ece-locale b/changelog/add-9245-ece-locale new file mode 100644 index 00000000000..d95e1eaa596 --- /dev/null +++ b/changelog/add-9245-ece-locale @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Provide locale to Express Checkout Element. 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/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/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/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/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/changelog/fix-9390-hide-bnpl-pmme-if-gateway-is-disabled b/changelog/fix-9390-hide-bnpl-pmme-if-gateway-is-disabled new file mode 100644 index 00000000000..ff3a9e534e5 --- /dev/null +++ b/changelog/fix-9390-hide-bnpl-pmme-if-gateway-is-disabled @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Do not display BNPL methods and PMME when WooPayments is disabled diff --git a/changelog/fix-reintroduce-support-for-wcpay_force_network_saved_cards-filter b/changelog/fix-reintroduce-support-for-wcpay_force_network_saved_cards-filter new file mode 100644 index 00000000000..459e9a4d73e --- /dev/null +++ b/changelog/fix-reintroduce-support-for-wcpay_force_network_saved_cards-filter @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Allow the network saved card payment method setting to be overridden by the `wcpay_force_network_saved_cards` filter diff --git a/changelog/fix-update-dark-blueberry-color b/changelog/fix-update-dark-blueberry-color new file mode 100644 index 00000000000..5ef4bda84ad --- /dev/null +++ b/changelog/fix-update-dark-blueberry-color @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix gutenberg blueberry focus color 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/changelog/multi-currency-v2 b/changelog/multi-currency-v2 deleted file mode 100644 index 463f61c882e..00000000000 --- a/changelog/multi-currency-v2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Decoupled Multi-currency module from gateway dependencies. diff --git a/changelog/revert-9439-multi-currency-v2 b/changelog/revert-9439-multi-currency-v2 new file mode 100644 index 00000000000..8b478ae7517 --- /dev/null +++ b/changelog/revert-9439-multi-currency-v2 @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Revert "Decoupled Multi-currency module from gateway dependencies." + + 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/additional-methods-setup/upe-preview-methods-selector/add-payment-methods-task.js b/client/additional-methods-setup/upe-preview-methods-selector/add-payment-methods-task.js index fbc23b63b67..76a2f3baa71 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/add-payment-methods-task.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/add-payment-methods-task.js @@ -36,7 +36,7 @@ import PaymentMethodCheckbox from '../../components/payment-methods-checkboxes/p import { LoadableBlock } from '../../components/loadable'; import LoadableSettingsSection from '../../settings/loadable-settings-section'; import CurrencyInformationForMethods from './currency-information-for-methods'; -import { getMissingCurrenciesTooltipMessage } from 'multi-currency/interface/functions'; +import { getMissingCurrenciesTooltipMessage } from 'wcpay/multi-currency/missing-currencies-message'; import { upeCapabilityStatuses, upeMethods } from '../constants'; import paymentMethodsMap from '../../payment-methods-map'; import ConfirmPaymentMethodActivationModal from 'wcpay/settings/payment-methods-list/activation-modal'; diff --git a/client/additional-methods-setup/upe-preview-methods-selector/currency-information-for-methods.js b/client/additional-methods-setup/upe-preview-methods-selector/currency-information-for-methods.js index 9639a729c8b..f4e1fc02cbc 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/currency-information-for-methods.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/currency-information-for-methods.js @@ -9,11 +9,11 @@ import interpolateComponents from '@automattic/interpolate-components'; /** * Internal dependencies */ -import { useAccountDomesticCurrency } from '../../data'; import { + useAccountDomesticCurrency, useCurrencies, useEnabledCurrencies, -} from 'multi-currency/interface/data'; +} from '../../data'; import WCPaySettingsContext from '../../settings/wcpay-settings-context'; import InlineNotice from 'components/inline-notice'; import PaymentMethodsMap from '../../payment-methods-map'; diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js index 3a12347327f..3339e812975 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js @@ -22,14 +22,11 @@ import { useEnabledPaymentMethodIds, useGetPaymentMethodStatuses, useSettings, + useCurrencies, + useEnabledCurrencies, useManualCapture, useAccountDomesticCurrency, } from '../../../data'; -import { - useCurrencies, - useEnabledCurrencies, -} from 'multi-currency/interface/data'; - import WCPaySettingsContext from '../../../settings/wcpay-settings-context'; import { upeCapabilityStatuses } from 'wcpay/additional-methods-setup/constants'; @@ -37,16 +34,13 @@ jest.mock( '../../../data', () => ( { useGetAvailablePaymentMethodIds: jest.fn(), useEnabledPaymentMethodIds: jest.fn(), useSettings: jest.fn(), + useCurrencies: jest.fn(), + useEnabledCurrencies: jest.fn(), useGetPaymentMethodStatuses: jest.fn(), useManualCapture: jest.fn(), useAccountDomesticCurrency: jest.fn(), } ) ); -jest.mock( 'multi-currency/interface/data', () => ( { - useCurrencies: jest.fn(), - useEnabledCurrencies: jest.fn(), -} ) ); - jest.mock( '@wordpress/a11y', () => ( { ...jest.requireActual( '@wordpress/a11y' ), speak: jest.fn(), diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js index cc2abefe96d..fd473249eb7 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js @@ -7,21 +7,18 @@ import { render, screen } from '@testing-library/react'; /** * Internal dependencies */ -import { useAccountDomesticCurrency } from '../../../data'; import { useCurrencies, useEnabledCurrencies, -} from 'multi-currency/interface/data'; + useAccountDomesticCurrency, +} from 'wcpay/data'; import CurrencyInformationForMethods from '../currency-information-for-methods'; import WCPaySettingsContext from '../../../settings/wcpay-settings-context'; -jest.mock( '../../../data', () => ( { - useAccountDomesticCurrency: jest.fn(), -} ) ); - -jest.mock( 'multi-currency/interface/data', () => ( { +jest.mock( 'wcpay/data', () => ( { useCurrencies: jest.fn(), useEnabledCurrencies: jest.fn(), + useAccountDomesticCurrency: jest.fn(), } ) ); jest.mock( '@wordpress/a11y', () => ( { diff --git a/client/additional-methods-setup/wizard/task-item.js b/client/additional-methods-setup/wizard/task-item.js index 57975deeb22..943ac88760e 100644 --- a/client/additional-methods-setup/wizard/task-item.js +++ b/client/additional-methods-setup/wizard/task-item.js @@ -11,13 +11,7 @@ import { Icon, check } from '@wordpress/icons'; import WizardTaskContext from './task/context'; import './task-item.scss'; -const WizardTaskItem = ( { - children, - title, - index, - className, - visibleDescription, -} ) => { +const WizardTaskItem = ( { children, title, index, className } ) => { const { isCompleted, isActive } = useContext( WizardTaskContext ); return ( @@ -45,16 +39,6 @@ const WizardTaskItem = ( { { title } - { visibleDescription && ! isActive && ( - - { visibleDescription } - - ) }
{ priceRoundingType ) } data-testid={ 'price_rounding' } - onChange={ ( event ) => + onChange={ ( event ) => { setPriceRoundingType( event.target.value - ) - } + ); + setIsDirty( true ); + } } > { /* eslint-enable jsx-a11y/no-onchange */ } { Object.keys( @@ -444,11 +449,12 @@ const SingleCurrencySettings = () => { priceCharmType ) } data-testid={ 'price_charm' } - onChange={ ( event ) => + onChange={ ( event ) => { setPriceCharmType( event.target.value - ) - } + ); + setIsDirty( true ); + } } > { /* eslint-enable jsx-a11y/no-onchange */ } { Object.keys( @@ -524,7 +530,7 @@ const SingleCurrencySettings = () => { { __( 'Save changes', 'woocommerce-payments' ) } diff --git a/multi-currency/client/settings/single-currency/style.scss b/client/multi-currency/single-currency-settings/style.scss similarity index 100% rename from multi-currency/client/settings/single-currency/style.scss rename to client/multi-currency/single-currency-settings/style.scss diff --git a/multi-currency/client/settings/single-currency/test/__snapshots__/index.test.js.snap b/client/multi-currency/single-currency-settings/test/__snapshots__/index.test.js.snap similarity index 99% rename from multi-currency/client/settings/single-currency/test/__snapshots__/index.test.js.snap rename to client/multi-currency/single-currency-settings/test/__snapshots__/index.test.js.snap index 8c6389ebb21..b434a8e1056 100644 --- a/multi-currency/client/settings/single-currency/test/__snapshots__/index.test.js.snap +++ b/client/multi-currency/single-currency-settings/test/__snapshots__/index.test.js.snap @@ -329,6 +329,7 @@ exports[`Single currency settings screen Page renders correctly 1`] = ` > Save changes diff --git a/multi-currency/client/settings/single-currency/test/index.test.js b/client/multi-currency/single-currency-settings/test/index.test.js similarity index 97% rename from multi-currency/client/settings/single-currency/test/index.test.js rename to client/multi-currency/single-currency-settings/test/index.test.js index 0559953e54b..5f2597dff2c 100644 --- a/multi-currency/client/settings/single-currency/test/index.test.js +++ b/client/multi-currency/single-currency-settings/test/index.test.js @@ -15,11 +15,11 @@ import { useEnabledCurrencies, useCurrencySettings, useStoreSettings, -} from 'multi-currency/data'; +} from 'wcpay/data'; -import MultiCurrencySettingsContext from 'multi-currency/context'; +import MultiCurrencySettingsContext from '../../context'; -jest.mock( 'multi-currency/data', () => ( { +jest.mock( 'wcpay/data', () => ( { useCurrencies: jest.fn(), useAvailableCurrencies: jest.fn(), useDefaultCurrency: jest.fn(), 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/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 } { className="stepper__nav-button" onClick={ handleExit } > - + { strings.cancel } 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 } - + - + { strings.cancel } 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..67a6351aab6 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, + $gutenberg-blueberry-focus + ); + } } } diff --git a/client/overview/task-list/tasks/dispute-task.tsx b/client/overview/task-list/tasks/dispute-task.tsx index 235b92696b9..fdf48c03d78 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 'multi-currency/interface/functions'; +import { formatCurrency } from 'wcpay/utils/currency'; 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 01f87431274..ccc0764f38b 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'utils/currency'; 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 0a43cb223e0..ac6dada265e 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'wcpay/utils/currency'; 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 193ee236288..9ff428c94fe 100644 --- a/client/payment-details/readers/index.js +++ b/client/payment-details/readers/index.js @@ -19,10 +19,7 @@ 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; 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 4e9d9559a4b..4350d609cd7 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -37,10 +37,7 @@ 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 'multi-currency/interface/functions'; +import { formatCurrency, formatExplicitCurrency } from 'utils/currency'; 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 64bc74d91d2..1cb14d6aa7a 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 'multi-currency/interface/functions'; +} from 'utils/currency'; import { formatFee } from 'utils/fees'; import { getAdminUrl } from 'wcpay/utils'; import { ShieldIcon } from 'wcpay/icons'; 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/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..5c47153e0fb 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 'multi-currency/interface/functions'; +import { getCurrency } from 'utils/currency'; import FraudProtectionRuleCard from '../rule-card'; import FraudProtectionRuleToggle from '../rule-toggle'; import FraudProtectionRuleDescription from '../rule-description'; @@ -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`] = ` Save Changes @@ -5568,6 +5569,7 @@ exports[`Advanced fraud protection settings renders correctly 1`] = ` Save Changes diff --git a/client/settings/fraud-protection/advanced-settings/test/allow-countries-notice.test.tsx b/client/settings/fraud-protection/advanced-settings/test/allow-countries-notice.test.tsx index 42f6dd7f1cf..c503bf74ed7 100644 --- a/client/settings/fraud-protection/advanced-settings/test/allow-countries-notice.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/test/allow-countries-notice.test.tsx @@ -19,6 +19,7 @@ const mockContext = { protectionSettingsChanged: false, setProtectionSettingsUI: jest.fn(), setProtectionSettingsChanged: jest.fn(), + setIsDirty: jest.fn(), }; declare const global: { diff --git a/client/settings/fraud-protection/advanced-settings/test/index.test.tsx b/client/settings/fraud-protection/advanced-settings/test/index.test.tsx index 10b6d8f99c9..4b51644a003 100644 --- a/client/settings/fraud-protection/advanced-settings/test/index.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/test/index.test.tsx @@ -29,6 +29,7 @@ jest.mock( '@wordpress/data', () => ( { createSuccessNotice: jest.fn(), createErrorNotice: jest.fn(), onLoad: jest.fn(), + onHistoryChange: jest.fn(), } ) ), registerStore: jest.fn(), select: jest.fn(), @@ -85,6 +86,7 @@ const mockUseSettings = useSettings as jest.MockedFunction< () => { settings: any; isLoading: boolean; + isDirty: boolean; saveSettings: jest.Mock; isSaving: boolean; } @@ -170,6 +172,7 @@ describe( 'Advanced fraud protection settings', () => { saveSettings: jest.fn(), isSaving: false, isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ [], @@ -186,6 +189,7 @@ describe( 'Advanced fraud protection settings', () => { saveSettings: jest.fn(), isSaving: false, isLoading: false, + isDirty: false, } ); mockUseAdvancedFraudProtectionSettings.mockReturnValue( [ 'error', @@ -241,6 +245,7 @@ describe( 'Advanced fraud protection settings', () => { saveSettings: jest.fn(), isLoading: false, isSaving: false, + isDirty: false, } ); container = render( @@ -252,6 +257,11 @@ 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(); 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..0719a904c78 100644 --- a/client/settings/fraud-protection/test/index.test.tsx +++ b/client/settings/fraud-protection/test/index.test.tsx @@ -11,6 +11,7 @@ import { useDispatch } from '@wordpress/data'; import FraudProtection from '..'; import { useCurrentProtectionLevel, + useCurrencies, useAdvancedFraudProtectionSettings, useSettings, } from 'wcpay/data'; @@ -28,6 +29,7 @@ jest.mock( 'wcpay/data', () => ( { useAdvancedFraudProtectionSettings: jest.fn(), useCurrentProtectionLevel: jest.fn(), useSettings: jest.fn(), + useCurrencies: jest.fn(), } ) ); jest.mock( '@wordpress/data', () => ( { @@ -42,6 +44,10 @@ 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 ] >; @@ -50,6 +56,7 @@ const mockUseSettings = useSettings as jest.MockedFunction< () => { settings: any; isLoading: boolean; + isDirty: boolean; saveSettings: () => void; isSaving: boolean; } @@ -63,6 +70,16 @@ 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( [ [], @@ -71,6 +88,7 @@ describe( 'FraudProtection', () => { mockUseSettings.mockReturnValue( { settings: {}, isSaving: false, + isDirty: false, saveSettings: jest.fn(), isLoading: false, } ); diff --git a/client/settings/payment-methods-list/index.js b/client/settings/payment-methods-list/index.js index 997c2bb9874..99a34b40daf 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 'multi-currency/interface/functions'; +import { getMissingCurrenciesTooltipMessage } from 'wcpay/multi-currency/missing-currencies-message'; 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 d64f2ded639..2e1f1da1538 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,6 +35,8 @@ 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(), @@ -43,16 +45,6 @@ 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/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 } ) => { { __( 'Save changes', 'woocommerce-payments' ) } diff --git a/client/settings/save-settings-section/test/index.test.js b/client/settings/save-settings-section/test/index.test.js index 561069e6e8b..2a7ef93142c 100644 --- a/client/settings/save-settings-section/test/index.test.js +++ b/client/settings/save-settings-section/test/index.test.js @@ -28,6 +28,16 @@ describe( 'SaveSettingsSection', () => { expect( screen.getByText( 'Save changes' ) ).toBeDisabled(); } ); + it( 'disables the button by default', () => { + useSettings.mockReturnValue( { + isDirty: false, + } ); + + render( ); + + expect( screen.getByText( 'Save changes' ) ).toBeDisabled(); + } ); + it( 'disables the button when saving data', () => { useSettings.mockReturnValue( { isSaving: true, @@ -43,6 +53,7 @@ describe( 'SaveSettingsSection', () => { useSettings.mockReturnValue( { isSaving: false, isLoading: false, + isDirty: true, saveSettings: saveSettingsMock, } ); diff --git a/client/stylesheets/abstracts/_colors.scss b/client/stylesheets/abstracts/_colors.scss index fe2338dc439..859feff2719 100644 --- a/client/stylesheets/abstracts/_colors.scss +++ b/client/stylesheets/abstracts/_colors.scss @@ -69,6 +69,8 @@ $wp-green-100: #001c05; // Missing from dependencies $gutenberg-blue: #007cba; +$gutenberg-blueberry: #3858e9; +$gutenberg-blueberry-focus: #1d35b4; // Accent color $components-color-accent: var( 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/client/transactions/blocked/columns.tsx b/client/transactions/blocked/columns.tsx index d321d9fe028..3ecf1554d06 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'utils/currency'; import TransactionStatusPill from 'wcpay/components/transaction-status-pill'; import { FraudOutcomeTransaction } from '../../data'; import { getDetailsURL } from '../../components/details-link'; @@ -32,6 +32,7 @@ export const getBlockedListColumns = (): Column[] => key: 'created', label: __( 'Date / Time', 'woocommerce-payments' ), screenReaderLabel: __( 'Date / Time', 'woocommerce-payments' ), + labelInCsv: __( 'Date / Time (UTC)', 'woocommerce-payments' ), required: true, isLeftAligned: true, defaultOrder: 'desc', diff --git a/client/transactions/blocked/index.tsx b/client/transactions/blocked/index.tsx index f6153231f69..e1bdce63664 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from '../../utils/currency'; 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 ef8c11d4f15..cbb729ce5d9 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 'multi-currency/interface/functions'; +import { formatCurrencyName } from '../../utils/currency'; import { recordEvent } from 'tracks'; interface TransactionsFiltersProps { diff --git a/client/transactions/list/converted-amount.tsx b/client/transactions/list/converted-amount.tsx index fac4d9acd25..6a74b01a25a 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'utils/currency'; declare const window: any; diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index 33dba4253f7..3893df6658a 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -54,7 +54,7 @@ import { formatCurrency, formatExplicitCurrency, formatExportAmount, -} from 'multi-currency/interface/functions'; +} from 'utils/currency'; import { getChargeChannel } from 'utils/charge'; import Deposit from './deposit'; import ConvertedAmount from './converted-amount'; @@ -94,6 +94,7 @@ interface Column extends TableCardColumn { | 'deposit'; visible?: boolean; cellClassName?: string; + labelInCsv?: string; } const getPaymentSourceDetails = ( txn: Transaction ) => { @@ -158,6 +159,7 @@ const getColumns = ( key: 'date', label: __( 'Date / Time', 'woocommerce-payments' ), screenReaderLabel: __( 'Date and time', 'woocommerce-payments' ), + labelInCsv: __( 'Date / Time (UTC)', 'woocommerce-payments' ), required: true, isLeftAligned: true, defaultOrder: 'desc', @@ -730,9 +732,15 @@ export const TransactionsList = ( endpointExport( '' ); } } else { + const columnsToDisplayInCsv = columnsToDisplay.map( ( column ) => { + if ( column.labelInCsv ) { + return { ...column, label: column.labelInCsv }; + } + return column; + } ); downloadCSVFile( generateCSVFileName( title, params ), - generateCSVDataFromTable( columnsToDisplay, rows ) + generateCSVDataFromTable( columnsToDisplayInCsv, rows ) ); } diff --git a/client/transactions/list/test/index.tsx b/client/transactions/list/test/index.tsx index 5aa669fbd0d..8d97397ce01 100644 --- a/client/transactions/list/test/index.tsx +++ b/client/transactions/list/test/index.tsx @@ -621,7 +621,7 @@ describe( 'Transactions list', () => { const expected = [ '"Transaction Id"', - '"Date / Time"', + '"Date / Time (UTC)"', 'Type', 'Channel', '"Paid Currency"', diff --git a/client/transactions/risk-review/columns.tsx b/client/transactions/risk-review/columns.tsx index d7f5de95111..59db13c52c8 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'utils/currency'; 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 f83010dc1e8..0681110d678 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from '../../utils/currency'; import autocompleter from '../fraud-protection/autocompleter'; import DownloadButton from '../../components/download-button'; import { getFraudOutcomeTransactionsExport } from '../../data/transactions/resolvers'; diff --git a/client/transactions/test/index.tsx b/client/transactions/test/index.tsx index efc014e7624..9eae004fd90 100644 --- a/client/transactions/test/index.tsx +++ b/client/transactions/test/index.tsx @@ -105,6 +105,7 @@ describe( 'TransactionsPage', () => { mockUseSettings.mockReturnValue( { isLoading: false, isSaving: false, + isDirty: false, saveSettings: ( a ) => a, } ); diff --git a/client/transactions/uncaptured/index.tsx b/client/transactions/uncaptured/index.tsx index 17058760c19..3cf14c956f7 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 'multi-currency/interface/functions'; +import { formatExplicitCurrency } from 'utils/currency'; 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 711d3d337ed..b3d0f5b9f2c 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -1,7 +1,7 @@ /** @format */ /** - * External dependencies + * External depencencies */ import { __, sprintf } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; @@ -10,7 +10,7 @@ import './account-fees.scss'; /** * Internal dependencies */ -import { formatCurrency } from 'multi-currency/interface/functions'; +import { formatCurrency } from 'utils/currency'; import { formatFee } from 'utils/fees'; import React from 'react'; import { BaseFee, DiscountFee, FeeStructure } from 'wcpay/types/fees'; diff --git a/multi-currency/client/utils/currency/index.js b/client/utils/currency/index.js similarity index 100% rename from multi-currency/client/utils/currency/index.js rename to client/utils/currency/index.js diff --git a/multi-currency/client/utils/currency/test/index.js b/client/utils/currency/test/index.js similarity index 98% rename from multi-currency/client/utils/currency/test/index.js rename to client/utils/currency/test/index.js index 0a08bbdd7db..25e67b62c3c 100644 --- a/multi-currency/client/utils/currency/test/index.js +++ b/client/utils/currency/test/index.js @@ -6,7 +6,7 @@ /** * Internal dependencies */ -import * as utils from 'multi-currency/utils/currency'; +import * as utils from 'utils/currency'; describe( 'Currency utilities', () => { beforeEach( () => { diff --git a/client/utils/test/account-fees.tsx b/client/utils/test/account-fees.tsx index 3184e42d644..c1b181ac160 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 'multi-currency/interface/functions'; +import { formatCurrency } from '../currency'; import { BaseFee, DiscountFee, FeeStructure } from 'wcpay/types/fees'; -jest.mock( 'multi-currency/interface/functions', () => ( { +jest.mock( '../currency', () => ( { 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 ee97f7045b4..223c35d309d 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,7 +97,6 @@ export const CompanyDataTask = ( { 'woocommerce-payments' ) } className={ null } - visibleDescription={ null } > { __( diff --git a/composer.json b/composer.json index 497530e36e1..cdb679b4afb 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "autoload": { "psr-4": { - "WCPay\\MultiCurrency\\": "multi-currency/src", + "WCPay\\MultiCurrency\\": "includes/multi-currency", "WCPay\\Vendor\\": "lib/packages", "WCPay\\": "src" }, 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/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index 90fe8721443..dbdc4abd522 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Admin */ +use WCPay\Constants\Payment_Method; use WCPay\Constants\Country_Code; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; use WCPay\Constants\Track_Events; @@ -603,6 +604,11 @@ private function update_enabled_payment_methods( WP_REST_Request $request ) { $payment_method_ids_to_enable = $request->get_param( 'enabled_payment_method_ids' ); $available_payment_methods = $this->wcpay_gateway->get_upe_available_payment_methods(); + // Only 'card' and 'link' support manual capture. Leave them enabled if they're already enabled. + if ( $request->has_param( 'is_manual_capture_enabled' ) && $request->get_param( 'is_manual_capture_enabled' ) ) { + $payment_method_ids_to_enable = array_intersect( $payment_method_ids_to_enable, [ Payment_Method::CARD, Payment_Method::LINK ] ); + } + $payment_method_ids_to_enable = array_values( array_filter( $payment_method_ids_to_enable, @@ -612,6 +618,18 @@ function ( $payment_method ) use ( $available_payment_methods ) { ) ); + $this->request_unrequested_payment_methods( $payment_method_ids_to_enable ); + $capability_key_map = $this->wcpay_gateway->get_payment_method_capability_key_map(); + $payment_method_statuses = $this->wcpay_gateway->get_upe_enabled_payment_method_statuses(); + + $payment_method_ids_to_enable = array_filter( + $payment_method_ids_to_enable, + function ( $payment_method_id_to_enable ) use ( $capability_key_map, $payment_method_statuses ) { + $stripe_key = $capability_key_map[ $payment_method_id_to_enable ] ?? null; + return array_key_exists( $stripe_key, $payment_method_statuses ) && 'active' === $payment_method_statuses[ $stripe_key ]['status']; + } + ); + $active_payment_methods = $this->wcpay_gateway->get_upe_enabled_payment_method_ids(); $disabled_payment_methods = array_diff( $active_payment_methods, $payment_method_ids_to_enable ); $enabled_payment_methods = array_diff( $payment_method_ids_to_enable, $active_payment_methods ); @@ -650,10 +668,6 @@ function ( $payment_method ) use ( $available_payment_methods ) { foreach ( WC_Payments::get_payment_gateway_map() as $payment_gateway ) { $payment_gateway->update_option( 'upe_enabled_payment_method_ids', $payment_method_ids_to_enable ); } - - if ( $payment_method_ids_to_enable ) { - $this->request_unrequested_payment_methods( $payment_method_ids_to_enable ); - } } /** diff --git a/includes/class-database-cache.php b/includes/class-database-cache.php index 042d99f7f9e..68b4c7d4d89 100644 --- a/includes/class-database-cache.php +++ b/includes/class-database-cache.php @@ -7,17 +7,16 @@ 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 implements MultiCurrencyCacheInterface { +class Database_Cache { 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-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index c46a9b70e62..b7a694e88a5 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' ), @@ -825,6 +825,9 @@ public function needs_https_setup() { * @return bool Whether the gateway is enabled and ready to accept payments. */ public function is_available() { + if ( ! WC_Payments::get_gateway()->is_enabled() ) { + return false; + } $processing_payment_method = $this->payment_methods[ $this->payment_method->get_id() ]; if ( ! $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) ) { return false; diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index bde6316122a..b2ebc33b7d3 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -17,12 +17,11 @@ 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 implements MultiCurrencyAccountInterface { +class WC_Payments_Account { // 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'; @@ -171,18 +170,6 @@ 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. * @@ -648,22 +635,11 @@ public function get_account_email(): string { * * @return array Currencies. */ - public function get_account_customer_supported_currencies(): array { + public function get_account_customer_supported_currencies() { $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. * @@ -1662,21 +1638,6 @@ 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. * @@ -1718,6 +1679,21 @@ 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 * @@ -1877,9 +1853,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-checkout.php b/includes/class-wc-payments-checkout.php index 5764ecd96d5..79e6e119267 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -332,7 +332,9 @@ public function get_enabled_payment_method_config() { 'number' => '', ] ); - $settings[ $payment_method_id ]['forceNetworkSavedCards'] = $gateway_for_payment_method->should_use_stripe_platform_on_checkout_page(); + + $should_enable_network_saved_cards = Payment_Method::CARD === $payment_method_id && WC_Payments::is_network_saved_cards_enabled(); + $settings[ $payment_method_id ]['forceNetworkSavedCards'] = $should_enable_network_saved_cards || $gateway_for_payment_method->should_use_stripe_platform_on_checkout_page(); } return $settings; diff --git a/includes/class-wc-payments-explicit-price-formatter.php b/includes/class-wc-payments-explicit-price-formatter.php index 31c5364cfbe..d96bfa4f25f 100644 --- a/includes/class-wc-payments-explicit-price-formatter.php +++ b/includes/class-wc-payments-explicit-price-formatter.php @@ -6,6 +6,7 @@ */ use WCPay\MultiCurrency\MultiCurrency; +use WCPay\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. 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/includes/class-wc-payments-localization-service.php b/includes/class-wc-payments-localization-service.php index c0c0d7d84f8..5ea9375429d 100644 --- a/includes/class-wc-payments-localization-service.php +++ b/includes/class-wc-payments-localization-service.php @@ -5,14 +5,12 @@ * @package WooCommerce\Payments */ -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; - defined( 'ABSPATH' ) || exit; /** * WC_Payments_Localization_Service. */ -class WC_Payments_Localization_Service implements MultiCurrencyLocalizationInterface { +class WC_Payments_Localization_Service { 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 93e22fe9897..ed023cbd24e 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -42,7 +42,6 @@ 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. @@ -300,13 +299,6 @@ 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. */ @@ -491,8 +483,7 @@ 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__ . '/compat/multi-currency/wc-payments-multi-currency.php'; - include_once __DIR__ . '/compat/multi-currency/class-wc-payments-currency-manager.php'; + include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php'; include_once __DIR__ . '/class-duplicates-detection-service.php'; self::$woopay_checkout_service = new Checkout_Service(); @@ -585,9 +576,6 @@ 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(); @@ -613,16 +601,18 @@ function () { } ); - // Insert the Stripe Payment Messaging Element only if there is at least one BNPL method enabled. - $enabled_bnpl_payment_methods = array_intersect( - Payment_Method::BNPL_PAYMENT_METHODS, - self::get_gateway()->get_upe_enabled_payment_method_ids() - ); - if ( [] !== $enabled_bnpl_payment_methods ) { - add_action( 'woocommerce_single_product_summary', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 10 ); - add_action( 'woocommerce_proceed_to_checkout', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 5 ); - add_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ] ); - add_action( 'wc_ajax_wcpay_get_cart_total', [ __CLASS__, 'ajax_get_cart_total' ] ); + if ( self::get_gateway()->is_enabled() ) { + // Insert the Stripe Payment Messaging Element only if there is at least one BNPL method enabled. + $enabled_bnpl_payment_methods = array_intersect( + Payment_Method::BNPL_PAYMENT_METHODS, + self::get_gateway()->get_upe_enabled_payment_method_ids() + ); + if ( [] !== $enabled_bnpl_payment_methods ) { + add_action( 'woocommerce_single_product_summary', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 10 ); + add_action( 'woocommerce_proceed_to_checkout', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 5 ); + add_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ] ); + add_action( 'wc_ajax_wcpay_get_cart_total', [ __CLASS__, 'ajax_get_cart_total' ] ); + } } add_filter( 'woocommerce_payment_gateways', [ __CLASS__, 'register_gateway' ] ); @@ -649,6 +639,7 @@ function () { require_once __DIR__ . '/migrations/class-giropay-deprecation-settings-update.php'; require_once __DIR__ . '/migrations/class-erase-bnpl-announcement-meta.php'; require_once __DIR__ . '/migrations/class-erase-deprecated-flags-and-options.php'; + require_once __DIR__ . '/migrations/class-manual-capture-payment-method-settings-update.php'; add_action( 'woocommerce_woocommerce_payments_updated', [ new Allowed_Payment_Request_Button_Types_Update( self::get_gateway() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Allowed_Payment_Request_Button_Sizes_Update( self::get_gateway() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Update_Service_Data_From_Server( self::get_account_service() ), 'maybe_migrate' ] ); @@ -659,6 +650,7 @@ function () { add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Giropay_Deprecation_Settings_Update( self::get_gateway(), self::get_payment_gateway_map() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Erase_Bnpl_Announcement_Meta(), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Erase_Deprecated_Flags_And_Options(), 'maybe_migrate' ] ); + add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Manual_Capture_Payment_Method_Settings_Update( self::get_gateway(), self::get_payment_gateway_map() ), 'maybe_migrate' ] ); include_once WCPAY_ABSPATH . '/includes/class-wc-payments-explicit-price-formatter.php'; WC_Payments_Explicit_Price_Formatter::init(); @@ -1410,22 +1402,6 @@ 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/migrations/class-manual-capture-payment-method-settings-update.php b/includes/migrations/class-manual-capture-payment-method-settings-update.php new file mode 100644 index 00000000000..6775cf301e1 --- /dev/null +++ b/includes/migrations/class-manual-capture-payment-method-settings-update.php @@ -0,0 +1,89 @@ +main_gateway = $main_gateway; + $this->all_registered_gateways = $all_registered_gateways; + } + + /** + * Checks whether we should trigger the event. + */ + public function maybe_migrate() { + $previous_version = get_option( 'woocommerce_woocommerce_payments_version' ); + if ( version_compare( self::VERSION_SINCE, $previous_version, '>' ) ) { + $this->migrate(); + } + } + + /** + * Disables payment methods that do not support manual capture, when manual capture is enabled and updates + * the enabled payment methods for each gateway. + */ + private function migrate() { + $enabled_payment_methods = $this->main_gateway->get_option( 'upe_enabled_payment_method_ids', [] ); + $is_manual_capture_enabled = 'yes' === $this->main_gateway->get_option( 'manual_capture' ); + + if ( $is_manual_capture_enabled ) { + $filtered_payment_methods = array_filter( + $enabled_payment_methods, + function ( $method ) { + return in_array( $method, [ Payment_Method::CARD, Payment_Method::LINK ], true ); + } + ); + + foreach ( $this->all_registered_gateways as $gateway ) { + $stripe_id = $gateway->get_stripe_id(); + if ( Payment_Method::CARD !== $stripe_id && Payment_Method::LINK !== $stripe_id ) { + $gateway->disable(); + } + $gateway->update_option( 'upe_enabled_payment_method_ids', $filtered_payment_methods ); + } + } + } +} diff --git a/multi-currency/src/AdminNotices.php b/includes/multi-currency/AdminNotices.php similarity index 100% rename from multi-currency/src/AdminNotices.php rename to includes/multi-currency/AdminNotices.php diff --git a/multi-currency/src/Analytics.php b/includes/multi-currency/Analytics.php similarity index 99% rename from multi-currency/src/Analytics.php rename to includes/multi-currency/Analytics.php index 258be02d223..ef3f5b17a5a 100644 --- a/multi-currency/src/Analytics.php +++ b/includes/multi-currency/Analytics.php @@ -12,6 +12,7 @@ use Automattic\WooCommerce\Utilities\OrderUtil; use WC_Order; use WC_Order_Refund; +use WC_Payments; defined( 'ABSPATH' ) || exit; @@ -62,7 +63,7 @@ public function init() { $this->register_customer_currencies(); } - if ( $this->multi_currency->gateway_context['is_dev_mode'] ) { + if ( WC_Payments::mode()->is_dev() ) { add_filter( 'woocommerce_analytics_report_should_use_cache', [ $this, 'disable_report_caching' ] ); } @@ -104,7 +105,7 @@ public function init() { * @return void */ public function register_admin_scripts() { - $this->multi_currency->register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); + WC_Payments::register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); } /** diff --git a/multi-currency/src/BackendCurrencies.php b/includes/multi-currency/BackendCurrencies.php similarity index 91% rename from multi-currency/src/BackendCurrencies.php rename to includes/multi-currency/BackendCurrencies.php index bdb2da92b72..9ea009ac4c4 100644 --- a/multi-currency/src/BackendCurrencies.php +++ b/includes/multi-currency/BackendCurrencies.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -23,9 +23,9 @@ class BackendCurrencies { protected $multi_currency; /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; @@ -39,10 +39,10 @@ class BackendCurrencies { /** * Constructor. * - * @param MultiCurrency $multi_currency The MultiCurrency instance. - * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. */ - public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service ) { + public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; } diff --git a/multi-currency/src/Compatibility.php b/includes/multi-currency/Compatibility.php similarity index 99% rename from multi-currency/src/Compatibility.php rename to includes/multi-currency/Compatibility.php index 87e10cbde78..4f60915fb97 100644 --- a/multi-currency/src/Compatibility.php +++ b/includes/multi-currency/Compatibility.php @@ -7,6 +7,8 @@ namespace WCPay\MultiCurrency; +use WC_Deposits; +use WC_Deposits_Product_Manager; use WC_Order; use WC_Order_Refund; use WCPay\MultiCurrency\Compatibility\BaseCompatibility; @@ -39,7 +41,7 @@ class Compatibility extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { add_action( 'init', [ $this, 'init_compatibility_classes' ], 11 ); if ( defined( 'DOING_CRON' ) ) { diff --git a/multi-currency/src/Compatibility/BaseCompatibility.php b/includes/multi-currency/Compatibility/BaseCompatibility.php similarity index 95% rename from multi-currency/src/Compatibility/BaseCompatibility.php rename to includes/multi-currency/Compatibility/BaseCompatibility.php index 3e7d1a67a20..a98073fd113 100644 --- a/multi-currency/src/Compatibility/BaseCompatibility.php +++ b/includes/multi-currency/Compatibility/BaseCompatibility.php @@ -46,5 +46,5 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils ) { * * @return void */ - abstract public function init(); + abstract protected function init(); } diff --git a/multi-currency/src/Compatibility/WooCommerceBookings.php b/includes/multi-currency/Compatibility/WooCommerceBookings.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceBookings.php rename to includes/multi-currency/Compatibility/WooCommerceBookings.php index 756e4eef355..5a99534e5d6 100644 --- a/multi-currency/src/Compatibility/WooCommerceBookings.php +++ b/includes/multi-currency/Compatibility/WooCommerceBookings.php @@ -39,7 +39,7 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils, Fronte * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceDeposits.php b/includes/multi-currency/Compatibility/WooCommerceDeposits.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceDeposits.php rename to includes/multi-currency/Compatibility/WooCommerceDeposits.php index e2ffa89d441..f92819785c5 100644 --- a/multi-currency/src/Compatibility/WooCommerceDeposits.php +++ b/includes/multi-currency/Compatibility/WooCommerceDeposits.php @@ -20,7 +20,7 @@ class WooCommerceDeposits extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { if ( class_exists( 'WC_Deposits' ) ) { /* * Multi-currency support was added to WooCommerce Deposits in version 2.0.1. diff --git a/multi-currency/src/Compatibility/WooCommerceFedEx.php b/includes/multi-currency/Compatibility/WooCommerceFedEx.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceFedEx.php rename to includes/multi-currency/Compatibility/WooCommerceFedEx.php index 8a38d058e40..738e738150f 100644 --- a/multi-currency/src/Compatibility/WooCommerceFedEx.php +++ b/includes/multi-currency/Compatibility/WooCommerceFedEx.php @@ -20,7 +20,7 @@ class WooCommerceFedEx extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php b/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceNameYourPrice.php rename to includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php index fad352e1d1f..155f99e1a4d 100644 --- a/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php +++ b/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php @@ -21,7 +21,7 @@ class WooCommerceNameYourPrice extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php b/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php similarity index 98% rename from multi-currency/src/Compatibility/WooCommercePointsAndRewards.php rename to includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php index 38819d15322..9d78886eb41 100644 --- a/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php +++ b/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php @@ -33,7 +33,7 @@ class WooCommercePointsAndRewards extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommercePreOrders.php b/includes/multi-currency/Compatibility/WooCommercePreOrders.php similarity index 96% rename from multi-currency/src/Compatibility/WooCommercePreOrders.php rename to includes/multi-currency/Compatibility/WooCommercePreOrders.php index b16dd91b646..3c3fe9d5efc 100644 --- a/multi-currency/src/Compatibility/WooCommercePreOrders.php +++ b/includes/multi-currency/Compatibility/WooCommercePreOrders.php @@ -20,7 +20,7 @@ class WooCommercePreOrders extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceProductAddOns.php b/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceProductAddOns.php rename to includes/multi-currency/Compatibility/WooCommerceProductAddOns.php index add583059f8..7d245cd4e4f 100644 --- a/multi-currency/src/Compatibility/WooCommerceProductAddOns.php +++ b/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php @@ -23,7 +23,7 @@ class WooCommerceProductAddOns extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceSubscriptions.php b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceSubscriptions.php rename to includes/multi-currency/Compatibility/WooCommerceSubscriptions.php index 6701f6739dd..94294827ea9 100644 --- a/multi-currency/src/Compatibility/WooCommerceSubscriptions.php +++ b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php @@ -7,8 +7,10 @@ namespace WCPay\MultiCurrency\Compatibility; +use WC_Payments_Explicit_Price_Formatter; +use WC_Payments_Features; use WC_Subscription; -use WCPay\MultiCurrency\Logger; +use WCPay\Logger; use WCPay\MultiCurrency\FrontendCurrencies; use WCPay\MultiCurrency\MultiCurrency; @@ -59,9 +61,9 @@ class WooCommerceSubscriptions extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { // Add needed actions and filters if WC Subscriptions or WCPay Subscriptions are active. - if ( class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Payments_Subscriptions' ) ) { + if ( class_exists( 'WC_Subscriptions' ) || WC_Payments_Features::is_wcpay_subscriptions_enabled() ) { if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) { $this->frontend_currencies = $this->multi_currency->get_frontend_currencies(); @@ -391,10 +393,6 @@ 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. @@ -402,15 +400,7 @@ 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(); - - // 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; + return WC_Payments_Explicit_Price_Formatter::get_explicit_price_with_currency( $html_price, $currency_code ); } /** diff --git a/multi-currency/src/Compatibility/WooCommerceUPS.php b/includes/multi-currency/Compatibility/WooCommerceUPS.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceUPS.php rename to includes/multi-currency/Compatibility/WooCommerceUPS.php index 427aa060d52..6a53f47bff3 100644 --- a/multi-currency/src/Compatibility/WooCommerceUPS.php +++ b/includes/multi-currency/Compatibility/WooCommerceUPS.php @@ -20,7 +20,7 @@ class WooCommerceUPS extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/includes/multi-currency/CountryFlags.php b/includes/multi-currency/CountryFlags.php new file mode 100644 index 00000000000..e841095255e --- /dev/null +++ b/includes/multi-currency/CountryFlags.php @@ -0,0 +1,304 @@ + '🇦🇩', + 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/multi-currency/src/Currency.php b/includes/multi-currency/Currency.php similarity index 89% rename from multi-currency/src/Currency.php rename to includes/multi-currency/Currency.php index 37b8fd17d12..0d9ae84da42 100644 --- a/multi-currency/src/Currency.php +++ b/includes/multi-currency/Currency.php @@ -7,7 +7,8 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; +use WC_Payments_Utils; defined( 'ABSPATH' ) || exit; @@ -66,21 +67,21 @@ class Currency implements \JsonSerializable { private $last_updated; /** - * Instance of MultiCurrencyLocalizationInterface. + * Instance of WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ private $localization_service; /** * Constructor. * - * @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. + * @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. */ - public function __construct( MultiCurrencyLocalizationInterface $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { + public function __construct( WC_Payments_Localization_Service $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { $this->localization_service = $localization_service; $this->code = $code; $this->rate = $rate; diff --git a/multi-currency/src/CurrencySwitcherBlock.php b/includes/multi-currency/CurrencySwitcherBlock.php similarity index 97% rename from multi-currency/src/CurrencySwitcherBlock.php rename to includes/multi-currency/CurrencySwitcherBlock.php index e13902c6ede..95d4762365c 100644 --- a/multi-currency/src/CurrencySwitcherBlock.php +++ b/includes/multi-currency/CurrencySwitcherBlock.php @@ -7,7 +7,9 @@ namespace WCPay\MultiCurrency; +use WC_Payments; use function http_build_query; +use function implode; use function urldecode; defined( 'ABSPATH' ) || exit; @@ -58,7 +60,7 @@ public function init_hooks() { */ public function init_block_widget() { // Automatically load dependencies and version. - $this->multi_currency->register_script_with_dependencies( 'woocommerce-payments/multi-currency-switcher', 'dist/multi-currency-switcher-block' ); + WC_Payments::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/multi-currency/src/CurrencySwitcherWidget.php b/includes/multi-currency/CurrencySwitcherWidget.php similarity index 100% rename from multi-currency/src/CurrencySwitcherWidget.php rename to includes/multi-currency/CurrencySwitcherWidget.php diff --git a/multi-currency/src/Exceptions/InvalidCurrencyException.php b/includes/multi-currency/Exceptions/InvalidCurrencyException.php similarity index 71% rename from multi-currency/src/Exceptions/InvalidCurrencyException.php rename to includes/multi-currency/Exceptions/InvalidCurrencyException.php index c3ec9046a27..454fa1c7383 100644 --- a/multi-currency/src/Exceptions/InvalidCurrencyException.php +++ b/includes/multi-currency/Exceptions/InvalidCurrencyException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use Exception; +use WCPay\Exceptions\Base_Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency is used. */ -class InvalidCurrencyException extends Exception {} +class InvalidCurrencyException extends Base_Exception {} diff --git a/multi-currency/src/Exceptions/InvalidCurrencyRateException.php b/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php similarity index 71% rename from multi-currency/src/Exceptions/InvalidCurrencyRateException.php rename to includes/multi-currency/Exceptions/InvalidCurrencyRateException.php index 6f0b5b2c007..e80cc0cb92d 100644 --- a/multi-currency/src/Exceptions/InvalidCurrencyRateException.php +++ b/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use Exception; +use WCPay\Exceptions\Base_Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency rate is used. */ -class InvalidCurrencyRateException extends Exception {} +class InvalidCurrencyRateException extends Base_Exception {} diff --git a/multi-currency/src/FrontendCurrencies.php b/includes/multi-currency/FrontendCurrencies.php similarity index 94% rename from multi-currency/src/FrontendCurrencies.php rename to includes/multi-currency/FrontendCurrencies.php index 065d0db24a8..da1342ac55a 100644 --- a/multi-currency/src/FrontendCurrencies.php +++ b/includes/multi-currency/FrontendCurrencies.php @@ -8,7 +8,7 @@ namespace WCPay\MultiCurrency; use WC_Order; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -24,9 +24,9 @@ class FrontendCurrencies { protected $multi_currency; /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; @@ -89,12 +89,12 @@ class FrontendCurrencies { /** * Constructor. * - * @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. + * @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. */ - public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service, Utils $utils, Compatibility $compatibility ) { + public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service, Utils $utils, Compatibility $compatibility ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; $this->utils = $utils; diff --git a/multi-currency/src/FrontendPrices.php b/includes/multi-currency/FrontendPrices.php similarity index 100% rename from multi-currency/src/FrontendPrices.php rename to includes/multi-currency/FrontendPrices.php diff --git a/multi-currency/src/Geolocation.php b/includes/multi-currency/Geolocation.php similarity index 84% rename from multi-currency/src/Geolocation.php rename to includes/multi-currency/Geolocation.php index 4f5da245958..5bd0b48b7db 100644 --- a/multi-currency/src/Geolocation.php +++ b/includes/multi-currency/Geolocation.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -16,18 +16,18 @@ */ class Geolocation { /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; /** * Constructor. * - * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. + * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. */ - public function __construct( MultiCurrencyLocalizationInterface $localization_service ) { + public function __construct( WC_Payments_Localization_Service $localization_service ) { $this->localization_service = $localization_service; } diff --git a/multi-currency/src/Helpers/OrderMetaHelper.md b/includes/multi-currency/Helpers/OrderMetaHelper.md similarity index 100% rename from multi-currency/src/Helpers/OrderMetaHelper.md rename to includes/multi-currency/Helpers/OrderMetaHelper.md diff --git a/multi-currency/src/Helpers/OrderMetaHelper.php b/includes/multi-currency/Helpers/OrderMetaHelper.php similarity index 95% rename from multi-currency/src/Helpers/OrderMetaHelper.php rename to includes/multi-currency/Helpers/OrderMetaHelper.php index 9315557617a..208fefc2c26 100644 --- a/multi-currency/src/Helpers/OrderMetaHelper.php +++ b/includes/multi-currency/Helpers/OrderMetaHelper.php @@ -7,9 +7,10 @@ namespace WCPay\MultiCurrency\Helpers; -use WCPay\MultiCurrency\BackendCurrencies; -use WCPay\MultiCurrency\Logger; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; +use WC_Payments_API_Client; +use WC_Payments_Utils; +use WCPay\Exceptions\API_Exception; +use WCPay\Logger; defined( 'ABSPATH' ) || exit; @@ -20,17 +21,10 @@ class OrderMetaHelper { /** * Client for making requests to the WooCommerce Payments API. * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $payments_api_client; - /** - * Backend currencies object. - * - * @var BackendCurrencies - */ - private $backend_currencies; - /** * Array of errors, if any. * @@ -41,12 +35,10 @@ class OrderMetaHelper { /** * Constructor. * - * @param MultiCurrencyApiClientInterface $payments_api_client Payments API client. - * @param BackendCurrencies $backend_currencies Backend currencies object. + * @param WC_Payments_API_Client $payments_api_client Payments API client. */ - public function __construct( MultiCurrencyApiClientInterface $payments_api_client, BackendCurrencies $backend_currencies ) { + public function __construct( WC_Payments_API_Client $payments_api_client ) { $this->payments_api_client = $payments_api_client; - $this->backend_currencies = $backend_currencies; } /** @@ -235,7 +227,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 ( \Exception $e ) { + } catch ( API_Exception $e ) { // Log the error returned. Logger::error( "Error when attempting to get intent ($intent_id):\n" . $e->getMessage() ); $intent_object = null; @@ -255,7 +247,7 @@ public function display_meta_box_content( $order ) { /** * Zero decimal currencies have a different conversion rate value. */ - if ( $this->backend_currencies->is_zero_decimal_currency( $order_currency ) ) { + if ( in_array( strtolower( $order_currency ), WC_Payments_Utils::zero_decimal_currencies(), true ) ) { 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/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php similarity index 90% rename from multi-currency/src/MultiCurrency.php rename to includes/multi-currency/MultiCurrency.php index 4caab08a113..6482d34fb2e 100644 --- a/multi-currency/src/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -7,16 +7,20 @@ 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; @@ -37,6 +41,13 @@ 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 * @@ -129,32 +140,32 @@ class MultiCurrency { protected $enabled_currencies; /** - * Client for making requests to the API + * Client for making requests to the WooCommerce Payments API * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $payments_api_client; /** - * Instance of MultiCurrencyAccountInterface. + * Instance of WC_Payments_Account. * - * @var MultiCurrencyAccountInterface + * @var WC_Payments_Account */ private $payments_account; /** - * Instance of MultiCurrencyLocalizationInterface. + * Instance of WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ private $localization_service; /** - * Instance of MultiCurrencyCacheInterface. + * Instance of Database_Cache. * - * @var MultiCurrencyCacheInterface + * @var Database_Cache */ - private $cache; + private $database_cache; /** * Tracking instance. @@ -178,28 +189,35 @@ class MultiCurrency { private $order_meta_helper; /** - * Gateway context. + * Main MultiCurrency Instance. * - * @var array + * Ensures only one instance of MultiCurrency is loaded or can be loaded. + * + * @static + * @return MultiCurrency - Main instance. */ - public $gateway_context; + 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; + } /** * Class constructor. * - * @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. + * @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. */ - 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; + 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 ) { $this->payments_api_client = $payments_api_client; $this->payments_account = $payments_account; $this->localization_service = $localization_service; - $this->cache = $cache; + $this->database_cache = $database_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 ); @@ -224,9 +242,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' ) && ! Utils::is_admin_api_request(); + $is_frontend_request = ! is_admin() && ! defined( 'DOING_CRON' ) && ! WC()->is_rest_api_request(); - if ( $is_frontend_request || Utils::is_store_api_request() ) { + if ( $is_frontend_request || \WC_Payments_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 ); @@ -262,17 +280,19 @@ public function init() { $this->update_manual_rate_currencies_notice_option(); } - $admin_notices = new AdminNotices(); - $user_settings = new UserSettings( $this ); + $payment_method_compat = new PaymentMethodsCompatibility( $this, WC_Payments::get_gateway() ); + $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->backend_currencies ); + $this->order_meta_helper = new OrderMetaHelper( $this->payments_api_client ); // Init all of the hooks. + $payment_method_compat->init_hooks(); $admin_notices->init_hooks(); $user_settings->init_hooks(); $this->frontend_prices->init_hooks(); @@ -290,7 +310,7 @@ public function init() { } if ( is_admin() ) { - add_action( 'admin_init', [ $this, 'add_woo_admin_notes' ] ); + add_action( 'admin_init', [ __CLASS__, 'add_woo_admin_notes' ] ); } // Update the customer currencies option after an order status change. @@ -312,7 +332,7 @@ public function init_rest_api() { return; } - $api_controller = new RestController(); + $api_controller = new RestController( \WC_Payments::create_api_client() ); $api_controller->register_routes(); } @@ -335,19 +355,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 the payment provider is connected for the + // We don't need to check if Stripe 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_provider_connected() ) { + if ( $this->payments_account->is_stripe_connected() ) { $settings = new Settings( $this ); $settings->init_hooks(); $settings_pages[] = $settings; } else { - $settings_onboard_cta = new SettingsOnboardCta( $this, $this->payments_account ); + $settings_onboard_cta = new SettingsOnboardCta( $this ); $settings_onboard_cta->init_hooks(); $settings_pages[] = $settings_onboard_cta; @@ -372,7 +392,7 @@ public function enqueue_admin_scripts() { $this->register_admin_scripts(); wp_enqueue_script( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); - wp_enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); + WC_Payments_Utils::enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); } /** @@ -395,7 +415,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->cache->delete( MultiCurrencyCacheInterface::CURRENCIES_KEY ); + $this->database_cache->delete( Database_Cache::CURRENCIES_KEY ); } /** @@ -406,14 +426,14 @@ public function clear_cache() { * @return ?array */ public function get_cached_currencies() { - $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() ) { + $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() ) { return $cached_data ?? null; } - return $this->cache->get_or_add( - MultiCurrencyCacheInterface::CURRENCIES_KEY, + return $this->database_cache->get_or_add( + Database_Cache::CURRENCIES_KEY, function () { try { $currency_data = $this->payments_api_client->get_currency_rates( strtolower( get_woocommerce_currency() ) ); @@ -421,7 +441,7 @@ function () { 'currencies' => $currency_data, 'updated' => time(), ]; - } catch ( \Exception $e ) { + } catch ( API_Exception $e ) { return null; } }, @@ -563,7 +583,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 ), 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); } update_option( 'wcpay_multi_currency_manual_rate_' . $currency_code, $manual_rate ); } @@ -601,6 +621,94 @@ 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. * @@ -855,7 +963,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 ), 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); } $amount = $amount * ( $to_currency_rate / $from_currency_rate ); @@ -931,20 +1039,24 @@ public function display_geolocation_currency_update_notice() { } $message = sprintf( - /* 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' ), + /* 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' ), 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() ), - esc_html( $currencies[ $store_currency ] ), - esc_url( '?currency=' . $store_currency ) + $currencies[ $store_currency ] ); $notice_id = md5( $message ); echo ''; - // No need to escape here as the contents of $message is already escaped. + // No need to escape here as the function called handles it. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo $message; + echo \WC_Payments_Utils::esc_interpolated_html( + $message, + [ + 'a' => '', + ] + ); echo ' ' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . ''; } @@ -968,14 +1080,14 @@ public function set_new_customer_currency_meta( $customer_id ) { * * @return void */ - public function add_woo_admin_notes() { + public static 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( $this->payments_account ); + NoteMultiCurrencyAvailable::set_account( WC_Payments::get_account_service() ); NoteMultiCurrencyAvailable::possibly_add_note(); } } @@ -992,138 +1104,292 @@ public static function remove_woo_admin_notes() { } /** - * Checks if the merchant has enabled automatic currency switching and geolocation. + * Gets the price after adjusting it with the rounding and charm settings. * - * @return bool + * @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. + * + * @return float The adjusted price. */ - public function is_using_auto_currency_switching(): bool { - return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' ); + 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 ); } /** - * Checks if the merchant has enabled the currency switcher widget. + * Ceils the price to the next number based on the rounding value. * - * @return bool + * @param float $price The price to be ceiled. + * @param float $rounding The rounding option. + * + * @return float The ceiled price. */ - public function is_using_storefront_switcher(): bool { - return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' ); + protected function ceil_price( float $price, float $rounding ): float { + if ( 0.00 === $rounding ) { + return $price; + } + return ceil( $price / $rounding ) * $rounding; } /** - * Gets the store settings. + * Returns the currency code stored for the user or in the session. * - * @return array The store settings. + * @return string|null Currency code. */ - 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' ) ) ), - ]; + 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; } /** - * Updates the store settings - * - * @param array $params Update requested values. + * Checks to see if the store currency has changed. If it has, this will + * also update the option containing the store currency. * - * @return void + * @return bool */ - public function update_settings( $params ) { - $updateable_options = [ - 'wcpay_multi_currency_enable_auto_currency', - 'wcpay_multi_currency_enable_storefront_switcher', - ]; + private function check_store_currency_for_change(): bool { + $last_known_currency = get_option( $this->id . '_store_currency', false ); + $woocommerce_currency = get_woocommerce_currency(); - foreach ( $updateable_options as $key ) { - if ( isset( $params[ $key ] ) ) { - update_option( $key, sanitize_text_field( $params[ $key ] ) ); - } + // 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; } /** - * Apply client order currency format and reduces the rounding precision to 2. + * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display. * - * @return void + * @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; + 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(); } - $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(); - ?> - - id . '_show_store_currency_changed_notice', $manual_currencies ); + } } /** - * Load script with all required dependencies. + * Accepts an array of currencies that should have their settings removed. * - * @param string $handler Script handler. - * @param string $script Script name relative to the plugin root. - * @param array $additional_dependencies Additional dependencies. + * @param array $currencies Array of Currency objects or 3 letter currency codes. * * @return void */ - 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 - ); + private function remove_currencies_settings( array $currencies ) { + + foreach ( $currencies as $currency ) { + $this->remove_currency_settings( $currency ); + } } /** - * Get the file modified time as a cache buster if we're in dev mode. + * Will remove a currency's settings if it is not enabled. * - * @param string $file Local path to the file. + * @param mixed $currency Currency object or 3 letter currency code. * - * @return string + * @return void */ - public function get_file_version( $file ) { - $plugin_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ); + private function remove_currency_settings( $currency ) { + $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency ); - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) { - return (string) filemtime( $plugin_path . trim( $file, '/' ) ); + // 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; } - return $this->gateway_context['plugin_version']; + $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 ) ); + } } /** - * Validates the given currency code. + * Returns the currencies enabled for the Stripe account that are + * also available in WC. * - * @param string $currency_code The currency code to check validity. + * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook. * - * @return string|false Returns back the currency code in uppercase letters if it's valid, or `false` if not. + * @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 ) @@ -1147,6 +1413,52 @@ 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 * @@ -1208,6 +1520,45 @@ 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_provider_connected() ) { + if ( is_null( self::$account ) || ! self::$account->is_stripe_connected() ) { return; } @@ -80,9 +80,9 @@ public static function possibly_add_note() { /** * Sets the account service instance reference on the class. * - * @param MultiCurrencyAccountInterface $account account service instance. + * @param WC_Payments_Account $account account service instance. */ - public static function set_account( MultiCurrencyAccountInterface $account ) { + public static function set_account( WC_Payments_Account $account ) { self::$account = $account; } } diff --git a/includes/compat/multi-currency/class-wc-payments-currency-manager.php b/includes/multi-currency/PaymentMethodsCompatibility.php similarity index 69% rename from includes/compat/multi-currency/class-wc-payments-currency-manager.php rename to includes/multi-currency/PaymentMethodsCompatibility.php index c00da0d1a69..0c5f7d4b736 100644 --- a/includes/compat/multi-currency/class-wc-payments-currency-manager.php +++ b/includes/multi-currency/PaymentMethodsCompatibility.php @@ -1,21 +1,29 @@ gateway = $gateway; + public function __construct( MultiCurrency $multi_currency, WC_Payment_Gateway_WCPay $gateway ) { + $this->multi_currency = $multi_currency; + $this->gateway = $gateway; } /** @@ -38,32 +48,17 @@ public function __construct( WC_Payment_Gateway_WCPay $gateway ) { * @return void */ public function init_hooks() { - add_action( 'update_option_woocommerce_woocommerce_payments_settings', [ $this, 'maybe_add_missing_currencies' ] ); + add_action( + 'update_option_woocommerce_woocommerce_payments_settings', + [ $this, '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(); @@ -102,19 +97,14 @@ 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 maybe_add_missing_currencies() { - $multi_currency = $this->get_multi_currency_instance(); - if ( is_null( $multi_currency ) ) { - return; - } - + public function add_missing_currencies() { $payment_methods_needing_currency = $this->get_enabled_payment_method_currencies(); if ( empty( $payment_methods_needing_currency ) ) { return; } - $enabled_currencies = $multi_currency->get_enabled_currencies(); - $available_currencies = $multi_currency->get_available_currencies(); + $enabled_currencies = $this->multi_currency->get_enabled_currencies(); + $available_currencies = $this->multi_currency->get_available_currencies(); $missing_currency_codes = []; @@ -147,21 +137,15 @@ public function maybe_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. */ - $multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); + $this->multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); } /** - * Adds the `multiCurrencyPaymentMethodsMap` JS object to the multi-currency settings page. + * Adds the notices for currencies that are bound to an UPE payment method. * - * This object maps currencies to payment methods that require them, so the multi-currency settings page displays a notice in case of dependencies. + * @return void */ 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; @@ -177,10 +161,11 @@ public function add_payment_method_currency_dependencies_script() { } } - ?> + if ( WC_Payments_Multi_Currency()->is_multi_currency_settings_page() ) : ?> - set_enabled_currencies( $enabled ); $response = $this->get_store_currencies(); } catch ( InvalidCurrencyException $e ) { - $response = new \WP_Error( $e->getCode(), $e->getMessage() ); + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); } @@ -185,7 +178,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->getCode(), $e->getMessage() ); + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -208,8 +201,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 ( Exception $e ) { - $response = new \WP_Error( $e->getCode(), $e->getMessage() ); + } catch ( Base_Exception $e ) { + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -236,11 +229,4 @@ 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/multi-currency/src/Settings.php b/includes/multi-currency/Settings.php similarity index 100% rename from multi-currency/src/Settings.php rename to includes/multi-currency/Settings.php diff --git a/multi-currency/src/SettingsOnboardCta.php b/includes/multi-currency/SettingsOnboardCta.php similarity index 74% rename from multi-currency/src/SettingsOnboardCta.php rename to includes/multi-currency/SettingsOnboardCta.php index 59a2eee286f..ee89ea5638c 100644 --- a/multi-currency/src/SettingsOnboardCta.php +++ b/includes/multi-currency/SettingsOnboardCta.php @@ -7,8 +7,6 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; - defined( 'ABSPATH' ) || exit; /** @@ -29,24 +27,15 @@ 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 MultiCurrencyAccountInterface $payments_account Payments Account instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. */ - 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' ); + 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' ); parent::__construct(); } @@ -64,7 +53,7 @@ public function init_hooks() { * Output the call to action button if needing to onboard. */ public function currencies_settings_onboarding_cta() { - $href = $this->payments_account->get_provider_onboarding_page_url(); + $href = \WC_Payments_Account::get_connect_page_url(); ?> diff --git a/multi-currency/src/StorefrontIntegration.php b/includes/multi-currency/StorefrontIntegration.php similarity index 100% rename from multi-currency/src/StorefrontIntegration.php rename to includes/multi-currency/StorefrontIntegration.php diff --git a/multi-currency/src/Tracking.php b/includes/multi-currency/Tracking.php similarity index 100% rename from multi-currency/src/Tracking.php rename to includes/multi-currency/Tracking.php diff --git a/multi-currency/src/UserSettings.php b/includes/multi-currency/UserSettings.php similarity index 100% rename from multi-currency/src/UserSettings.php rename to includes/multi-currency/UserSettings.php diff --git a/multi-currency/src/Utils.php b/includes/multi-currency/Utils.php similarity index 71% rename from multi-currency/src/Utils.php rename to includes/multi-currency/Utils.php index 1c3fd074076..64e5356a77a 100644 --- a/multi-currency/src/Utils.php +++ b/includes/multi-currency/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() && ! self::is_store_api_request(); + return 0 === stripos( wp_get_referer(), admin_url() ) && WC()->is_rest_api_request() && ! \WC_Payments_Utils::is_store_api_request(); } @@ -71,21 +71,4 @@ 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/includes/compat/multi-currency/wc-payments-multi-currency.php b/includes/multi-currency/wc-payments-multi-currency.php similarity index 80% rename from includes/compat/multi-currency/wc-payments-multi-currency.php rename to includes/multi-currency/wc-payments-multi-currency.php index 0d0775e7d62..fef6e10db81 100644 --- a/includes/compat/multi-currency/wc-payments-multi-currency.php +++ b/includes/multi-currency/wc-payments-multi-currency.php @@ -29,25 +29,12 @@ function wcpay_multi_currency_onboarding_check() { } /** - * Returns the MultiCurrency singleton. + * Returns the main instance of MultiCurrency. * * @return WCPay\MultiCurrency\MultiCurrency */ function WC_Payments_Multi_Currency() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid - static $instance = null; - - if ( is_null( $instance ) ) { - $instance = new WCPay\MultiCurrency\MultiCurrency( - WC_Payments::get_context_for_multi_currency(), - WC_Payments::get_payments_api_client(), - WC_Payments::get_account_service(), - WC_Payments::get_localization_service(), - WC_Payments::get_database_cache() - ); - $instance->init_hooks(); - } - - return $instance; + return WCPay\MultiCurrency\MultiCurrency::instance(); } add_action( 'plugins_loaded', 'WC_Payments_Multi_Currency', 12 ); 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 13b25e07dd6..50aabec994c 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -21,12 +21,11 @@ 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 implements MultiCurrencyApiClientInterface { +class WC_Payments_API_Client { const ENDPOINT_BASE = 'https://public-api.wordpress.com/wpcom/v2'; const ENDPOINT_SITE_FRAGMENT = 'sites/%s'; @@ -192,7 +191,7 @@ public function __construct( $user_agent, $http_client, $wcpay_db ) { * * @return bool */ - public function is_server_connected(): bool { + public function is_server_connected() { return $this->http_client->is_connected(); } @@ -858,7 +857,7 @@ function ( $a, $b ) { * * @throws API_Exception - Error contacting the API. */ - public function get_currency_rates( string $currency_from, $currencies_to = null ): array { + public function get_currency_rates( string $currency_from, $currencies_to = null ) { if ( empty( $currency_from ) ) { throw new API_Exception( __( 'Currency From parameter is required', 'woocommerce-payments' ), diff --git a/multi-currency/client/data/constants.js b/multi-currency/client/data/constants.js deleted file mode 100644 index eaee38c6ee6..00000000000 --- a/multi-currency/client/data/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @format */ - -export const NAMESPACE = '/wc/v3/payments'; -export const STORE_NAME = 'wc/payments/multi-currency'; diff --git a/multi-currency/client/data/index.ts b/multi-currency/client/data/index.ts deleted file mode 100644 index f1d8d84a456..00000000000 --- a/multi-currency/client/data/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** @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/multi-currency/client/data/store.js b/multi-currency/client/data/store.js deleted file mode 100644 index 2e142e66f9c..00000000000 --- a/multi-currency/client/data/store.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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/multi-currency/client/interface/assets.js b/multi-currency/client/interface/assets.js deleted file mode 100644 index 3a3a095891a..00000000000 --- a/multi-currency/client/interface/assets.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 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 deleted file mode 100644 index eddab5ae84e..00000000000 --- a/multi-currency/client/interface/components.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 deleted file mode 100644 index af162ddd6ed..00000000000 --- a/multi-currency/client/interface/data.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 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 deleted file mode 100644 index 5ffed26e00d..00000000000 --- a/multi-currency/client/interface/functions.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 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/multi-currency/src/CountryFlags.php b/multi-currency/src/CountryFlags.php deleted file mode 100644 index 286e77dfbe2..00000000000 --- a/multi-currency/src/CountryFlags.php +++ /dev/null @@ -1,301 +0,0 @@ - '🇦🇩', - '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/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php b/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php deleted file mode 100644 index b827c5ca3d8..00000000000 --- a/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php +++ /dev/null @@ -1,61 +0,0 @@ -log( $level, $message, [ 'source' => self::LOG_FILE ] ); - } -} diff --git a/package-lock.json b/package-lock.json index fb0a1965e2a..0c5635e6817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index fd6d97fe3f9..5b525252554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index a0bf916f7a9..6e21606d2bb 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -102,7 +102,7 @@ tests/* - multi-currency/src + includes/multi-currency src diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1eb5fab3f04..2a5164e8af3 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/readme.txt b/readme.txt index c4f1b121e3c..219da74fc9a 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.6 Requires PHP: 7.3 -Stable tag: 8.2.1 +Stable tag: 8.2.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,10 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 8.2.2 - 2024-09-24 = +* Fix - Fix WooPay pre-checking place order bug when buying a subscription. + + = 8.2.1 - 2024-09-13 = * Fix - Create div container element with JS dynamically. diff --git a/tasks/release.js b/tasks/release.js index 72c3eccdef7..92559af4ffb 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -14,11 +14,11 @@ const targetFolder = 'release/' + pluginSlug; const filesToCopy = [ 'assets', 'dist', - 'i18n', 'includes', + 'i18n', 'languages', - 'lib', 'src', + 'lib', 'templates', 'vendor', 'woocommerce-payments.php', @@ -43,10 +43,6 @@ 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/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', - } ); - } ); -} ); 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"]' + ); } ); } ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 03060196d4c..18c11bc9553 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -2,18 +2,13 @@ const { jsWithBabel: tsjPreset } = require( 'ts-jest/presets' ); module.exports = { rootDir: '../../', - moduleDirectories: [ - 'node_modules', - '/client', - '/multi-currency/client', - ], + moduleDirectories: [ 'node_modules', '/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/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index d84f4b680ba..d68c5c1f82e 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 @@ -377,11 +377,13 @@ public function test_update_settings_saves_enabled_payment_methods() { WC_Payments::get_gateway()->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); $request = new WP_REST_Request(); + + // Ideal will not have the capability status, 'active', and will therefore not added to the list of enabled payment methods. $request->set_param( 'enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::IDEAL ] ); $this->controller->update_settings( $request ); - $this->assertEquals( [ Payment_Method::CARD, Payment_Method::IDEAL ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); + $this->assertEquals( [ Payment_Method::CARD ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); } public function test_update_settings_fails_if_user_cannot_manage_woocommerce() { @@ -550,7 +552,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/multi-currency/compatibility/test-class-woocommerce-subscriptions.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php index 5edd60cd8a6..5b71b8f8e7f 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php @@ -6,10 +6,6 @@ */ 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; @@ -66,16 +62,7 @@ class WCPay_Multi_Currency_WooCommerceSubscriptions_Tests extends WCPAY_UnitTest public function set_up() { parent::set_up(); - $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_multi_currency = $this->createMock( MultiCurrency::class ); $this->mock_utils = $this->createMock( Utils::class ); $this->woocommerce_subscriptions = new WooCommerceSubscriptions( $this->mock_multi_currency, $this->mock_utils ); @@ -841,11 +828,18 @@ 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 e434ae9f661..5a985dd0511 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,7 +6,6 @@ */ use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; /** * Class Note_Multi_Currency_Available_Test tests. @@ -56,8 +55,8 @@ public function test_possibly_add_note_without_account() { } public function test_possibly_add_note_with_account_not_connected() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( false ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_connected' )->willReturn( false ); NoteMultiCurrencyAvailable::set_account( $account_mock ); NoteMultiCurrencyAvailable::possibly_add_note(); @@ -66,8 +65,8 @@ public function test_possibly_add_note_with_account_not_connected() { } public function test_possibly_add_note_with_connected_account() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( true ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_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 02c719ca64b..00b46e2d7ef 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 ); - $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 = $this->createMock( MultiCurrency::class ); $this->mock_multi_currency->expects( $this->any() ) ->method( 'get_all_customer_currencies' ) @@ -98,10 +84,7 @@ public function set_up() { $this->analytics = new Analytics( $this->mock_multi_currency ); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); - $this->mock_localization_service->expects( $this->any() ) - ->method( 'get_currency_format' ) - ->willReturn( [ 'num_decimals' => 2 ] ); + $this->localization_service = new WC_Payments_Localization_Service(); remove_filter( 'user_has_cap', $cb ); } @@ -181,7 +164,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 14.00 ); $order = wc_create_order(); @@ -196,7 +179,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 130500.75, 20000, 10000, 100500.75 ); $order = wc_create_order(); @@ -211,7 +194,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 15.00 ); $order = wc_create_order(); @@ -604,19 +587,14 @@ private function create_can_manage_woocommerce_cap_override( bool $can_manage_wo } private function get_mock_available_currencies() { - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->localization_service = new WC_Payments_Localization_Service(); 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->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 ), + '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 ), ]; } diff --git a/tests/unit/multi-currency/test-class-backend-currencies.php b/tests/unit/multi-currency/test-class-backend-currencies.php index 8b333c6f189..530efc71b7d 100644 --- a/tests/unit/multi-currency/test-class-backend-currencies.php +++ b/tests/unit/multi-currency/test-class-backend-currencies.php @@ -6,7 +6,6 @@ */ use WCPay\MultiCurrency\BackendCurrencies; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; /** @@ -14,9 +13,9 @@ */ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { /** - * Mock MultiCurrencyLocalizationInterface. + * Mock WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface|PHPUnit_Framework_MockObject_MockObject + * @var WC_Payments_Localization_Service|PHPUnit_Framework_MockObject_MockObject */ private $mock_localization_service; @@ -37,7 +36,7 @@ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::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 ae4cc75dc31..cefb478ca50 100644 --- a/tests/unit/multi-currency/test-class-compatibility.php +++ b/tests/unit/multi-currency/test-class-compatibility.php @@ -7,7 +7,6 @@ use WCPay\MultiCurrency\Compatibility; use WCPay\MultiCurrency\Currency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Utils; @@ -37,11 +36,11 @@ class WCPay_Multi_Currency_Compatibility_Tests extends WCPAY_UnitTestCase { private $mock_utils; /** - * MultiCurrencyLocalizationInterface. + * WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $localization_service; /** * Pre-test setup @@ -49,15 +48,10 @@ 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->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 ); + $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(); } public function test_init_compatibility_classes_does_not_add_classes_if_one_enabled_currencies() { @@ -114,7 +108,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -138,7 +132,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -159,7 +153,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -183,7 +177,7 @@ public function test_filter_woocommerce_order_query() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->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 cd3adff8feb..0ebe63fb8ef 100644 --- a/tests/unit/multi-currency/test-class-country-flags.php +++ b/tests/unit/multi-currency/test-class-country-flags.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\CountryFlags; /** @@ -12,7 +13,7 @@ */ class Country_Flags_Test extends WCPAY_UnitTestCase { public function test_get_by_country_returns_emoji_flag() { - $this->assertEquals( CountryFlags::get_by_country( 'US' ), '🇺🇸' ); + $this->assertEquals( CountryFlags::get_by_country( Country_Code::UNITED_STATES ), '🇺🇸' ); } 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 2ac0a69fa11..f716f6dbe75 100644 --- a/tests/unit/multi-currency/test-class-currency-switcher-block.php +++ b/tests/unit/multi-currency/test-class-currency-switcher-block.php @@ -10,7 +10,6 @@ use WCPay\MultiCurrency\Currency; use WCPay\MultiCurrency\CurrencySwitcherBlock; use WCPay\MultiCurrency\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; /** * CurrencySwitcherBlock unit tests. @@ -38,25 +37,23 @@ class WCPay_Multi_Currency_Currency_Switcher_Block_Tests extends WCPAY_UnitTestC protected $mock_currencies; /** - * @var MockObject\MultiCurrencyLocalizationInterface + * WC_Payments_Localization_Service. + * + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $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->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->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->currency_switcher_block = new CurrencySwitcherBlock( @@ -222,8 +219,8 @@ public function test_render_currency_option_will_escape_output() { ->method( 'get_enabled_currencies' ) ->willReturn( [ - new Currency( $this->mock_localization_service, 'USD' ), - new Currency( $this->mock_localization_service, $currency_code, 1 ), + new Currency( $this->localization_service, 'USD' ), + new Currency( $this->localization_service, $currency_code, 1 ), ] ); @@ -278,7 +275,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->mock_localization_service, 'USD' ) ] ); + ->willReturn( [ new Currency( $this->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 13d6b4bb34d..c38d343dcc9 100644 --- a/tests/unit/multi-currency/test-class-frontend-prices.php +++ b/tests/unit/multi-currency/test-class-frontend-prices.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\FrontendPrices unit tests. */ @@ -214,7 +216,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; @@ -258,7 +260,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, '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 92b0b50b166..b66543dbf67 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\Geolocation unit tests. */ @@ -49,10 +51,10 @@ public function test_get_country_by_customer_location_returns_geolocation_countr add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); - $this->assertSame( 'CA', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::CANADA, $this->geolocation->get_country_by_customer_location() ); } public function test_get_country_by_customer_location_returns_default_country_when_no_geolocation() { @@ -66,20 +68,20 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); - $this->assertSame( 'BR', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::BRAZIL, $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( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -87,7 +89,7 @@ function () { } public function test_get_currency_by_customer_location_returns_default_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'BR' )->willReturn( [ 'currency_code' => 'BRL' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::BRAZIL )->willReturn( [ 'currency_code' => 'BRL' ] ); add_filter( 'woocommerce_geolocate_ip', @@ -98,7 +100,7 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index 70487b835ee..023fd92868c 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -5,12 +5,11 @@ * @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; @@ -66,14 +65,14 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { /** * Mock of the API client. * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $mock_api_client; /** - * Mock of the MultiCurrencyAccountInterface. + * Mock of the WC_Payments_Account. * - * @var MultiCurrencyAccountInterface + * @var WC_Payments_Account */ private $mock_account; @@ -85,11 +84,11 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { private $localization_service; /** - * Mock of MultiCurrencyCacheInterface. + * Mock of Database_Cache. * - * @var MultiCurrencyCacheInterface; + * @var Database_Cache; */ - private $mock_cache; + private $mock_database_cache; /** * Mock of Utils. @@ -457,7 +456,7 @@ public function test_update_selected_currency_by_geolocation_does_not_set_sessio add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -474,7 +473,7 @@ public function test_update_selected_currency_by_geolocation_updates_session_whe add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -489,7 +488,7 @@ public function test_update_selected_currency_by_geolocation_displays_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -510,7 +509,7 @@ public function test_update_selected_currency_by_geolocation_does_not_update_if_ add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -535,7 +534,7 @@ public function test_display_geolocation_currency_update_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -545,11 +544,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, 'US' ); + WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, Country_Code::UNITED_STATES ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -563,7 +562,7 @@ public function test_display_geolocation_currency_update_notice_does_not_display add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -722,13 +721,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( MultiCurrencyApiClientInterface::class ); + $mock_api_client = $this->createMock( WC_Payments_API_Client::class ); $mock_api_client->method( 'is_server_connected' )->willReturn( false ); $this->init_multi_currency( $mock_api_client ); - $this->mock_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, @@ -737,7 +736,7 @@ public function test_get_cached_currencies_with_no_server_connection() { } public function test_get_cached_currencies_with_account_rejected() { - $this->mock_cache + $this->mock_database_cache ->expects( $this->once() ) ->method( 'get' ) ->willReturn( null ); @@ -747,7 +746,7 @@ public function test_get_cached_currencies_with_account_rejected() { ->method( 'is_account_rejected' ) ->willReturn( true ); - $this->mock_cache + $this->mock_database_cache ->expects( $this->never() ) ->method( 'get_or_add' ); @@ -759,11 +758,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->exactly( 2 ) ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturnCallback( function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { if ( 1 === $get_or_add_call_count ) { @@ -777,7 +776,7 @@ function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { } ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $currency_from = strtolower( get_woocommerce_currency() ); $currencies_to = get_woocommerce_currencies(); @@ -887,7 +886,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_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, $this->multi_currency->get_cached_currencies() @@ -1052,14 +1051,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1074,14 +1073,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1104,14 +1103,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1415,32 +1414,31 @@ 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_cache = null ) { - $this->mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); + 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 ); - $this->mock_account = $mock_account ?? $this->createMock( MultiCurrencyAccountInterface::class ); - $this->mock_account->method( 'is_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account = $mock_account ?? $this->createMock( WC_Payments_Account::class ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_api_client->method( 'is_server_connected' )->willReturn( true ); - $this->mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $this->mock_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache = $this->createMock( Database_Cache::class ); + $this->mock_database_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_cache ?? $this->mock_cache, + $mock_database_cache ?? $this->mock_database_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/test-class-wc-payments-currency-manager.php b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php similarity index 84% rename from tests/unit/test-class-wc-payments-currency-manager.php rename to tests/unit/multi-currency/test-class-payment-methods-compatibility.php index 6f13664ec75..bdbbb767cde 100644 --- a/tests/unit/test-class-wc-payments-currency-manager.php +++ b/tests/unit/multi-currency/test-class-payment-methods-compatibility.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( [ @@ -67,16 +68,8 @@ public function set_up() { ->getMock(); $this->gateway_mock->method( 'get_account_country' )->willReturn( 'US' ); - $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->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); + $this->payment_methods_compatibility->init_hooks(); $this->localization_service = new WC_Payments_Localization_Service(); } @@ -86,7 +79,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_when_not_needed() { @@ -114,7 +107,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_when_needed() { @@ -154,7 +147,7 @@ public function test_it_should_update_available_currencies_when_needed() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_with_bnpl_methods() { @@ -180,7 +173,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_with_bnpl_methods() { @@ -214,6 +207,6 @@ public function test_it_should_update_available_currencies_with_bnpl_methods() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } } diff --git a/tests/unit/multi-currency/test-class-rest-controller.php b/tests/unit/multi-currency/test-class-rest-controller.php index dfe581cde25..49ac60ad1be 100644 --- a/tests/unit/multi-currency/test-class-rest-controller.php +++ b/tests/unit/multi-currency/test-class-rest-controller.php @@ -5,7 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; use WCPay\MultiCurrency\RestController; /** @@ -34,7 +33,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( MultiCurrencyApiClientInterface::class )->disableOriginalConstructor()->getMock(); + $mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )->disableOriginalConstructor()->getMock(); $this->controller = new RestController( $mock_api_client ); } @@ -83,8 +82,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/update-enabled-currencies' ); @@ -129,8 +129,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'GET', self::ROUTE . '/currencies/AAA' ); @@ -190,8 +191,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/currencies/AAA' ); @@ -218,8 +220,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $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 272a691910b..1ccbd373a0c 100644 --- a/tests/unit/multi-currency/test-class-settings.php +++ b/tests/unit/multi-currency/test-class-settings.php @@ -5,6 +5,8 @@ * @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 553df2a0cc1..1f3c9cce762 100644 --- a/tests/unit/multi-currency/test-class-utils.php +++ b/tests/unit/multi-currency/test-class-utils.php @@ -60,7 +60,8 @@ 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/'; - $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ) . 'wc/store/v1/checkout'; + $_REQUEST['rest_route'] = '/wc/store/v1/checkout'; + $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ); $this->assertFalse( $this->utils->is_admin_api_request() ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index e14803b6812..b97fad22eea 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -2789,6 +2789,7 @@ function ( $order ) { } public function test_gateway_enabled_when_payment_method_is_enabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2797,6 +2798,7 @@ public function test_gateway_enabled_when_payment_method_is_enabled() { } public function test_gateway_disabled_when_payment_method_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2804,6 +2806,15 @@ public function test_gateway_disabled_when_payment_method_is_disabled() { $this->assertFalse( $afterpay->is_available() ); } + public function test_gateway_disabled_when_card_gateway_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'no' ); + $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); + $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); + $this->prepare_gateway_for_availability_testing( $afterpay ); + + $this->assertFalse( $afterpay->is_available() ); + } + public function test_process_payment_for_order_cc_payment_method() { $payment_method = 'woocommerce_payments'; $expected_upe_payment_method_for_pi_creation = 'card'; @@ -3943,7 +3954,7 @@ private function create_charge_object() { private function prepare_gateway_for_availability_testing( $gateway ) { WC_Payments::mode()->test(); $current_currency = strtolower( get_woocommerce_currency() ); - $this->mock_wcpay_account->expects( $this->once() )->method( 'get_account_customer_supported_currencies' )->will( + $this->mock_wcpay_account->expects( $this->any() )->method( 'get_account_customer_supported_currencies' )->will( $this->returnValue( [ $current_currency, 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-explicit-price-formatter.php b/tests/unit/test-class-wc-payments-explicit-price-formatter.php index b01ce3092d2..9c32f3afdb5 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_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); @@ -248,10 +248,7 @@ 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 ); - $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 = new MultiCurrency( $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/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-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( 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', diff --git a/tsconfig.json b/tsconfig.json index f7f56cddd52..3b9efabf45e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,8 @@ "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 55c249694ea..7ef039967cd 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': './multi-currency/client/index.js', + 'multi-currency': './client/multi-currency/index.js', 'multi-currency-switcher-block': - './multi-currency/client/blocks/currency-switcher.js', + './client/multi-currency/blocks/currency-switcher.js', 'multi-currency-analytics': - './multi-currency/client/analytics/index.js', + './client/multi-currency-analytics/index.js', order: './client/order/index.js', 'subscriptions-empty-state': './client/subscriptions-empty-state/index.js', @@ -113,18 +113,9 @@ module.exports = { }, resolve: { extensions: [ '.ts', '.tsx', '.json', '.js', '.jsx' ], - modules: [ - path.join( process.cwd(), 'client' ), - path.join( process.cwd(), 'multi-currency', 'client' ), - 'node_modules', - ], + modules: [ path.join( process.cwd(), '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(), diff --git a/woocommerce-payments.php b/woocommerce-payments.php index c079725904c..825bb4c8f18 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 9.3.1 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 8.2.1 + * Version: 8.2.2 * Requires Plugins: woocommerce * * @package WooCommerce\Payments
{ __( diff --git a/composer.json b/composer.json index 497530e36e1..cdb679b4afb 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "autoload": { "psr-4": { - "WCPay\\MultiCurrency\\": "multi-currency/src", + "WCPay\\MultiCurrency\\": "includes/multi-currency", "WCPay\\Vendor\\": "lib/packages", "WCPay\\": "src" }, 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/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index 90fe8721443..dbdc4abd522 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Admin */ +use WCPay\Constants\Payment_Method; use WCPay\Constants\Country_Code; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; use WCPay\Constants\Track_Events; @@ -603,6 +604,11 @@ private function update_enabled_payment_methods( WP_REST_Request $request ) { $payment_method_ids_to_enable = $request->get_param( 'enabled_payment_method_ids' ); $available_payment_methods = $this->wcpay_gateway->get_upe_available_payment_methods(); + // Only 'card' and 'link' support manual capture. Leave them enabled if they're already enabled. + if ( $request->has_param( 'is_manual_capture_enabled' ) && $request->get_param( 'is_manual_capture_enabled' ) ) { + $payment_method_ids_to_enable = array_intersect( $payment_method_ids_to_enable, [ Payment_Method::CARD, Payment_Method::LINK ] ); + } + $payment_method_ids_to_enable = array_values( array_filter( $payment_method_ids_to_enable, @@ -612,6 +618,18 @@ function ( $payment_method ) use ( $available_payment_methods ) { ) ); + $this->request_unrequested_payment_methods( $payment_method_ids_to_enable ); + $capability_key_map = $this->wcpay_gateway->get_payment_method_capability_key_map(); + $payment_method_statuses = $this->wcpay_gateway->get_upe_enabled_payment_method_statuses(); + + $payment_method_ids_to_enable = array_filter( + $payment_method_ids_to_enable, + function ( $payment_method_id_to_enable ) use ( $capability_key_map, $payment_method_statuses ) { + $stripe_key = $capability_key_map[ $payment_method_id_to_enable ] ?? null; + return array_key_exists( $stripe_key, $payment_method_statuses ) && 'active' === $payment_method_statuses[ $stripe_key ]['status']; + } + ); + $active_payment_methods = $this->wcpay_gateway->get_upe_enabled_payment_method_ids(); $disabled_payment_methods = array_diff( $active_payment_methods, $payment_method_ids_to_enable ); $enabled_payment_methods = array_diff( $payment_method_ids_to_enable, $active_payment_methods ); @@ -650,10 +668,6 @@ function ( $payment_method ) use ( $available_payment_methods ) { foreach ( WC_Payments::get_payment_gateway_map() as $payment_gateway ) { $payment_gateway->update_option( 'upe_enabled_payment_method_ids', $payment_method_ids_to_enable ); } - - if ( $payment_method_ids_to_enable ) { - $this->request_unrequested_payment_methods( $payment_method_ids_to_enable ); - } } /** diff --git a/includes/class-database-cache.php b/includes/class-database-cache.php index 042d99f7f9e..68b4c7d4d89 100644 --- a/includes/class-database-cache.php +++ b/includes/class-database-cache.php @@ -7,17 +7,16 @@ 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 implements MultiCurrencyCacheInterface { +class Database_Cache { 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-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index c46a9b70e62..b7a694e88a5 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' ), @@ -825,6 +825,9 @@ public function needs_https_setup() { * @return bool Whether the gateway is enabled and ready to accept payments. */ public function is_available() { + if ( ! WC_Payments::get_gateway()->is_enabled() ) { + return false; + } $processing_payment_method = $this->payment_methods[ $this->payment_method->get_id() ]; if ( ! $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) ) { return false; diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index bde6316122a..b2ebc33b7d3 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -17,12 +17,11 @@ 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 implements MultiCurrencyAccountInterface { +class WC_Payments_Account { // 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'; @@ -171,18 +170,6 @@ 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. * @@ -648,22 +635,11 @@ public function get_account_email(): string { * * @return array Currencies. */ - public function get_account_customer_supported_currencies(): array { + public function get_account_customer_supported_currencies() { $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. * @@ -1662,21 +1638,6 @@ 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. * @@ -1718,6 +1679,21 @@ 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 * @@ -1877,9 +1853,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-checkout.php b/includes/class-wc-payments-checkout.php index 5764ecd96d5..79e6e119267 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -332,7 +332,9 @@ public function get_enabled_payment_method_config() { 'number' => '', ] ); - $settings[ $payment_method_id ]['forceNetworkSavedCards'] = $gateway_for_payment_method->should_use_stripe_platform_on_checkout_page(); + + $should_enable_network_saved_cards = Payment_Method::CARD === $payment_method_id && WC_Payments::is_network_saved_cards_enabled(); + $settings[ $payment_method_id ]['forceNetworkSavedCards'] = $should_enable_network_saved_cards || $gateway_for_payment_method->should_use_stripe_platform_on_checkout_page(); } return $settings; diff --git a/includes/class-wc-payments-explicit-price-formatter.php b/includes/class-wc-payments-explicit-price-formatter.php index 31c5364cfbe..d96bfa4f25f 100644 --- a/includes/class-wc-payments-explicit-price-formatter.php +++ b/includes/class-wc-payments-explicit-price-formatter.php @@ -6,6 +6,7 @@ */ use WCPay\MultiCurrency\MultiCurrency; +use WCPay\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. 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/includes/class-wc-payments-localization-service.php b/includes/class-wc-payments-localization-service.php index c0c0d7d84f8..5ea9375429d 100644 --- a/includes/class-wc-payments-localization-service.php +++ b/includes/class-wc-payments-localization-service.php @@ -5,14 +5,12 @@ * @package WooCommerce\Payments */ -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; - defined( 'ABSPATH' ) || exit; /** * WC_Payments_Localization_Service. */ -class WC_Payments_Localization_Service implements MultiCurrencyLocalizationInterface { +class WC_Payments_Localization_Service { 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 93e22fe9897..ed023cbd24e 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -42,7 +42,6 @@ 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. @@ -300,13 +299,6 @@ 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. */ @@ -491,8 +483,7 @@ 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__ . '/compat/multi-currency/wc-payments-multi-currency.php'; - include_once __DIR__ . '/compat/multi-currency/class-wc-payments-currency-manager.php'; + include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php'; include_once __DIR__ . '/class-duplicates-detection-service.php'; self::$woopay_checkout_service = new Checkout_Service(); @@ -585,9 +576,6 @@ 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(); @@ -613,16 +601,18 @@ function () { } ); - // Insert the Stripe Payment Messaging Element only if there is at least one BNPL method enabled. - $enabled_bnpl_payment_methods = array_intersect( - Payment_Method::BNPL_PAYMENT_METHODS, - self::get_gateway()->get_upe_enabled_payment_method_ids() - ); - if ( [] !== $enabled_bnpl_payment_methods ) { - add_action( 'woocommerce_single_product_summary', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 10 ); - add_action( 'woocommerce_proceed_to_checkout', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 5 ); - add_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ] ); - add_action( 'wc_ajax_wcpay_get_cart_total', [ __CLASS__, 'ajax_get_cart_total' ] ); + if ( self::get_gateway()->is_enabled() ) { + // Insert the Stripe Payment Messaging Element only if there is at least one BNPL method enabled. + $enabled_bnpl_payment_methods = array_intersect( + Payment_Method::BNPL_PAYMENT_METHODS, + self::get_gateway()->get_upe_enabled_payment_method_ids() + ); + if ( [] !== $enabled_bnpl_payment_methods ) { + add_action( 'woocommerce_single_product_summary', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 10 ); + add_action( 'woocommerce_proceed_to_checkout', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ], 5 ); + add_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after', [ __CLASS__, 'load_stripe_bnpl_site_messaging' ] ); + add_action( 'wc_ajax_wcpay_get_cart_total', [ __CLASS__, 'ajax_get_cart_total' ] ); + } } add_filter( 'woocommerce_payment_gateways', [ __CLASS__, 'register_gateway' ] ); @@ -649,6 +639,7 @@ function () { require_once __DIR__ . '/migrations/class-giropay-deprecation-settings-update.php'; require_once __DIR__ . '/migrations/class-erase-bnpl-announcement-meta.php'; require_once __DIR__ . '/migrations/class-erase-deprecated-flags-and-options.php'; + require_once __DIR__ . '/migrations/class-manual-capture-payment-method-settings-update.php'; add_action( 'woocommerce_woocommerce_payments_updated', [ new Allowed_Payment_Request_Button_Types_Update( self::get_gateway() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Allowed_Payment_Request_Button_Sizes_Update( self::get_gateway() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Update_Service_Data_From_Server( self::get_account_service() ), 'maybe_migrate' ] ); @@ -659,6 +650,7 @@ function () { add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Giropay_Deprecation_Settings_Update( self::get_gateway(), self::get_payment_gateway_map() ), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Erase_Bnpl_Announcement_Meta(), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Erase_Deprecated_Flags_And_Options(), 'maybe_migrate' ] ); + add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Manual_Capture_Payment_Method_Settings_Update( self::get_gateway(), self::get_payment_gateway_map() ), 'maybe_migrate' ] ); include_once WCPAY_ABSPATH . '/includes/class-wc-payments-explicit-price-formatter.php'; WC_Payments_Explicit_Price_Formatter::init(); @@ -1410,22 +1402,6 @@ 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/migrations/class-manual-capture-payment-method-settings-update.php b/includes/migrations/class-manual-capture-payment-method-settings-update.php new file mode 100644 index 00000000000..6775cf301e1 --- /dev/null +++ b/includes/migrations/class-manual-capture-payment-method-settings-update.php @@ -0,0 +1,89 @@ +main_gateway = $main_gateway; + $this->all_registered_gateways = $all_registered_gateways; + } + + /** + * Checks whether we should trigger the event. + */ + public function maybe_migrate() { + $previous_version = get_option( 'woocommerce_woocommerce_payments_version' ); + if ( version_compare( self::VERSION_SINCE, $previous_version, '>' ) ) { + $this->migrate(); + } + } + + /** + * Disables payment methods that do not support manual capture, when manual capture is enabled and updates + * the enabled payment methods for each gateway. + */ + private function migrate() { + $enabled_payment_methods = $this->main_gateway->get_option( 'upe_enabled_payment_method_ids', [] ); + $is_manual_capture_enabled = 'yes' === $this->main_gateway->get_option( 'manual_capture' ); + + if ( $is_manual_capture_enabled ) { + $filtered_payment_methods = array_filter( + $enabled_payment_methods, + function ( $method ) { + return in_array( $method, [ Payment_Method::CARD, Payment_Method::LINK ], true ); + } + ); + + foreach ( $this->all_registered_gateways as $gateway ) { + $stripe_id = $gateway->get_stripe_id(); + if ( Payment_Method::CARD !== $stripe_id && Payment_Method::LINK !== $stripe_id ) { + $gateway->disable(); + } + $gateway->update_option( 'upe_enabled_payment_method_ids', $filtered_payment_methods ); + } + } + } +} diff --git a/multi-currency/src/AdminNotices.php b/includes/multi-currency/AdminNotices.php similarity index 100% rename from multi-currency/src/AdminNotices.php rename to includes/multi-currency/AdminNotices.php diff --git a/multi-currency/src/Analytics.php b/includes/multi-currency/Analytics.php similarity index 99% rename from multi-currency/src/Analytics.php rename to includes/multi-currency/Analytics.php index 258be02d223..ef3f5b17a5a 100644 --- a/multi-currency/src/Analytics.php +++ b/includes/multi-currency/Analytics.php @@ -12,6 +12,7 @@ use Automattic\WooCommerce\Utilities\OrderUtil; use WC_Order; use WC_Order_Refund; +use WC_Payments; defined( 'ABSPATH' ) || exit; @@ -62,7 +63,7 @@ public function init() { $this->register_customer_currencies(); } - if ( $this->multi_currency->gateway_context['is_dev_mode'] ) { + if ( WC_Payments::mode()->is_dev() ) { add_filter( 'woocommerce_analytics_report_should_use_cache', [ $this, 'disable_report_caching' ] ); } @@ -104,7 +105,7 @@ public function init() { * @return void */ public function register_admin_scripts() { - $this->multi_currency->register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); + WC_Payments::register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' ); } /** diff --git a/multi-currency/src/BackendCurrencies.php b/includes/multi-currency/BackendCurrencies.php similarity index 91% rename from multi-currency/src/BackendCurrencies.php rename to includes/multi-currency/BackendCurrencies.php index bdb2da92b72..9ea009ac4c4 100644 --- a/multi-currency/src/BackendCurrencies.php +++ b/includes/multi-currency/BackendCurrencies.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -23,9 +23,9 @@ class BackendCurrencies { protected $multi_currency; /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; @@ -39,10 +39,10 @@ class BackendCurrencies { /** * Constructor. * - * @param MultiCurrency $multi_currency The MultiCurrency instance. - * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. + * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. */ - public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service ) { + public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; } diff --git a/multi-currency/src/Compatibility.php b/includes/multi-currency/Compatibility.php similarity index 99% rename from multi-currency/src/Compatibility.php rename to includes/multi-currency/Compatibility.php index 87e10cbde78..4f60915fb97 100644 --- a/multi-currency/src/Compatibility.php +++ b/includes/multi-currency/Compatibility.php @@ -7,6 +7,8 @@ namespace WCPay\MultiCurrency; +use WC_Deposits; +use WC_Deposits_Product_Manager; use WC_Order; use WC_Order_Refund; use WCPay\MultiCurrency\Compatibility\BaseCompatibility; @@ -39,7 +41,7 @@ class Compatibility extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { add_action( 'init', [ $this, 'init_compatibility_classes' ], 11 ); if ( defined( 'DOING_CRON' ) ) { diff --git a/multi-currency/src/Compatibility/BaseCompatibility.php b/includes/multi-currency/Compatibility/BaseCompatibility.php similarity index 95% rename from multi-currency/src/Compatibility/BaseCompatibility.php rename to includes/multi-currency/Compatibility/BaseCompatibility.php index 3e7d1a67a20..a98073fd113 100644 --- a/multi-currency/src/Compatibility/BaseCompatibility.php +++ b/includes/multi-currency/Compatibility/BaseCompatibility.php @@ -46,5 +46,5 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils ) { * * @return void */ - abstract public function init(); + abstract protected function init(); } diff --git a/multi-currency/src/Compatibility/WooCommerceBookings.php b/includes/multi-currency/Compatibility/WooCommerceBookings.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceBookings.php rename to includes/multi-currency/Compatibility/WooCommerceBookings.php index 756e4eef355..5a99534e5d6 100644 --- a/multi-currency/src/Compatibility/WooCommerceBookings.php +++ b/includes/multi-currency/Compatibility/WooCommerceBookings.php @@ -39,7 +39,7 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils, Fronte * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceDeposits.php b/includes/multi-currency/Compatibility/WooCommerceDeposits.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceDeposits.php rename to includes/multi-currency/Compatibility/WooCommerceDeposits.php index e2ffa89d441..f92819785c5 100644 --- a/multi-currency/src/Compatibility/WooCommerceDeposits.php +++ b/includes/multi-currency/Compatibility/WooCommerceDeposits.php @@ -20,7 +20,7 @@ class WooCommerceDeposits extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { if ( class_exists( 'WC_Deposits' ) ) { /* * Multi-currency support was added to WooCommerce Deposits in version 2.0.1. diff --git a/multi-currency/src/Compatibility/WooCommerceFedEx.php b/includes/multi-currency/Compatibility/WooCommerceFedEx.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceFedEx.php rename to includes/multi-currency/Compatibility/WooCommerceFedEx.php index 8a38d058e40..738e738150f 100644 --- a/multi-currency/src/Compatibility/WooCommerceFedEx.php +++ b/includes/multi-currency/Compatibility/WooCommerceFedEx.php @@ -20,7 +20,7 @@ class WooCommerceFedEx extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php b/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceNameYourPrice.php rename to includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php index fad352e1d1f..155f99e1a4d 100644 --- a/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php +++ b/includes/multi-currency/Compatibility/WooCommerceNameYourPrice.php @@ -21,7 +21,7 @@ class WooCommerceNameYourPrice extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php b/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php similarity index 98% rename from multi-currency/src/Compatibility/WooCommercePointsAndRewards.php rename to includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php index 38819d15322..9d78886eb41 100644 --- a/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php +++ b/includes/multi-currency/Compatibility/WooCommercePointsAndRewards.php @@ -33,7 +33,7 @@ class WooCommercePointsAndRewards extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommercePreOrders.php b/includes/multi-currency/Compatibility/WooCommercePreOrders.php similarity index 96% rename from multi-currency/src/Compatibility/WooCommercePreOrders.php rename to includes/multi-currency/Compatibility/WooCommercePreOrders.php index b16dd91b646..3c3fe9d5efc 100644 --- a/multi-currency/src/Compatibility/WooCommercePreOrders.php +++ b/includes/multi-currency/Compatibility/WooCommercePreOrders.php @@ -20,7 +20,7 @@ class WooCommercePreOrders extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceProductAddOns.php b/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php similarity index 99% rename from multi-currency/src/Compatibility/WooCommerceProductAddOns.php rename to includes/multi-currency/Compatibility/WooCommerceProductAddOns.php index add583059f8..7d245cd4e4f 100644 --- a/multi-currency/src/Compatibility/WooCommerceProductAddOns.php +++ b/includes/multi-currency/Compatibility/WooCommerceProductAddOns.php @@ -23,7 +23,7 @@ class WooCommerceProductAddOns extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/multi-currency/src/Compatibility/WooCommerceSubscriptions.php b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceSubscriptions.php rename to includes/multi-currency/Compatibility/WooCommerceSubscriptions.php index 6701f6739dd..94294827ea9 100644 --- a/multi-currency/src/Compatibility/WooCommerceSubscriptions.php +++ b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php @@ -7,8 +7,10 @@ namespace WCPay\MultiCurrency\Compatibility; +use WC_Payments_Explicit_Price_Formatter; +use WC_Payments_Features; use WC_Subscription; -use WCPay\MultiCurrency\Logger; +use WCPay\Logger; use WCPay\MultiCurrency\FrontendCurrencies; use WCPay\MultiCurrency\MultiCurrency; @@ -59,9 +61,9 @@ class WooCommerceSubscriptions extends BaseCompatibility { * * @return void */ - public function init() { + protected function init() { // Add needed actions and filters if WC Subscriptions or WCPay Subscriptions are active. - if ( class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Payments_Subscriptions' ) ) { + if ( class_exists( 'WC_Subscriptions' ) || WC_Payments_Features::is_wcpay_subscriptions_enabled() ) { if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) { $this->frontend_currencies = $this->multi_currency->get_frontend_currencies(); @@ -391,10 +393,6 @@ 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. @@ -402,15 +400,7 @@ 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(); - - // 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; + return WC_Payments_Explicit_Price_Formatter::get_explicit_price_with_currency( $html_price, $currency_code ); } /** diff --git a/multi-currency/src/Compatibility/WooCommerceUPS.php b/includes/multi-currency/Compatibility/WooCommerceUPS.php similarity index 97% rename from multi-currency/src/Compatibility/WooCommerceUPS.php rename to includes/multi-currency/Compatibility/WooCommerceUPS.php index 427aa060d52..6a53f47bff3 100644 --- a/multi-currency/src/Compatibility/WooCommerceUPS.php +++ b/includes/multi-currency/Compatibility/WooCommerceUPS.php @@ -20,7 +20,7 @@ class WooCommerceUPS extends BaseCompatibility { * * @return void */ - public function init() { + protected 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/includes/multi-currency/CountryFlags.php b/includes/multi-currency/CountryFlags.php new file mode 100644 index 00000000000..e841095255e --- /dev/null +++ b/includes/multi-currency/CountryFlags.php @@ -0,0 +1,304 @@ + '🇦🇩', + 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/multi-currency/src/Currency.php b/includes/multi-currency/Currency.php similarity index 89% rename from multi-currency/src/Currency.php rename to includes/multi-currency/Currency.php index 37b8fd17d12..0d9ae84da42 100644 --- a/multi-currency/src/Currency.php +++ b/includes/multi-currency/Currency.php @@ -7,7 +7,8 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; +use WC_Payments_Utils; defined( 'ABSPATH' ) || exit; @@ -66,21 +67,21 @@ class Currency implements \JsonSerializable { private $last_updated; /** - * Instance of MultiCurrencyLocalizationInterface. + * Instance of WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ private $localization_service; /** * Constructor. * - * @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. + * @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. */ - public function __construct( MultiCurrencyLocalizationInterface $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { + public function __construct( WC_Payments_Localization_Service $localization_service, $code = '', float $rate = 1.0, $last_updated = null ) { $this->localization_service = $localization_service; $this->code = $code; $this->rate = $rate; diff --git a/multi-currency/src/CurrencySwitcherBlock.php b/includes/multi-currency/CurrencySwitcherBlock.php similarity index 97% rename from multi-currency/src/CurrencySwitcherBlock.php rename to includes/multi-currency/CurrencySwitcherBlock.php index e13902c6ede..95d4762365c 100644 --- a/multi-currency/src/CurrencySwitcherBlock.php +++ b/includes/multi-currency/CurrencySwitcherBlock.php @@ -7,7 +7,9 @@ namespace WCPay\MultiCurrency; +use WC_Payments; use function http_build_query; +use function implode; use function urldecode; defined( 'ABSPATH' ) || exit; @@ -58,7 +60,7 @@ public function init_hooks() { */ public function init_block_widget() { // Automatically load dependencies and version. - $this->multi_currency->register_script_with_dependencies( 'woocommerce-payments/multi-currency-switcher', 'dist/multi-currency-switcher-block' ); + WC_Payments::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/multi-currency/src/CurrencySwitcherWidget.php b/includes/multi-currency/CurrencySwitcherWidget.php similarity index 100% rename from multi-currency/src/CurrencySwitcherWidget.php rename to includes/multi-currency/CurrencySwitcherWidget.php diff --git a/multi-currency/src/Exceptions/InvalidCurrencyException.php b/includes/multi-currency/Exceptions/InvalidCurrencyException.php similarity index 71% rename from multi-currency/src/Exceptions/InvalidCurrencyException.php rename to includes/multi-currency/Exceptions/InvalidCurrencyException.php index c3ec9046a27..454fa1c7383 100644 --- a/multi-currency/src/Exceptions/InvalidCurrencyException.php +++ b/includes/multi-currency/Exceptions/InvalidCurrencyException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use Exception; +use WCPay\Exceptions\Base_Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency is used. */ -class InvalidCurrencyException extends Exception {} +class InvalidCurrencyException extends Base_Exception {} diff --git a/multi-currency/src/Exceptions/InvalidCurrencyRateException.php b/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php similarity index 71% rename from multi-currency/src/Exceptions/InvalidCurrencyRateException.php rename to includes/multi-currency/Exceptions/InvalidCurrencyRateException.php index 6f0b5b2c007..e80cc0cb92d 100644 --- a/multi-currency/src/Exceptions/InvalidCurrencyRateException.php +++ b/includes/multi-currency/Exceptions/InvalidCurrencyRateException.php @@ -7,11 +7,11 @@ namespace WCPay\MultiCurrency\Exceptions; -use Exception; +use WCPay\Exceptions\Base_Exception; defined( 'ABSPATH' ) || exit; /** * Exception for throwing errors when an invalid currency rate is used. */ -class InvalidCurrencyRateException extends Exception {} +class InvalidCurrencyRateException extends Base_Exception {} diff --git a/multi-currency/src/FrontendCurrencies.php b/includes/multi-currency/FrontendCurrencies.php similarity index 94% rename from multi-currency/src/FrontendCurrencies.php rename to includes/multi-currency/FrontendCurrencies.php index 065d0db24a8..da1342ac55a 100644 --- a/multi-currency/src/FrontendCurrencies.php +++ b/includes/multi-currency/FrontendCurrencies.php @@ -8,7 +8,7 @@ namespace WCPay\MultiCurrency; use WC_Order; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -24,9 +24,9 @@ class FrontendCurrencies { protected $multi_currency; /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; @@ -89,12 +89,12 @@ class FrontendCurrencies { /** * Constructor. * - * @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. + * @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. */ - public function __construct( MultiCurrency $multi_currency, MultiCurrencyLocalizationInterface $localization_service, Utils $utils, Compatibility $compatibility ) { + public function __construct( MultiCurrency $multi_currency, WC_Payments_Localization_Service $localization_service, Utils $utils, Compatibility $compatibility ) { $this->multi_currency = $multi_currency; $this->localization_service = $localization_service; $this->utils = $utils; diff --git a/multi-currency/src/FrontendPrices.php b/includes/multi-currency/FrontendPrices.php similarity index 100% rename from multi-currency/src/FrontendPrices.php rename to includes/multi-currency/FrontendPrices.php diff --git a/multi-currency/src/Geolocation.php b/includes/multi-currency/Geolocation.php similarity index 84% rename from multi-currency/src/Geolocation.php rename to includes/multi-currency/Geolocation.php index 4f5da245958..5bd0b48b7db 100644 --- a/multi-currency/src/Geolocation.php +++ b/includes/multi-currency/Geolocation.php @@ -7,7 +7,7 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; +use WC_Payments_Localization_Service; defined( 'ABSPATH' ) || exit; @@ -16,18 +16,18 @@ */ class Geolocation { /** - * MultiCurrencyLocalizationInterface instance. + * WC_Payments_Localization_Service instance. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ protected $localization_service; /** * Constructor. * - * @param MultiCurrencyLocalizationInterface $localization_service The Localization Service instance. + * @param WC_Payments_Localization_Service $localization_service The Localization Service instance. */ - public function __construct( MultiCurrencyLocalizationInterface $localization_service ) { + public function __construct( WC_Payments_Localization_Service $localization_service ) { $this->localization_service = $localization_service; } diff --git a/multi-currency/src/Helpers/OrderMetaHelper.md b/includes/multi-currency/Helpers/OrderMetaHelper.md similarity index 100% rename from multi-currency/src/Helpers/OrderMetaHelper.md rename to includes/multi-currency/Helpers/OrderMetaHelper.md diff --git a/multi-currency/src/Helpers/OrderMetaHelper.php b/includes/multi-currency/Helpers/OrderMetaHelper.php similarity index 95% rename from multi-currency/src/Helpers/OrderMetaHelper.php rename to includes/multi-currency/Helpers/OrderMetaHelper.php index 9315557617a..208fefc2c26 100644 --- a/multi-currency/src/Helpers/OrderMetaHelper.php +++ b/includes/multi-currency/Helpers/OrderMetaHelper.php @@ -7,9 +7,10 @@ namespace WCPay\MultiCurrency\Helpers; -use WCPay\MultiCurrency\BackendCurrencies; -use WCPay\MultiCurrency\Logger; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; +use WC_Payments_API_Client; +use WC_Payments_Utils; +use WCPay\Exceptions\API_Exception; +use WCPay\Logger; defined( 'ABSPATH' ) || exit; @@ -20,17 +21,10 @@ class OrderMetaHelper { /** * Client for making requests to the WooCommerce Payments API. * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $payments_api_client; - /** - * Backend currencies object. - * - * @var BackendCurrencies - */ - private $backend_currencies; - /** * Array of errors, if any. * @@ -41,12 +35,10 @@ class OrderMetaHelper { /** * Constructor. * - * @param MultiCurrencyApiClientInterface $payments_api_client Payments API client. - * @param BackendCurrencies $backend_currencies Backend currencies object. + * @param WC_Payments_API_Client $payments_api_client Payments API client. */ - public function __construct( MultiCurrencyApiClientInterface $payments_api_client, BackendCurrencies $backend_currencies ) { + public function __construct( WC_Payments_API_Client $payments_api_client ) { $this->payments_api_client = $payments_api_client; - $this->backend_currencies = $backend_currencies; } /** @@ -235,7 +227,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 ( \Exception $e ) { + } catch ( API_Exception $e ) { // Log the error returned. Logger::error( "Error when attempting to get intent ($intent_id):\n" . $e->getMessage() ); $intent_object = null; @@ -255,7 +247,7 @@ public function display_meta_box_content( $order ) { /** * Zero decimal currencies have a different conversion rate value. */ - if ( $this->backend_currencies->is_zero_decimal_currency( $order_currency ) ) { + if ( in_array( strtolower( $order_currency ), WC_Payments_Utils::zero_decimal_currencies(), true ) ) { 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/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php similarity index 90% rename from multi-currency/src/MultiCurrency.php rename to includes/multi-currency/MultiCurrency.php index 4caab08a113..6482d34fb2e 100644 --- a/multi-currency/src/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -7,16 +7,20 @@ 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; @@ -37,6 +41,13 @@ 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 * @@ -129,32 +140,32 @@ class MultiCurrency { protected $enabled_currencies; /** - * Client for making requests to the API + * Client for making requests to the WooCommerce Payments API * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $payments_api_client; /** - * Instance of MultiCurrencyAccountInterface. + * Instance of WC_Payments_Account. * - * @var MultiCurrencyAccountInterface + * @var WC_Payments_Account */ private $payments_account; /** - * Instance of MultiCurrencyLocalizationInterface. + * Instance of WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ private $localization_service; /** - * Instance of MultiCurrencyCacheInterface. + * Instance of Database_Cache. * - * @var MultiCurrencyCacheInterface + * @var Database_Cache */ - private $cache; + private $database_cache; /** * Tracking instance. @@ -178,28 +189,35 @@ class MultiCurrency { private $order_meta_helper; /** - * Gateway context. + * Main MultiCurrency Instance. * - * @var array + * Ensures only one instance of MultiCurrency is loaded or can be loaded. + * + * @static + * @return MultiCurrency - Main instance. */ - public $gateway_context; + 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; + } /** * Class constructor. * - * @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. + * @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. */ - 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; + 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 ) { $this->payments_api_client = $payments_api_client; $this->payments_account = $payments_account; $this->localization_service = $localization_service; - $this->cache = $cache; + $this->database_cache = $database_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 ); @@ -224,9 +242,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' ) && ! Utils::is_admin_api_request(); + $is_frontend_request = ! is_admin() && ! defined( 'DOING_CRON' ) && ! WC()->is_rest_api_request(); - if ( $is_frontend_request || Utils::is_store_api_request() ) { + if ( $is_frontend_request || \WC_Payments_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 ); @@ -262,17 +280,19 @@ public function init() { $this->update_manual_rate_currencies_notice_option(); } - $admin_notices = new AdminNotices(); - $user_settings = new UserSettings( $this ); + $payment_method_compat = new PaymentMethodsCompatibility( $this, WC_Payments::get_gateway() ); + $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->backend_currencies ); + $this->order_meta_helper = new OrderMetaHelper( $this->payments_api_client ); // Init all of the hooks. + $payment_method_compat->init_hooks(); $admin_notices->init_hooks(); $user_settings->init_hooks(); $this->frontend_prices->init_hooks(); @@ -290,7 +310,7 @@ public function init() { } if ( is_admin() ) { - add_action( 'admin_init', [ $this, 'add_woo_admin_notes' ] ); + add_action( 'admin_init', [ __CLASS__, 'add_woo_admin_notes' ] ); } // Update the customer currencies option after an order status change. @@ -312,7 +332,7 @@ public function init_rest_api() { return; } - $api_controller = new RestController(); + $api_controller = new RestController( \WC_Payments::create_api_client() ); $api_controller->register_routes(); } @@ -335,19 +355,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 the payment provider is connected for the + // We don't need to check if Stripe 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_provider_connected() ) { + if ( $this->payments_account->is_stripe_connected() ) { $settings = new Settings( $this ); $settings->init_hooks(); $settings_pages[] = $settings; } else { - $settings_onboard_cta = new SettingsOnboardCta( $this, $this->payments_account ); + $settings_onboard_cta = new SettingsOnboardCta( $this ); $settings_onboard_cta->init_hooks(); $settings_pages[] = $settings_onboard_cta; @@ -372,7 +392,7 @@ public function enqueue_admin_scripts() { $this->register_admin_scripts(); wp_enqueue_script( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); - wp_enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); + WC_Payments_Utils::enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); } /** @@ -395,7 +415,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->cache->delete( MultiCurrencyCacheInterface::CURRENCIES_KEY ); + $this->database_cache->delete( Database_Cache::CURRENCIES_KEY ); } /** @@ -406,14 +426,14 @@ public function clear_cache() { * @return ?array */ public function get_cached_currencies() { - $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() ) { + $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() ) { return $cached_data ?? null; } - return $this->cache->get_or_add( - MultiCurrencyCacheInterface::CURRENCIES_KEY, + return $this->database_cache->get_or_add( + Database_Cache::CURRENCIES_KEY, function () { try { $currency_data = $this->payments_api_client->get_currency_rates( strtolower( get_woocommerce_currency() ) ); @@ -421,7 +441,7 @@ function () { 'currencies' => $currency_data, 'updated' => time(), ]; - } catch ( \Exception $e ) { + } catch ( API_Exception $e ) { return null; } }, @@ -563,7 +583,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 ), 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); } update_option( 'wcpay_multi_currency_manual_rate_' . $currency_code, $manual_rate ); } @@ -601,6 +621,94 @@ 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. * @@ -855,7 +963,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 ), 500 ); + throw new InvalidCurrencyRateException( esc_html( $message ), 'wcpay_multi_currency_invalid_currency_rate', 500 ); } $amount = $amount * ( $to_currency_rate / $from_currency_rate ); @@ -931,20 +1039,24 @@ public function display_geolocation_currency_update_notice() { } $message = sprintf( - /* 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' ), + /* 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' ), 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() ), - esc_html( $currencies[ $store_currency ] ), - esc_url( '?currency=' . $store_currency ) + $currencies[ $store_currency ] ); $notice_id = md5( $message ); echo ''; - // No need to escape here as the contents of $message is already escaped. + // No need to escape here as the function called handles it. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo $message; + echo \WC_Payments_Utils::esc_interpolated_html( + $message, + [ + 'a' => '', + ] + ); echo ' ' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . ''; } @@ -968,14 +1080,14 @@ public function set_new_customer_currency_meta( $customer_id ) { * * @return void */ - public function add_woo_admin_notes() { + public static 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( $this->payments_account ); + NoteMultiCurrencyAvailable::set_account( WC_Payments::get_account_service() ); NoteMultiCurrencyAvailable::possibly_add_note(); } } @@ -992,138 +1104,292 @@ public static function remove_woo_admin_notes() { } /** - * Checks if the merchant has enabled automatic currency switching and geolocation. + * Gets the price after adjusting it with the rounding and charm settings. * - * @return bool + * @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. + * + * @return float The adjusted price. */ - public function is_using_auto_currency_switching(): bool { - return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' ); + 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 ); } /** - * Checks if the merchant has enabled the currency switcher widget. + * Ceils the price to the next number based on the rounding value. * - * @return bool + * @param float $price The price to be ceiled. + * @param float $rounding The rounding option. + * + * @return float The ceiled price. */ - public function is_using_storefront_switcher(): bool { - return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' ); + protected function ceil_price( float $price, float $rounding ): float { + if ( 0.00 === $rounding ) { + return $price; + } + return ceil( $price / $rounding ) * $rounding; } /** - * Gets the store settings. + * Returns the currency code stored for the user or in the session. * - * @return array The store settings. + * @return string|null Currency code. */ - 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' ) ) ), - ]; + 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; } /** - * Updates the store settings - * - * @param array $params Update requested values. + * Checks to see if the store currency has changed. If it has, this will + * also update the option containing the store currency. * - * @return void + * @return bool */ - public function update_settings( $params ) { - $updateable_options = [ - 'wcpay_multi_currency_enable_auto_currency', - 'wcpay_multi_currency_enable_storefront_switcher', - ]; + private function check_store_currency_for_change(): bool { + $last_known_currency = get_option( $this->id . '_store_currency', false ); + $woocommerce_currency = get_woocommerce_currency(); - foreach ( $updateable_options as $key ) { - if ( isset( $params[ $key ] ) ) { - update_option( $key, sanitize_text_field( $params[ $key ] ) ); - } + // 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; } /** - * Apply client order currency format and reduces the rounding precision to 2. + * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display. * - * @return void + * @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; + 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(); } - $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(); - ?> - - id . '_show_store_currency_changed_notice', $manual_currencies ); + } } /** - * Load script with all required dependencies. + * Accepts an array of currencies that should have their settings removed. * - * @param string $handler Script handler. - * @param string $script Script name relative to the plugin root. - * @param array $additional_dependencies Additional dependencies. + * @param array $currencies Array of Currency objects or 3 letter currency codes. * * @return void */ - 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 - ); + private function remove_currencies_settings( array $currencies ) { + + foreach ( $currencies as $currency ) { + $this->remove_currency_settings( $currency ); + } } /** - * Get the file modified time as a cache buster if we're in dev mode. + * Will remove a currency's settings if it is not enabled. * - * @param string $file Local path to the file. + * @param mixed $currency Currency object or 3 letter currency code. * - * @return string + * @return void */ - public function get_file_version( $file ) { - $plugin_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ); + private function remove_currency_settings( $currency ) { + $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency ); - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) { - return (string) filemtime( $plugin_path . trim( $file, '/' ) ); + // 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; } - return $this->gateway_context['plugin_version']; + $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 ) ); + } } /** - * Validates the given currency code. + * Returns the currencies enabled for the Stripe account that are + * also available in WC. * - * @param string $currency_code The currency code to check validity. + * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook. * - * @return string|false Returns back the currency code in uppercase letters if it's valid, or `false` if not. + * @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 ) @@ -1147,6 +1413,52 @@ 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 * @@ -1208,6 +1520,45 @@ 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_provider_connected() ) { + if ( is_null( self::$account ) || ! self::$account->is_stripe_connected() ) { return; } @@ -80,9 +80,9 @@ public static function possibly_add_note() { /** * Sets the account service instance reference on the class. * - * @param MultiCurrencyAccountInterface $account account service instance. + * @param WC_Payments_Account $account account service instance. */ - public static function set_account( MultiCurrencyAccountInterface $account ) { + public static function set_account( WC_Payments_Account $account ) { self::$account = $account; } } diff --git a/includes/compat/multi-currency/class-wc-payments-currency-manager.php b/includes/multi-currency/PaymentMethodsCompatibility.php similarity index 69% rename from includes/compat/multi-currency/class-wc-payments-currency-manager.php rename to includes/multi-currency/PaymentMethodsCompatibility.php index c00da0d1a69..0c5f7d4b736 100644 --- a/includes/compat/multi-currency/class-wc-payments-currency-manager.php +++ b/includes/multi-currency/PaymentMethodsCompatibility.php @@ -1,21 +1,29 @@ gateway = $gateway; + public function __construct( MultiCurrency $multi_currency, WC_Payment_Gateway_WCPay $gateway ) { + $this->multi_currency = $multi_currency; + $this->gateway = $gateway; } /** @@ -38,32 +48,17 @@ public function __construct( WC_Payment_Gateway_WCPay $gateway ) { * @return void */ public function init_hooks() { - add_action( 'update_option_woocommerce_woocommerce_payments_settings', [ $this, 'maybe_add_missing_currencies' ] ); + add_action( + 'update_option_woocommerce_woocommerce_payments_settings', + [ $this, '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(); @@ -102,19 +97,14 @@ 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 maybe_add_missing_currencies() { - $multi_currency = $this->get_multi_currency_instance(); - if ( is_null( $multi_currency ) ) { - return; - } - + public function add_missing_currencies() { $payment_methods_needing_currency = $this->get_enabled_payment_method_currencies(); if ( empty( $payment_methods_needing_currency ) ) { return; } - $enabled_currencies = $multi_currency->get_enabled_currencies(); - $available_currencies = $multi_currency->get_available_currencies(); + $enabled_currencies = $this->multi_currency->get_enabled_currencies(); + $available_currencies = $this->multi_currency->get_available_currencies(); $missing_currency_codes = []; @@ -147,21 +137,15 @@ public function maybe_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. */ - $multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); + $this->multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) ); } /** - * Adds the `multiCurrencyPaymentMethodsMap` JS object to the multi-currency settings page. + * Adds the notices for currencies that are bound to an UPE payment method. * - * This object maps currencies to payment methods that require them, so the multi-currency settings page displays a notice in case of dependencies. + * @return void */ 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; @@ -177,10 +161,11 @@ public function add_payment_method_currency_dependencies_script() { } } - ?> + if ( WC_Payments_Multi_Currency()->is_multi_currency_settings_page() ) : ?> - set_enabled_currencies( $enabled ); $response = $this->get_store_currencies(); } catch ( InvalidCurrencyException $e ) { - $response = new \WP_Error( $e->getCode(), $e->getMessage() ); + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); } @@ -185,7 +178,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->getCode(), $e->getMessage() ); + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -208,8 +201,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 ( Exception $e ) { - $response = new \WP_Error( $e->getCode(), $e->getMessage() ); + } catch ( Base_Exception $e ) { + $response = new \WP_Error( $e->get_error_code(), $e->getMessage() ); } return rest_ensure_response( $response ); @@ -236,11 +229,4 @@ 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/multi-currency/src/Settings.php b/includes/multi-currency/Settings.php similarity index 100% rename from multi-currency/src/Settings.php rename to includes/multi-currency/Settings.php diff --git a/multi-currency/src/SettingsOnboardCta.php b/includes/multi-currency/SettingsOnboardCta.php similarity index 74% rename from multi-currency/src/SettingsOnboardCta.php rename to includes/multi-currency/SettingsOnboardCta.php index 59a2eee286f..ee89ea5638c 100644 --- a/multi-currency/src/SettingsOnboardCta.php +++ b/includes/multi-currency/SettingsOnboardCta.php @@ -7,8 +7,6 @@ namespace WCPay\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; - defined( 'ABSPATH' ) || exit; /** @@ -29,24 +27,15 @@ 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 MultiCurrencyAccountInterface $payments_account Payments Account instance. + * @param MultiCurrency $multi_currency The MultiCurrency instance. */ - 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' ); + 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' ); parent::__construct(); } @@ -64,7 +53,7 @@ public function init_hooks() { * Output the call to action button if needing to onboard. */ public function currencies_settings_onboarding_cta() { - $href = $this->payments_account->get_provider_onboarding_page_url(); + $href = \WC_Payments_Account::get_connect_page_url(); ?> diff --git a/multi-currency/src/StorefrontIntegration.php b/includes/multi-currency/StorefrontIntegration.php similarity index 100% rename from multi-currency/src/StorefrontIntegration.php rename to includes/multi-currency/StorefrontIntegration.php diff --git a/multi-currency/src/Tracking.php b/includes/multi-currency/Tracking.php similarity index 100% rename from multi-currency/src/Tracking.php rename to includes/multi-currency/Tracking.php diff --git a/multi-currency/src/UserSettings.php b/includes/multi-currency/UserSettings.php similarity index 100% rename from multi-currency/src/UserSettings.php rename to includes/multi-currency/UserSettings.php diff --git a/multi-currency/src/Utils.php b/includes/multi-currency/Utils.php similarity index 71% rename from multi-currency/src/Utils.php rename to includes/multi-currency/Utils.php index 1c3fd074076..64e5356a77a 100644 --- a/multi-currency/src/Utils.php +++ b/includes/multi-currency/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() && ! self::is_store_api_request(); + return 0 === stripos( wp_get_referer(), admin_url() ) && WC()->is_rest_api_request() && ! \WC_Payments_Utils::is_store_api_request(); } @@ -71,21 +71,4 @@ 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/includes/compat/multi-currency/wc-payments-multi-currency.php b/includes/multi-currency/wc-payments-multi-currency.php similarity index 80% rename from includes/compat/multi-currency/wc-payments-multi-currency.php rename to includes/multi-currency/wc-payments-multi-currency.php index 0d0775e7d62..fef6e10db81 100644 --- a/includes/compat/multi-currency/wc-payments-multi-currency.php +++ b/includes/multi-currency/wc-payments-multi-currency.php @@ -29,25 +29,12 @@ function wcpay_multi_currency_onboarding_check() { } /** - * Returns the MultiCurrency singleton. + * Returns the main instance of MultiCurrency. * * @return WCPay\MultiCurrency\MultiCurrency */ function WC_Payments_Multi_Currency() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid - static $instance = null; - - if ( is_null( $instance ) ) { - $instance = new WCPay\MultiCurrency\MultiCurrency( - WC_Payments::get_context_for_multi_currency(), - WC_Payments::get_payments_api_client(), - WC_Payments::get_account_service(), - WC_Payments::get_localization_service(), - WC_Payments::get_database_cache() - ); - $instance->init_hooks(); - } - - return $instance; + return WCPay\MultiCurrency\MultiCurrency::instance(); } add_action( 'plugins_loaded', 'WC_Payments_Multi_Currency', 12 ); 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 13b25e07dd6..50aabec994c 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -21,12 +21,11 @@ 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 implements MultiCurrencyApiClientInterface { +class WC_Payments_API_Client { const ENDPOINT_BASE = 'https://public-api.wordpress.com/wpcom/v2'; const ENDPOINT_SITE_FRAGMENT = 'sites/%s'; @@ -192,7 +191,7 @@ public function __construct( $user_agent, $http_client, $wcpay_db ) { * * @return bool */ - public function is_server_connected(): bool { + public function is_server_connected() { return $this->http_client->is_connected(); } @@ -858,7 +857,7 @@ function ( $a, $b ) { * * @throws API_Exception - Error contacting the API. */ - public function get_currency_rates( string $currency_from, $currencies_to = null ): array { + public function get_currency_rates( string $currency_from, $currencies_to = null ) { if ( empty( $currency_from ) ) { throw new API_Exception( __( 'Currency From parameter is required', 'woocommerce-payments' ), diff --git a/multi-currency/client/data/constants.js b/multi-currency/client/data/constants.js deleted file mode 100644 index eaee38c6ee6..00000000000 --- a/multi-currency/client/data/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @format */ - -export const NAMESPACE = '/wc/v3/payments'; -export const STORE_NAME = 'wc/payments/multi-currency'; diff --git a/multi-currency/client/data/index.ts b/multi-currency/client/data/index.ts deleted file mode 100644 index f1d8d84a456..00000000000 --- a/multi-currency/client/data/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** @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/multi-currency/client/data/store.js b/multi-currency/client/data/store.js deleted file mode 100644 index 2e142e66f9c..00000000000 --- a/multi-currency/client/data/store.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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/multi-currency/client/interface/assets.js b/multi-currency/client/interface/assets.js deleted file mode 100644 index 3a3a095891a..00000000000 --- a/multi-currency/client/interface/assets.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 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 deleted file mode 100644 index eddab5ae84e..00000000000 --- a/multi-currency/client/interface/components.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 deleted file mode 100644 index af162ddd6ed..00000000000 --- a/multi-currency/client/interface/data.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 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 deleted file mode 100644 index 5ffed26e00d..00000000000 --- a/multi-currency/client/interface/functions.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 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/multi-currency/src/CountryFlags.php b/multi-currency/src/CountryFlags.php deleted file mode 100644 index 286e77dfbe2..00000000000 --- a/multi-currency/src/CountryFlags.php +++ /dev/null @@ -1,301 +0,0 @@ - '🇦🇩', - '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/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php b/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php deleted file mode 100644 index b827c5ca3d8..00000000000 --- a/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php +++ /dev/null @@ -1,61 +0,0 @@ -log( $level, $message, [ 'source' => self::LOG_FILE ] ); - } -} diff --git a/package-lock.json b/package-lock.json index fb0a1965e2a..0c5635e6817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index fd6d97fe3f9..5b525252554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index a0bf916f7a9..6e21606d2bb 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -102,7 +102,7 @@ tests/* - multi-currency/src + includes/multi-currency src diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1eb5fab3f04..2a5164e8af3 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/readme.txt b/readme.txt index c4f1b121e3c..219da74fc9a 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.6 Requires PHP: 7.3 -Stable tag: 8.2.1 +Stable tag: 8.2.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,10 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 8.2.2 - 2024-09-24 = +* Fix - Fix WooPay pre-checking place order bug when buying a subscription. + + = 8.2.1 - 2024-09-13 = * Fix - Create div container element with JS dynamically. diff --git a/tasks/release.js b/tasks/release.js index 72c3eccdef7..92559af4ffb 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -14,11 +14,11 @@ const targetFolder = 'release/' + pluginSlug; const filesToCopy = [ 'assets', 'dist', - 'i18n', 'includes', + 'i18n', 'languages', - 'lib', 'src', + 'lib', 'templates', 'vendor', 'woocommerce-payments.php', @@ -43,10 +43,6 @@ 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/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', - } ); - } ); -} ); 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"]' + ); } ); } ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 03060196d4c..18c11bc9553 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -2,18 +2,13 @@ const { jsWithBabel: tsjPreset } = require( 'ts-jest/presets' ); module.exports = { rootDir: '../../', - moduleDirectories: [ - 'node_modules', - '/client', - '/multi-currency/client', - ], + moduleDirectories: [ 'node_modules', '/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/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index d84f4b680ba..d68c5c1f82e 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 @@ -377,11 +377,13 @@ public function test_update_settings_saves_enabled_payment_methods() { WC_Payments::get_gateway()->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); $request = new WP_REST_Request(); + + // Ideal will not have the capability status, 'active', and will therefore not added to the list of enabled payment methods. $request->set_param( 'enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::IDEAL ] ); $this->controller->update_settings( $request ); - $this->assertEquals( [ Payment_Method::CARD, Payment_Method::IDEAL ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); + $this->assertEquals( [ Payment_Method::CARD ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); } public function test_update_settings_fails_if_user_cannot_manage_woocommerce() { @@ -550,7 +552,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/multi-currency/compatibility/test-class-woocommerce-subscriptions.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php index 5edd60cd8a6..5b71b8f8e7f 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php @@ -6,10 +6,6 @@ */ 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; @@ -66,16 +62,7 @@ class WCPay_Multi_Currency_WooCommerceSubscriptions_Tests extends WCPAY_UnitTest public function set_up() { parent::set_up(); - $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_multi_currency = $this->createMock( MultiCurrency::class ); $this->mock_utils = $this->createMock( Utils::class ); $this->woocommerce_subscriptions = new WooCommerceSubscriptions( $this->mock_multi_currency, $this->mock_utils ); @@ -841,11 +828,18 @@ 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 e434ae9f661..5a985dd0511 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,7 +6,6 @@ */ use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; /** * Class Note_Multi_Currency_Available_Test tests. @@ -56,8 +55,8 @@ public function test_possibly_add_note_without_account() { } public function test_possibly_add_note_with_account_not_connected() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( false ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_connected' )->willReturn( false ); NoteMultiCurrencyAvailable::set_account( $account_mock ); NoteMultiCurrencyAvailable::possibly_add_note(); @@ -66,8 +65,8 @@ public function test_possibly_add_note_with_account_not_connected() { } public function test_possibly_add_note_with_connected_account() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( true ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_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 02c719ca64b..00b46e2d7ef 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 ); - $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 = $this->createMock( MultiCurrency::class ); $this->mock_multi_currency->expects( $this->any() ) ->method( 'get_all_customer_currencies' ) @@ -98,10 +84,7 @@ public function set_up() { $this->analytics = new Analytics( $this->mock_multi_currency ); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); - $this->mock_localization_service->expects( $this->any() ) - ->method( 'get_currency_format' ) - ->willReturn( [ 'num_decimals' => 2 ] ); + $this->localization_service = new WC_Payments_Localization_Service(); remove_filter( 'user_has_cap', $cb ); } @@ -181,7 +164,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 14.00 ); $order = wc_create_order(); @@ -196,7 +179,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 130500.75, 20000, 10000, 100500.75 ); $order = wc_create_order(); @@ -211,7 +194,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 15.00 ); $order = wc_create_order(); @@ -604,19 +587,14 @@ private function create_can_manage_woocommerce_cap_override( bool $can_manage_wo } private function get_mock_available_currencies() { - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->localization_service = new WC_Payments_Localization_Service(); 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->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 ), + '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 ), ]; } diff --git a/tests/unit/multi-currency/test-class-backend-currencies.php b/tests/unit/multi-currency/test-class-backend-currencies.php index 8b333c6f189..530efc71b7d 100644 --- a/tests/unit/multi-currency/test-class-backend-currencies.php +++ b/tests/unit/multi-currency/test-class-backend-currencies.php @@ -6,7 +6,6 @@ */ use WCPay\MultiCurrency\BackendCurrencies; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; /** @@ -14,9 +13,9 @@ */ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { /** - * Mock MultiCurrencyLocalizationInterface. + * Mock WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface|PHPUnit_Framework_MockObject_MockObject + * @var WC_Payments_Localization_Service|PHPUnit_Framework_MockObject_MockObject */ private $mock_localization_service; @@ -37,7 +36,7 @@ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::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 ae4cc75dc31..cefb478ca50 100644 --- a/tests/unit/multi-currency/test-class-compatibility.php +++ b/tests/unit/multi-currency/test-class-compatibility.php @@ -7,7 +7,6 @@ use WCPay\MultiCurrency\Compatibility; use WCPay\MultiCurrency\Currency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Utils; @@ -37,11 +36,11 @@ class WCPay_Multi_Currency_Compatibility_Tests extends WCPAY_UnitTestCase { private $mock_utils; /** - * MultiCurrencyLocalizationInterface. + * WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $localization_service; /** * Pre-test setup @@ -49,15 +48,10 @@ 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->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 ); + $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(); } public function test_init_compatibility_classes_does_not_add_classes_if_one_enabled_currencies() { @@ -114,7 +108,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -138,7 +132,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -159,7 +153,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -183,7 +177,7 @@ public function test_filter_woocommerce_order_query() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->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 cd3adff8feb..0ebe63fb8ef 100644 --- a/tests/unit/multi-currency/test-class-country-flags.php +++ b/tests/unit/multi-currency/test-class-country-flags.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\CountryFlags; /** @@ -12,7 +13,7 @@ */ class Country_Flags_Test extends WCPAY_UnitTestCase { public function test_get_by_country_returns_emoji_flag() { - $this->assertEquals( CountryFlags::get_by_country( 'US' ), '🇺🇸' ); + $this->assertEquals( CountryFlags::get_by_country( Country_Code::UNITED_STATES ), '🇺🇸' ); } 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 2ac0a69fa11..f716f6dbe75 100644 --- a/tests/unit/multi-currency/test-class-currency-switcher-block.php +++ b/tests/unit/multi-currency/test-class-currency-switcher-block.php @@ -10,7 +10,6 @@ use WCPay\MultiCurrency\Currency; use WCPay\MultiCurrency\CurrencySwitcherBlock; use WCPay\MultiCurrency\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; /** * CurrencySwitcherBlock unit tests. @@ -38,25 +37,23 @@ class WCPay_Multi_Currency_Currency_Switcher_Block_Tests extends WCPAY_UnitTestC protected $mock_currencies; /** - * @var MockObject\MultiCurrencyLocalizationInterface + * WC_Payments_Localization_Service. + * + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $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->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->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->currency_switcher_block = new CurrencySwitcherBlock( @@ -222,8 +219,8 @@ public function test_render_currency_option_will_escape_output() { ->method( 'get_enabled_currencies' ) ->willReturn( [ - new Currency( $this->mock_localization_service, 'USD' ), - new Currency( $this->mock_localization_service, $currency_code, 1 ), + new Currency( $this->localization_service, 'USD' ), + new Currency( $this->localization_service, $currency_code, 1 ), ] ); @@ -278,7 +275,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->mock_localization_service, 'USD' ) ] ); + ->willReturn( [ new Currency( $this->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 13d6b4bb34d..c38d343dcc9 100644 --- a/tests/unit/multi-currency/test-class-frontend-prices.php +++ b/tests/unit/multi-currency/test-class-frontend-prices.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\FrontendPrices unit tests. */ @@ -214,7 +216,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; @@ -258,7 +260,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, '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 92b0b50b166..b66543dbf67 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\Geolocation unit tests. */ @@ -49,10 +51,10 @@ public function test_get_country_by_customer_location_returns_geolocation_countr add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); - $this->assertSame( 'CA', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::CANADA, $this->geolocation->get_country_by_customer_location() ); } public function test_get_country_by_customer_location_returns_default_country_when_no_geolocation() { @@ -66,20 +68,20 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); - $this->assertSame( 'BR', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::BRAZIL, $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( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -87,7 +89,7 @@ function () { } public function test_get_currency_by_customer_location_returns_default_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'BR' )->willReturn( [ 'currency_code' => 'BRL' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::BRAZIL )->willReturn( [ 'currency_code' => 'BRL' ] ); add_filter( 'woocommerce_geolocate_ip', @@ -98,7 +100,7 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index 70487b835ee..023fd92868c 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -5,12 +5,11 @@ * @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; @@ -66,14 +65,14 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { /** * Mock of the API client. * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $mock_api_client; /** - * Mock of the MultiCurrencyAccountInterface. + * Mock of the WC_Payments_Account. * - * @var MultiCurrencyAccountInterface + * @var WC_Payments_Account */ private $mock_account; @@ -85,11 +84,11 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { private $localization_service; /** - * Mock of MultiCurrencyCacheInterface. + * Mock of Database_Cache. * - * @var MultiCurrencyCacheInterface; + * @var Database_Cache; */ - private $mock_cache; + private $mock_database_cache; /** * Mock of Utils. @@ -457,7 +456,7 @@ public function test_update_selected_currency_by_geolocation_does_not_set_sessio add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -474,7 +473,7 @@ public function test_update_selected_currency_by_geolocation_updates_session_whe add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -489,7 +488,7 @@ public function test_update_selected_currency_by_geolocation_displays_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -510,7 +509,7 @@ public function test_update_selected_currency_by_geolocation_does_not_update_if_ add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -535,7 +534,7 @@ public function test_display_geolocation_currency_update_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -545,11 +544,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, 'US' ); + WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, Country_Code::UNITED_STATES ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -563,7 +562,7 @@ public function test_display_geolocation_currency_update_notice_does_not_display add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -722,13 +721,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( MultiCurrencyApiClientInterface::class ); + $mock_api_client = $this->createMock( WC_Payments_API_Client::class ); $mock_api_client->method( 'is_server_connected' )->willReturn( false ); $this->init_multi_currency( $mock_api_client ); - $this->mock_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, @@ -737,7 +736,7 @@ public function test_get_cached_currencies_with_no_server_connection() { } public function test_get_cached_currencies_with_account_rejected() { - $this->mock_cache + $this->mock_database_cache ->expects( $this->once() ) ->method( 'get' ) ->willReturn( null ); @@ -747,7 +746,7 @@ public function test_get_cached_currencies_with_account_rejected() { ->method( 'is_account_rejected' ) ->willReturn( true ); - $this->mock_cache + $this->mock_database_cache ->expects( $this->never() ) ->method( 'get_or_add' ); @@ -759,11 +758,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->exactly( 2 ) ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturnCallback( function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { if ( 1 === $get_or_add_call_count ) { @@ -777,7 +776,7 @@ function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { } ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $currency_from = strtolower( get_woocommerce_currency() ); $currencies_to = get_woocommerce_currencies(); @@ -887,7 +886,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_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, $this->multi_currency->get_cached_currencies() @@ -1052,14 +1051,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1074,14 +1073,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1104,14 +1103,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1415,32 +1414,31 @@ 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_cache = null ) { - $this->mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); + 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 ); - $this->mock_account = $mock_account ?? $this->createMock( MultiCurrencyAccountInterface::class ); - $this->mock_account->method( 'is_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account = $mock_account ?? $this->createMock( WC_Payments_Account::class ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_api_client->method( 'is_server_connected' )->willReturn( true ); - $this->mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $this->mock_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache = $this->createMock( Database_Cache::class ); + $this->mock_database_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_cache ?? $this->mock_cache, + $mock_database_cache ?? $this->mock_database_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/test-class-wc-payments-currency-manager.php b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php similarity index 84% rename from tests/unit/test-class-wc-payments-currency-manager.php rename to tests/unit/multi-currency/test-class-payment-methods-compatibility.php index 6f13664ec75..bdbbb767cde 100644 --- a/tests/unit/test-class-wc-payments-currency-manager.php +++ b/tests/unit/multi-currency/test-class-payment-methods-compatibility.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( [ @@ -67,16 +68,8 @@ public function set_up() { ->getMock(); $this->gateway_mock->method( 'get_account_country' )->willReturn( 'US' ); - $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->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); + $this->payment_methods_compatibility->init_hooks(); $this->localization_service = new WC_Payments_Localization_Service(); } @@ -86,7 +79,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_when_not_needed() { @@ -114,7 +107,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_when_needed() { @@ -154,7 +147,7 @@ public function test_it_should_update_available_currencies_when_needed() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_with_bnpl_methods() { @@ -180,7 +173,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_with_bnpl_methods() { @@ -214,6 +207,6 @@ public function test_it_should_update_available_currencies_with_bnpl_methods() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } } diff --git a/tests/unit/multi-currency/test-class-rest-controller.php b/tests/unit/multi-currency/test-class-rest-controller.php index dfe581cde25..49ac60ad1be 100644 --- a/tests/unit/multi-currency/test-class-rest-controller.php +++ b/tests/unit/multi-currency/test-class-rest-controller.php @@ -5,7 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; use WCPay\MultiCurrency\RestController; /** @@ -34,7 +33,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( MultiCurrencyApiClientInterface::class )->disableOriginalConstructor()->getMock(); + $mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )->disableOriginalConstructor()->getMock(); $this->controller = new RestController( $mock_api_client ); } @@ -83,8 +82,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/update-enabled-currencies' ); @@ -129,8 +129,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'GET', self::ROUTE . '/currencies/AAA' ); @@ -190,8 +191,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/currencies/AAA' ); @@ -218,8 +220,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $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 272a691910b..1ccbd373a0c 100644 --- a/tests/unit/multi-currency/test-class-settings.php +++ b/tests/unit/multi-currency/test-class-settings.php @@ -5,6 +5,8 @@ * @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 553df2a0cc1..1f3c9cce762 100644 --- a/tests/unit/multi-currency/test-class-utils.php +++ b/tests/unit/multi-currency/test-class-utils.php @@ -60,7 +60,8 @@ 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/'; - $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ) . 'wc/store/v1/checkout'; + $_REQUEST['rest_route'] = '/wc/store/v1/checkout'; + $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ); $this->assertFalse( $this->utils->is_admin_api_request() ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index e14803b6812..b97fad22eea 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -2789,6 +2789,7 @@ function ( $order ) { } public function test_gateway_enabled_when_payment_method_is_enabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2797,6 +2798,7 @@ public function test_gateway_enabled_when_payment_method_is_enabled() { } public function test_gateway_disabled_when_payment_method_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2804,6 +2806,15 @@ public function test_gateway_disabled_when_payment_method_is_disabled() { $this->assertFalse( $afterpay->is_available() ); } + public function test_gateway_disabled_when_card_gateway_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'no' ); + $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); + $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); + $this->prepare_gateway_for_availability_testing( $afterpay ); + + $this->assertFalse( $afterpay->is_available() ); + } + public function test_process_payment_for_order_cc_payment_method() { $payment_method = 'woocommerce_payments'; $expected_upe_payment_method_for_pi_creation = 'card'; @@ -3943,7 +3954,7 @@ private function create_charge_object() { private function prepare_gateway_for_availability_testing( $gateway ) { WC_Payments::mode()->test(); $current_currency = strtolower( get_woocommerce_currency() ); - $this->mock_wcpay_account->expects( $this->once() )->method( 'get_account_customer_supported_currencies' )->will( + $this->mock_wcpay_account->expects( $this->any() )->method( 'get_account_customer_supported_currencies' )->will( $this->returnValue( [ $current_currency, 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-explicit-price-formatter.php b/tests/unit/test-class-wc-payments-explicit-price-formatter.php index b01ce3092d2..9c32f3afdb5 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_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); @@ -248,10 +248,7 @@ 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 ); - $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 = new MultiCurrency( $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/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-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( 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', diff --git a/tsconfig.json b/tsconfig.json index f7f56cddd52..3b9efabf45e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,8 @@ "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 55c249694ea..7ef039967cd 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': './multi-currency/client/index.js', + 'multi-currency': './client/multi-currency/index.js', 'multi-currency-switcher-block': - './multi-currency/client/blocks/currency-switcher.js', + './client/multi-currency/blocks/currency-switcher.js', 'multi-currency-analytics': - './multi-currency/client/analytics/index.js', + './client/multi-currency-analytics/index.js', order: './client/order/index.js', 'subscriptions-empty-state': './client/subscriptions-empty-state/index.js', @@ -113,18 +113,9 @@ module.exports = { }, resolve: { extensions: [ '.ts', '.tsx', '.json', '.js', '.jsx' ], - modules: [ - path.join( process.cwd(), 'client' ), - path.join( process.cwd(), 'multi-currency', 'client' ), - 'node_modules', - ], + modules: [ path.join( process.cwd(), '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(), diff --git a/woocommerce-payments.php b/woocommerce-payments.php index c079725904c..825bb4c8f18 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 9.3.1 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 8.2.1 + * Version: 8.2.2 * Requires Plugins: woocommerce * * @package WooCommerce\Payments
'; - // No need to escape here as the contents of $message is already escaped. + // No need to escape here as the function called handles it. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo $message; + echo \WC_Payments_Utils::esc_interpolated_html( + $message, + [ + 'a' => '', + ] + ); echo ' ' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . '
diff --git a/multi-currency/src/StorefrontIntegration.php b/includes/multi-currency/StorefrontIntegration.php similarity index 100% rename from multi-currency/src/StorefrontIntegration.php rename to includes/multi-currency/StorefrontIntegration.php diff --git a/multi-currency/src/Tracking.php b/includes/multi-currency/Tracking.php similarity index 100% rename from multi-currency/src/Tracking.php rename to includes/multi-currency/Tracking.php diff --git a/multi-currency/src/UserSettings.php b/includes/multi-currency/UserSettings.php similarity index 100% rename from multi-currency/src/UserSettings.php rename to includes/multi-currency/UserSettings.php diff --git a/multi-currency/src/Utils.php b/includes/multi-currency/Utils.php similarity index 71% rename from multi-currency/src/Utils.php rename to includes/multi-currency/Utils.php index 1c3fd074076..64e5356a77a 100644 --- a/multi-currency/src/Utils.php +++ b/includes/multi-currency/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() && ! self::is_store_api_request(); + return 0 === stripos( wp_get_referer(), admin_url() ) && WC()->is_rest_api_request() && ! \WC_Payments_Utils::is_store_api_request(); } @@ -71,21 +71,4 @@ 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/includes/compat/multi-currency/wc-payments-multi-currency.php b/includes/multi-currency/wc-payments-multi-currency.php similarity index 80% rename from includes/compat/multi-currency/wc-payments-multi-currency.php rename to includes/multi-currency/wc-payments-multi-currency.php index 0d0775e7d62..fef6e10db81 100644 --- a/includes/compat/multi-currency/wc-payments-multi-currency.php +++ b/includes/multi-currency/wc-payments-multi-currency.php @@ -29,25 +29,12 @@ function wcpay_multi_currency_onboarding_check() { } /** - * Returns the MultiCurrency singleton. + * Returns the main instance of MultiCurrency. * * @return WCPay\MultiCurrency\MultiCurrency */ function WC_Payments_Multi_Currency() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid - static $instance = null; - - if ( is_null( $instance ) ) { - $instance = new WCPay\MultiCurrency\MultiCurrency( - WC_Payments::get_context_for_multi_currency(), - WC_Payments::get_payments_api_client(), - WC_Payments::get_account_service(), - WC_Payments::get_localization_service(), - WC_Payments::get_database_cache() - ); - $instance->init_hooks(); - } - - return $instance; + return WCPay\MultiCurrency\MultiCurrency::instance(); } add_action( 'plugins_loaded', 'WC_Payments_Multi_Currency', 12 ); 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 13b25e07dd6..50aabec994c 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -21,12 +21,11 @@ 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 implements MultiCurrencyApiClientInterface { +class WC_Payments_API_Client { const ENDPOINT_BASE = 'https://public-api.wordpress.com/wpcom/v2'; const ENDPOINT_SITE_FRAGMENT = 'sites/%s'; @@ -192,7 +191,7 @@ public function __construct( $user_agent, $http_client, $wcpay_db ) { * * @return bool */ - public function is_server_connected(): bool { + public function is_server_connected() { return $this->http_client->is_connected(); } @@ -858,7 +857,7 @@ function ( $a, $b ) { * * @throws API_Exception - Error contacting the API. */ - public function get_currency_rates( string $currency_from, $currencies_to = null ): array { + public function get_currency_rates( string $currency_from, $currencies_to = null ) { if ( empty( $currency_from ) ) { throw new API_Exception( __( 'Currency From parameter is required', 'woocommerce-payments' ), diff --git a/multi-currency/client/data/constants.js b/multi-currency/client/data/constants.js deleted file mode 100644 index eaee38c6ee6..00000000000 --- a/multi-currency/client/data/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @format */ - -export const NAMESPACE = '/wc/v3/payments'; -export const STORE_NAME = 'wc/payments/multi-currency'; diff --git a/multi-currency/client/data/index.ts b/multi-currency/client/data/index.ts deleted file mode 100644 index f1d8d84a456..00000000000 --- a/multi-currency/client/data/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** @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/multi-currency/client/data/store.js b/multi-currency/client/data/store.js deleted file mode 100644 index 2e142e66f9c..00000000000 --- a/multi-currency/client/data/store.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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/multi-currency/client/interface/assets.js b/multi-currency/client/interface/assets.js deleted file mode 100644 index 3a3a095891a..00000000000 --- a/multi-currency/client/interface/assets.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 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 deleted file mode 100644 index eddab5ae84e..00000000000 --- a/multi-currency/client/interface/components.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 deleted file mode 100644 index af162ddd6ed..00000000000 --- a/multi-currency/client/interface/data.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 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 deleted file mode 100644 index 5ffed26e00d..00000000000 --- a/multi-currency/client/interface/functions.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 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/multi-currency/src/CountryFlags.php b/multi-currency/src/CountryFlags.php deleted file mode 100644 index 286e77dfbe2..00000000000 --- a/multi-currency/src/CountryFlags.php +++ /dev/null @@ -1,301 +0,0 @@ - '🇦🇩', - '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/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php b/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php deleted file mode 100644 index b827c5ca3d8..00000000000 --- a/multi-currency/src/Interfaces/MultiCurrencyAccountInterface.php +++ /dev/null @@ -1,61 +0,0 @@ -log( $level, $message, [ 'source' => self::LOG_FILE ] ); - } -} diff --git a/package-lock.json b/package-lock.json index fb0a1965e2a..0c5635e6817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index fd6d97fe3f9..5b525252554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "8.2.1", + "version": "8.2.2", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index a0bf916f7a9..6e21606d2bb 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -102,7 +102,7 @@ tests/* - multi-currency/src + includes/multi-currency src diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1eb5fab3f04..2a5164e8af3 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/readme.txt b/readme.txt index c4f1b121e3c..219da74fc9a 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.6 Requires PHP: 7.3 -Stable tag: 8.2.1 +Stable tag: 8.2.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,10 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 8.2.2 - 2024-09-24 = +* Fix - Fix WooPay pre-checking place order bug when buying a subscription. + + = 8.2.1 - 2024-09-13 = * Fix - Create div container element with JS dynamically. diff --git a/tasks/release.js b/tasks/release.js index 72c3eccdef7..92559af4ffb 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -14,11 +14,11 @@ const targetFolder = 'release/' + pluginSlug; const filesToCopy = [ 'assets', 'dist', - 'i18n', 'includes', + 'i18n', 'languages', - 'lib', 'src', + 'lib', 'templates', 'vendor', 'woocommerce-payments.php', @@ -43,10 +43,6 @@ 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/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', - } ); - } ); -} ); 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"]' + ); } ); } ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index 03060196d4c..18c11bc9553 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -2,18 +2,13 @@ const { jsWithBabel: tsjPreset } = require( 'ts-jest/presets' ); module.exports = { rootDir: '../../', - moduleDirectories: [ - 'node_modules', - '/client', - '/multi-currency/client', - ], + moduleDirectories: [ 'node_modules', '/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/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index d84f4b680ba..d68c5c1f82e 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 @@ -377,11 +377,13 @@ public function test_update_settings_saves_enabled_payment_methods() { WC_Payments::get_gateway()->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); $request = new WP_REST_Request(); + + // Ideal will not have the capability status, 'active', and will therefore not added to the list of enabled payment methods. $request->set_param( 'enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::IDEAL ] ); $this->controller->update_settings( $request ); - $this->assertEquals( [ Payment_Method::CARD, Payment_Method::IDEAL ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); + $this->assertEquals( [ Payment_Method::CARD ], WC_Payments::get_gateway()->get_option( 'upe_enabled_payment_method_ids' ) ); } public function test_update_settings_fails_if_user_cannot_manage_woocommerce() { @@ -550,7 +552,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/multi-currency/compatibility/test-class-woocommerce-subscriptions.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php index 5edd60cd8a6..5b71b8f8e7f 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-subscriptions.php @@ -6,10 +6,6 @@ */ 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; @@ -66,16 +62,7 @@ class WCPay_Multi_Currency_WooCommerceSubscriptions_Tests extends WCPAY_UnitTest public function set_up() { parent::set_up(); - $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_multi_currency = $this->createMock( MultiCurrency::class ); $this->mock_utils = $this->createMock( Utils::class ); $this->woocommerce_subscriptions = new WooCommerceSubscriptions( $this->mock_multi_currency, $this->mock_utils ); @@ -841,11 +828,18 @@ 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 e434ae9f661..5a985dd0511 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,7 +6,6 @@ */ use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; /** * Class Note_Multi_Currency_Available_Test tests. @@ -56,8 +55,8 @@ public function test_possibly_add_note_without_account() { } public function test_possibly_add_note_with_account_not_connected() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( false ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_connected' )->willReturn( false ); NoteMultiCurrencyAvailable::set_account( $account_mock ); NoteMultiCurrencyAvailable::possibly_add_note(); @@ -66,8 +65,8 @@ public function test_possibly_add_note_with_account_not_connected() { } public function test_possibly_add_note_with_connected_account() { - $account_mock = $this->createMock( MultiCurrencyAccountInterface::class ); - $account_mock->method( 'is_provider_connected' )->willReturn( true ); + $account_mock = $this->createMock( WC_Payments_Account::class ); + $account_mock->method( 'is_stripe_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 02c719ca64b..00b46e2d7ef 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 ); - $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 = $this->createMock( MultiCurrency::class ); $this->mock_multi_currency->expects( $this->any() ) ->method( 'get_all_customer_currencies' ) @@ -98,10 +84,7 @@ public function set_up() { $this->analytics = new Analytics( $this->mock_multi_currency ); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); - $this->mock_localization_service->expects( $this->any() ) - ->method( 'get_currency_format' ) - ->willReturn( [ 'num_decimals' => 2 ] ); + $this->localization_service = new WC_Payments_Localization_Service(); remove_filter( 'user_has_cap', $cb ); } @@ -181,7 +164,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 14.00 ); $order = wc_create_order(); @@ -196,7 +179,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 130500.75, 20000, 10000, 100500.75 ); $order = wc_create_order(); @@ -211,7 +194,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $args = $this->order_args_provider( 123, 0, 1, 15.50, 1.50, 0, 15.00 ); $order = wc_create_order(); @@ -604,19 +587,14 @@ private function create_can_manage_woocommerce_cap_override( bool $can_manage_wo } private function get_mock_available_currencies() { - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->localization_service = new WC_Payments_Localization_Service(); 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->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 ), + '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 ), ]; } diff --git a/tests/unit/multi-currency/test-class-backend-currencies.php b/tests/unit/multi-currency/test-class-backend-currencies.php index 8b333c6f189..530efc71b7d 100644 --- a/tests/unit/multi-currency/test-class-backend-currencies.php +++ b/tests/unit/multi-currency/test-class-backend-currencies.php @@ -6,7 +6,6 @@ */ use WCPay\MultiCurrency\BackendCurrencies; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; /** @@ -14,9 +13,9 @@ */ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { /** - * Mock MultiCurrencyLocalizationInterface. + * Mock WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface|PHPUnit_Framework_MockObject_MockObject + * @var WC_Payments_Localization_Service|PHPUnit_Framework_MockObject_MockObject */ private $mock_localization_service; @@ -37,7 +36,7 @@ class WCPay_Multi_Currency_Backend_Currencies_Tests extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_localization_service = $this->createMock( MultiCurrencyLocalizationInterface::class ); + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::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 ae4cc75dc31..cefb478ca50 100644 --- a/tests/unit/multi-currency/test-class-compatibility.php +++ b/tests/unit/multi-currency/test-class-compatibility.php @@ -7,7 +7,6 @@ use WCPay\MultiCurrency\Compatibility; use WCPay\MultiCurrency\Currency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\MultiCurrency; use WCPay\MultiCurrency\Utils; @@ -37,11 +36,11 @@ class WCPay_Multi_Currency_Compatibility_Tests extends WCPAY_UnitTestCase { private $mock_utils; /** - * MultiCurrencyLocalizationInterface. + * WC_Payments_Localization_Service. * - * @var MultiCurrencyLocalizationInterface + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $localization_service; /** * Pre-test setup @@ -49,15 +48,10 @@ 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->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 ); + $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(); } public function test_init_compatibility_classes_does_not_add_classes_if_one_enabled_currencies() { @@ -114,7 +108,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -138,7 +132,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -159,7 +153,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->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->localization_service, 'USD', 1.0 ) ); $this->mock_utils->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) @@ -183,7 +177,7 @@ public function test_filter_woocommerce_order_query() { $this->mock_multi_currency->expects( $this->once() ) ->method( 'get_default_currency' ) - ->willReturn( new Currency( $this->mock_localization_service, 'USD', 1.0 ) ); + ->willReturn( new Currency( $this->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 cd3adff8feb..0ebe63fb8ef 100644 --- a/tests/unit/multi-currency/test-class-country-flags.php +++ b/tests/unit/multi-currency/test-class-country-flags.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\CountryFlags; /** @@ -12,7 +13,7 @@ */ class Country_Flags_Test extends WCPAY_UnitTestCase { public function test_get_by_country_returns_emoji_flag() { - $this->assertEquals( CountryFlags::get_by_country( 'US' ), '🇺🇸' ); + $this->assertEquals( CountryFlags::get_by_country( Country_Code::UNITED_STATES ), '🇺🇸' ); } 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 2ac0a69fa11..f716f6dbe75 100644 --- a/tests/unit/multi-currency/test-class-currency-switcher-block.php +++ b/tests/unit/multi-currency/test-class-currency-switcher-block.php @@ -10,7 +10,6 @@ use WCPay\MultiCurrency\Currency; use WCPay\MultiCurrency\CurrencySwitcherBlock; use WCPay\MultiCurrency\MultiCurrency; -use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; /** * CurrencySwitcherBlock unit tests. @@ -38,25 +37,23 @@ class WCPay_Multi_Currency_Currency_Switcher_Block_Tests extends WCPAY_UnitTestC protected $mock_currencies; /** - * @var MockObject\MultiCurrencyLocalizationInterface + * WC_Payments_Localization_Service. + * + * @var WC_Payments_Localization_Service */ - private $mock_localization_service; + private $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->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->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->currency_switcher_block = new CurrencySwitcherBlock( @@ -222,8 +219,8 @@ public function test_render_currency_option_will_escape_output() { ->method( 'get_enabled_currencies' ) ->willReturn( [ - new Currency( $this->mock_localization_service, 'USD' ), - new Currency( $this->mock_localization_service, $currency_code, 1 ), + new Currency( $this->localization_service, 'USD' ), + new Currency( $this->localization_service, $currency_code, 1 ), ] ); @@ -278,7 +275,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->mock_localization_service, 'USD' ) ] ); + ->willReturn( [ new Currency( $this->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 13d6b4bb34d..c38d343dcc9 100644 --- a/tests/unit/multi-currency/test-class-frontend-prices.php +++ b/tests/unit/multi-currency/test-class-frontend-prices.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\FrontendPrices unit tests. */ @@ -214,7 +216,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; @@ -258,7 +260,7 @@ function () { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, '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 92b0b50b166..b66543dbf67 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\Geolocation unit tests. */ @@ -49,10 +51,10 @@ public function test_get_country_by_customer_location_returns_geolocation_countr add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); - $this->assertSame( 'CA', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::CANADA, $this->geolocation->get_country_by_customer_location() ); } public function test_get_country_by_customer_location_returns_default_country_when_no_geolocation() { @@ -66,20 +68,20 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); - $this->assertSame( 'BR', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::BRAZIL, $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( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -87,7 +89,7 @@ function () { } public function test_get_currency_by_customer_location_returns_default_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'BR' )->willReturn( [ 'currency_code' => 'BRL' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::BRAZIL )->willReturn( [ 'currency_code' => 'BRL' ] ); add_filter( 'woocommerce_geolocate_ip', @@ -98,7 +100,7 @@ function () { add_filter( 'woocommerce_customer_default_location', function () { - return 'BR'; + return Country_Code::BRAZIL; } ); diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index 70487b835ee..023fd92868c 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -5,12 +5,11 @@ * @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; @@ -66,14 +65,14 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { /** * Mock of the API client. * - * @var MultiCurrencyApiClientInterface + * @var WC_Payments_API_Client */ private $mock_api_client; /** - * Mock of the MultiCurrencyAccountInterface. + * Mock of the WC_Payments_Account. * - * @var MultiCurrencyAccountInterface + * @var WC_Payments_Account */ private $mock_account; @@ -85,11 +84,11 @@ class WCPay_Multi_Currency_Tests extends WCPAY_UnitTestCase { private $localization_service; /** - * Mock of MultiCurrencyCacheInterface. + * Mock of Database_Cache. * - * @var MultiCurrencyCacheInterface; + * @var Database_Cache; */ - private $mock_cache; + private $mock_database_cache; /** * Mock of Utils. @@ -457,7 +456,7 @@ public function test_update_selected_currency_by_geolocation_does_not_set_sessio add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -474,7 +473,7 @@ public function test_update_selected_currency_by_geolocation_updates_session_whe add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -489,7 +488,7 @@ public function test_update_selected_currency_by_geolocation_displays_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -510,7 +509,7 @@ public function test_update_selected_currency_by_geolocation_does_not_update_if_ add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -535,7 +534,7 @@ public function test_display_geolocation_currency_update_notice() { add_filter( 'woocommerce_geolocate_ip', function () { - return 'CA'; + return Country_Code::CANADA; } ); @@ -545,11 +544,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, 'US' ); + WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, Country_Code::UNITED_STATES ); add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -563,7 +562,7 @@ public function test_display_geolocation_currency_update_notice_does_not_display add_filter( 'woocommerce_geolocate_ip', function () { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -722,13 +721,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( MultiCurrencyApiClientInterface::class ); + $mock_api_client = $this->createMock( WC_Payments_API_Client::class ); $mock_api_client->method( 'is_server_connected' )->willReturn( false ); $this->init_multi_currency( $mock_api_client ); - $this->mock_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, @@ -737,7 +736,7 @@ public function test_get_cached_currencies_with_no_server_connection() { } public function test_get_cached_currencies_with_account_rejected() { - $this->mock_cache + $this->mock_database_cache ->expects( $this->once() ) ->method( 'get' ) ->willReturn( null ); @@ -747,7 +746,7 @@ public function test_get_cached_currencies_with_account_rejected() { ->method( 'is_account_rejected' ) ->willReturn( true ); - $this->mock_cache + $this->mock_database_cache ->expects( $this->never() ) ->method( 'get_or_add' ); @@ -759,11 +758,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->exactly( 2 ) ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturnCallback( function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { if ( 1 === $get_or_add_call_count ) { @@ -777,7 +776,7 @@ function ( $key, $generator, $validator ) use ( &$get_or_add_call_count ) { } ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $currency_from = strtolower( get_woocommerce_currency() ); $currencies_to = get_woocommerce_currencies(); @@ -887,7 +886,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_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache->method( 'get' )->willReturn( $this->mock_cached_currencies ); $this->assertEquals( $this->mock_cached_currencies, $this->multi_currency->get_cached_currencies() @@ -1052,14 +1051,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1074,14 +1073,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1104,14 +1103,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_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $mock_cache + $mock_database_cache = $this->createMock( Database_Cache::class ); + $mock_database_cache ->expects( $this->once() ) ->method( 'get_or_add' ) - ->with( MultiCurrencyCacheInterface::CURRENCIES_KEY, $this->anything(), $this->anything() ) + ->with( Database_Cache::CURRENCIES_KEY, $this->anything(), $this->anything() ) ->willReturn( $this->mock_cached_currencies ); - $this->init_multi_currency( null, true, null, $mock_cache ); + $this->init_multi_currency( null, true, null, $mock_database_cache ); $result = $this->multi_currency->get_all_customer_currencies(); @@ -1415,32 +1414,31 @@ 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_cache = null ) { - $this->mock_api_client = $this->createMock( MultiCurrencyApiClientInterface::class ); + 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 ); - $this->mock_account = $mock_account ?? $this->createMock( MultiCurrencyAccountInterface::class ); - $this->mock_account->method( 'is_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account = $mock_account ?? $this->createMock( WC_Payments_Account::class ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_api_client->method( 'is_server_connected' )->willReturn( true ); - $this->mock_cache = $this->createMock( MultiCurrencyCacheInterface::class ); - $this->mock_cache->method( 'get_or_add' )->willReturn( $this->mock_cached_currencies ); + $this->mock_database_cache = $this->createMock( Database_Cache::class ); + $this->mock_database_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_cache ?? $this->mock_cache, + $mock_database_cache ?? $this->mock_database_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/test-class-wc-payments-currency-manager.php b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php similarity index 84% rename from tests/unit/test-class-wc-payments-currency-manager.php rename to tests/unit/multi-currency/test-class-payment-methods-compatibility.php index 6f13664ec75..bdbbb767cde 100644 --- a/tests/unit/test-class-wc-payments-currency-manager.php +++ b/tests/unit/multi-currency/test-class-payment-methods-compatibility.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( [ @@ -67,16 +68,8 @@ public function set_up() { ->getMock(); $this->gateway_mock->method( 'get_account_country' )->willReturn( 'US' ); - $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->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); + $this->payment_methods_compatibility->init_hooks(); $this->localization_service = new WC_Payments_Localization_Service(); } @@ -86,7 +79,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_when_not_needed() { @@ -114,7 +107,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_when_needed() { @@ -154,7 +147,7 @@ public function test_it_should_update_available_currencies_when_needed() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_not_update_available_currencies_with_bnpl_methods() { @@ -180,7 +173,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->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } public function test_it_should_update_available_currencies_with_bnpl_methods() { @@ -214,6 +207,6 @@ public function test_it_should_update_available_currencies_with_bnpl_methods() { ) ); - $this->currency_manager->maybe_add_missing_currencies(); + $this->payment_methods_compatibility->add_missing_currencies(); } } diff --git a/tests/unit/multi-currency/test-class-rest-controller.php b/tests/unit/multi-currency/test-class-rest-controller.php index dfe581cde25..49ac60ad1be 100644 --- a/tests/unit/multi-currency/test-class-rest-controller.php +++ b/tests/unit/multi-currency/test-class-rest-controller.php @@ -5,7 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; use WCPay\MultiCurrency\RestController; /** @@ -34,7 +33,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( MultiCurrencyApiClientInterface::class )->disableOriginalConstructor()->getMock(); + $mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )->disableOriginalConstructor()->getMock(); $this->controller = new RestController( $mock_api_client ); } @@ -83,8 +82,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/update-enabled-currencies' ); @@ -129,8 +129,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'GET', self::ROUTE . '/currencies/AAA' ); @@ -190,8 +191,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $error_message ) ); // Arrange: Create the new REST request. $request = new WP_REST_Request( 'POST', self::ROUTE . '/currencies/AAA' ); @@ -218,8 +220,9 @@ 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( 500, $error_message ) ); + $expected = rest_ensure_response( new WP_Error( $error_code, $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 272a691910b..1ccbd373a0c 100644 --- a/tests/unit/multi-currency/test-class-settings.php +++ b/tests/unit/multi-currency/test-class-settings.php @@ -5,6 +5,8 @@ * @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 553df2a0cc1..1f3c9cce762 100644 --- a/tests/unit/multi-currency/test-class-utils.php +++ b/tests/unit/multi-currency/test-class-utils.php @@ -60,7 +60,8 @@ 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/'; - $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ) . 'wc/store/v1/checkout'; + $_REQUEST['rest_route'] = '/wc/store/v1/checkout'; + $_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() ); $this->assertFalse( $this->utils->is_admin_api_request() ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index e14803b6812..b97fad22eea 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -2789,6 +2789,7 @@ function ( $order ) { } public function test_gateway_enabled_when_payment_method_is_enabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2797,6 +2798,7 @@ public function test_gateway_enabled_when_payment_method_is_enabled() { } public function test_gateway_disabled_when_payment_method_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'yes' ); $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); $this->prepare_gateway_for_availability_testing( $afterpay ); @@ -2804,6 +2806,15 @@ public function test_gateway_disabled_when_payment_method_is_disabled() { $this->assertFalse( $afterpay->is_available() ); } + public function test_gateway_disabled_when_card_gateway_is_disabled() { + $this->card_gateway->update_option( 'enabled', 'no' ); + $afterpay = $this->get_gateway( Payment_Method::AFTERPAY ); + $afterpay->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::AFTERPAY, Payment_Method::CARD, Payment_Method::P24, Payment_Method::BANCONTACT ] ); + $this->prepare_gateway_for_availability_testing( $afterpay ); + + $this->assertFalse( $afterpay->is_available() ); + } + public function test_process_payment_for_order_cc_payment_method() { $payment_method = 'woocommerce_payments'; $expected_upe_payment_method_for_pi_creation = 'card'; @@ -3943,7 +3954,7 @@ private function create_charge_object() { private function prepare_gateway_for_availability_testing( $gateway ) { WC_Payments::mode()->test(); $current_currency = strtolower( get_woocommerce_currency() ); - $this->mock_wcpay_account->expects( $this->once() )->method( 'get_account_customer_supported_currencies' )->will( + $this->mock_wcpay_account->expects( $this->any() )->method( 'get_account_customer_supported_currencies' )->will( $this->returnValue( [ $current_currency, 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-explicit-price-formatter.php b/tests/unit/test-class-wc-payments-explicit-price-formatter.php index b01ce3092d2..9c32f3afdb5 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_provider_connected' )->willReturn( $wcpay_account_connected ); + $this->mock_account->method( 'is_stripe_connected' )->willReturn( $wcpay_account_connected ); $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); @@ -248,10 +248,7 @@ 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 ); - $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 = new MultiCurrency( $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/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-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( 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', diff --git a/tsconfig.json b/tsconfig.json index f7f56cddd52..3b9efabf45e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,8 @@ "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 55c249694ea..7ef039967cd 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': './multi-currency/client/index.js', + 'multi-currency': './client/multi-currency/index.js', 'multi-currency-switcher-block': - './multi-currency/client/blocks/currency-switcher.js', + './client/multi-currency/blocks/currency-switcher.js', 'multi-currency-analytics': - './multi-currency/client/analytics/index.js', + './client/multi-currency-analytics/index.js', order: './client/order/index.js', 'subscriptions-empty-state': './client/subscriptions-empty-state/index.js', @@ -113,18 +113,9 @@ module.exports = { }, resolve: { extensions: [ '.ts', '.tsx', '.json', '.js', '.jsx' ], - modules: [ - path.join( process.cwd(), 'client' ), - path.join( process.cwd(), 'multi-currency', 'client' ), - 'node_modules', - ], + modules: [ path.join( process.cwd(), '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(), diff --git a/woocommerce-payments.php b/woocommerce-payments.php index c079725904c..825bb4c8f18 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 9.3.1 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 8.2.1 + * Version: 8.2.2 * Requires Plugins: woocommerce * * @package WooCommerce\Payments
WC_Subscriptions_Product
\WC_Product_Addons_Helper
\WC_Name_Your_Price_Helpers