From e6f80ba05cfa4706ee3a92373be5eadec0878eb2 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 5 Dec 2023 13:47:17 -0300 Subject: [PATCH 01/56] Revemoved pre-fretch session for button (#7764) Co-authored-by: Brett Shumaker --- changelog/revert-prefetch-session-for-button | 4 ++++ .../woopay-express-checkout-button.test.js | 12 ++++------ .../woopay-express-checkout-button.js | 24 ++++++++----------- 3 files changed, 18 insertions(+), 22 deletions(-) create mode 100644 changelog/revert-prefetch-session-for-button diff --git a/changelog/revert-prefetch-session-for-button b/changelog/revert-prefetch-session-for-button new file mode 100644 index 00000000000..bcb2e97e61e --- /dev/null +++ b/changelog/revert-prefetch-session-for-button @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Revemoved pre-fretch session for button to prevent draft order creation diff --git a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js index 5fd5b1d3235..64e804fb30b 100644 --- a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js +++ b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js @@ -82,7 +82,7 @@ describe( 'WoopayExpressCheckoutButton', () => { ).toBeInTheDocument(); } ); - test( 'prefetch session data by default', async () => { + test( 'does not prefetch session data by default', async () => { getConfig.mockImplementation( ( v ) => { switch ( v ) { case 'wcAjaxUrl': @@ -104,14 +104,12 @@ describe( 'WoopayExpressCheckoutButton', () => { ); await waitFor( () => { - expect( request ).toHaveBeenCalledWith( 'woopay.url', { - _ajax_nonce: 'sessionnonce', - } ); + expect( request ).not.toHaveBeenCalled(); expect( expressCheckoutIframe ).not.toHaveBeenCalled(); } ); } ); - test( 'request session data on button click', async () => { + test( 'should not request session data on button click', async () => { getConfig.mockImplementation( ( v ) => { switch ( v ) { case 'wcAjaxUrl': @@ -138,9 +136,7 @@ describe( 'WoopayExpressCheckoutButton', () => { userEvent.click( expressButton ); await waitFor( () => { - expect( request ).toHaveBeenCalledWith( 'woopay.url', { - _ajax_nonce: 'sessionnonce', - } ); + expect( request ).not.toHaveBeenCalled(); expect( expressCheckoutIframe ).not.toHaveBeenCalled(); } ); } ); diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index a7b26ef4853..b7ccb0adade 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -41,7 +41,6 @@ export const WoopayExpressCheckoutButton = ( { narrow: 'narrow', wide: 'wide', }; - const sessionDataPromiseRef = useRef( null ); const initWoopayRef = useRef( null ); const buttonRef = useRef( null ); const initialOnClickEventRef = useRef( null ); @@ -305,9 +304,16 @@ export const WoopayExpressCheckoutButton = ( { } ); } ); } else { - // Non-product pages already have pre-fetched session data. - sessionDataPromiseRef.current - ?.then( ( response ) => { + request( + buildAjaxURL( + getConfig( 'wcAjaxUrl' ), + 'get_woopay_session' + ), + { + _ajax_nonce: getConfig( 'woopaySessionNonce' ), + } + ) + .then( ( response ) => { iframe.contentWindow.postMessage( { action: 'setPreemptiveSessionData', @@ -349,16 +355,6 @@ export const WoopayExpressCheckoutButton = ( { return; } - if ( ! isProductPage ) { - // Start to pre-fetch session data for non-product pages. - sessionDataPromiseRef.current = request( - buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'get_woopay_session' ), - { - _ajax_nonce: getConfig( 'woopaySessionNonce' ), - } - ).then( ( response ) => response ); - } - buttonRef.current.parentElement.style.position = 'relative'; buttonRef.current.parentElement.appendChild( newIframe() ); From abea32fd1ff6381f94f5c5442df6fa3f3f719db6 Mon Sep 17 00:00:00 2001 From: Hsing-yu Flowers Date: Tue, 5 Dec 2023 13:34:45 -0500 Subject: [PATCH 02/56] Pass the pay-for-order params to get the pre-fetch session data. (#7797) --- ...-pay-for-order-and-first-party-auth-compatibility | 4 ++++ .../test/woopay-express-checkout-button.test.js | 12 ++++++++++++ .../express-button/woopay-express-checkout-button.js | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 changelog/fix-pay-for-order-and-first-party-auth-compatibility diff --git a/changelog/fix-pay-for-order-and-first-party-auth-compatibility b/changelog/fix-pay-for-order-and-first-party-auth-compatibility new file mode 100644 index 00000000000..26182c064d4 --- /dev/null +++ b/changelog/fix-pay-for-order-and-first-party-auth-compatibility @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Pass the pay-for-order params to get the pre-fetch session data diff --git a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js index 64e804fb30b..8bfcaf75482 100644 --- a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js +++ b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js @@ -89,6 +89,12 @@ describe( 'WoopayExpressCheckoutButton', () => { return 'woopay.url'; case 'woopaySessionNonce': return 'sessionnonce'; + case 'billing_email': + return 'test@test.com'; + case 'key': + return 'testkey'; + case 'order_id': + return 1; default: return 'foo'; } @@ -116,6 +122,12 @@ describe( 'WoopayExpressCheckoutButton', () => { return 'woopay.url'; case 'woopaySessionNonce': return 'sessionnonce'; + case 'billing_email': + return 'test@test.com'; + case 'key': + return 'testkey'; + case 'order_id': + return 1; default: return 'foo'; } diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index b7ccb0adade..24413105304 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -311,6 +311,9 @@ export const WoopayExpressCheckoutButton = ( { ), { _ajax_nonce: getConfig( 'woopaySessionNonce' ), + order_id: getConfig( 'order_id' ), + key: getConfig( 'key' ), + billing_email: getConfig( 'billing_email' ), } ) .then( ( response ) => { From bb63e3c163b076d568ec3319f899943158a35032 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Tue, 5 Dec 2023 20:11:30 +0100 Subject: [PATCH 03/56] Remove legacy UPE and card front-end scripts and the corresponding code for script registration (#7795) Co-authored-by: Timur Karimov --- .../partially-ccleanup-legacy-upe-and-card | 4 + client/checkout/blocks/fields.js | 138 ---- client/checkout/blocks/index.js | 139 +++- .../payment-elements.js | 7 +- .../payment-processor.js | 6 +- .../test/payment-processor.test.js | 11 +- client/checkout/blocks/upe-fields.js | 378 --------- client/checkout/blocks/upe-split-fields.js | 436 ---------- client/checkout/blocks/upe-split.js | 123 --- client/checkout/blocks/upe.js | 62 -- .../3ds-flow-handling.js | 0 .../event-handlers.js | 5 +- client/checkout/classic/index.js | 561 ------------- .../payment-processing.js | 4 +- client/checkout/classic/style.scss | 30 - .../test/3ds-flow-handling.test.js | 0 .../test/payment-processing.test.js | 4 +- .../checkout/classic/test/upe-split.test.js | 192 ----- client/checkout/classic/test/upe.test.js | 44 - client/checkout/classic/upe-split.js | 772 ------------------ client/checkout/classic/upe.js | 759 ----------------- client/checkout/utils/test/upe.test.js | 132 +++ includes/class-wc-payments-checkout.php | 29 - includes/class-wc-payments-upe-checkout.php | 2 +- webpack/shared.js | 9 +- 25 files changed, 251 insertions(+), 3596 deletions(-) create mode 100644 changelog/partially-ccleanup-legacy-upe-and-card delete mode 100644 client/checkout/blocks/fields.js rename client/checkout/blocks/{upe-deferred-intent-creation => }/payment-elements.js (90%) rename client/checkout/blocks/{upe-deferred-intent-creation => }/payment-processor.js (97%) rename client/checkout/blocks/{upe-deferred-intent-creation => }/test/payment-processor.test.js (96%) delete mode 100644 client/checkout/blocks/upe-fields.js delete mode 100644 client/checkout/blocks/upe-split-fields.js delete mode 100644 client/checkout/blocks/upe-split.js delete mode 100644 client/checkout/blocks/upe.js rename client/checkout/classic/{upe-deferred-intent-creation => }/3ds-flow-handling.js (100%) rename client/checkout/classic/{upe-deferred-intent-creation => }/event-handlers.js (97%) delete mode 100644 client/checkout/classic/index.js rename client/checkout/classic/{upe-deferred-intent-creation => }/payment-processing.js (99%) rename client/checkout/classic/{upe-deferred-intent-creation => }/test/3ds-flow-handling.test.js (100%) rename client/checkout/classic/{upe-deferred-intent-creation => }/test/payment-processing.test.js (99%) delete mode 100644 client/checkout/classic/test/upe-split.test.js delete mode 100644 client/checkout/classic/test/upe.test.js delete mode 100644 client/checkout/classic/upe-split.js delete mode 100644 client/checkout/classic/upe.js diff --git a/changelog/partially-ccleanup-legacy-upe-and-card b/changelog/partially-ccleanup-legacy-upe-and-card new file mode 100644 index 00000000000..9e5386f594e --- /dev/null +++ b/changelog/partially-ccleanup-legacy-upe-and-card @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Cleanup the deprecated payment gateway processing - part II diff --git a/client/checkout/blocks/fields.js b/client/checkout/blocks/fields.js deleted file mode 100644 index c89c34888bc..00000000000 --- a/client/checkout/blocks/fields.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * External dependencies - */ -import { - Elements, - ElementsConsumer, - CardElement, -} from '@stripe/react-stripe-js'; -import { useEffect, useState } from '@wordpress/element'; -import { getConfig } from 'utils/checkout'; - -/** - * Internal dependencies - */ -import './style.scss'; -import generatePaymentMethod from './generate-payment-method.js'; -import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; -import { useFingerprint, usePaymentCompleteHandler } from './hooks'; - -const WCPayFields = ( { - api, - activePaymentMethod, - stripe, - elements, - billing: { billingData }, - eventRegistration: { onPaymentSetup, onCheckoutSuccess }, - emitResponse, - shouldSavePayment, -} ) => { - const [ errorMessage, setErrorMessage ] = useState( null ); - const isTestMode = getConfig( 'testMode' ); - const testingInstructions = ( -

- Test mode: use the test VISA card 4242424242424242 - with any expiry date and CVC, or any test card numbers listed{ ' ' } - - here - - . -

- ); - - const [ fingerprint, fingerprintErrorMessage ] = useFingerprint(); - - useEffect( () => { - setErrorMessage( fingerprintErrorMessage ); - }, [ fingerprintErrorMessage ] ); - - // When it's time to process the payment, generate a Stripe payment method object. - useEffect( - () => - onPaymentSetup( () => { - if ( PAYMENT_METHOD_NAME_CARD !== activePaymentMethod ) { - return; - } - - if ( errorMessage ) { - return { - type: 'error', - message: errorMessage, - }; - } - - const cardElement = elements.getElement( CardElement ); - const paymentElements = { - type: 'card', - card: cardElement, - }; - - return generatePaymentMethod( - api, - paymentElements, - billingData, - fingerprint - ); - } ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ elements, stripe, activePaymentMethod, billingData, fingerprint ] - ); - - // Once the server has completed payment processing, confirm the intent of necessary. - usePaymentCompleteHandler( - api, - stripe, - elements, - onCheckoutSuccess, - emitResponse, - shouldSavePayment - ); - - // Checks whether there are errors within a field, and saves them for later reporting. - const checkForErrors = ( { error } ) => { - setErrorMessage( error ? error.message : null ); - }; - - const elementOptions = { - hidePostalCode: true, - classes: { - base: 'wcpay-card-mounted', - }, - }; - - return ( - <> - { isTestMode ? testingInstructions : '' } -
- -
- - ); -}; - -/** - * Wraps WCPayFields within the necessary Stripe consumer components. - * - * @param {Object} props All props given by WooCommerce Blocks. - * @return {Object} The wrapped React element. - */ -const ConsumableWCPayFields = ( { api, ...props } ) => ( - - - { ( { elements, stripe } ) => ( - - ) } - - -); - -export default ConsumableWCPayFields; diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index a9cf210c815..72f1c49b6b9 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -1,8 +1,6 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; - // Handled as an external dependency: see '/webpack.config.js:83' import { registerPaymentMethod, @@ -13,76 +11,127 @@ import { /** * Internal dependencies */ -import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; -import { getConfig } from 'utils/checkout'; -import WCPayAPI from './../api'; -import WCPayFields from './fields.js'; +import { getUPEConfig } from 'utils/checkout'; +import { isLinkEnabled } from '../utils/upe'; +import WCPayAPI from '../api'; import { SavedTokenHandler } from './saved-token-handler'; import request from '../utils/request'; import enqueueFraudScripts from 'fraud-scripts'; import paymentRequestPaymentMethod from '../../payment-request/blocks'; +import { + PAYMENT_METHOD_NAME_CARD, + PAYMENT_METHOD_NAME_BANCONTACT, + PAYMENT_METHOD_NAME_BECS, + PAYMENT_METHOD_NAME_EPS, + PAYMENT_METHOD_NAME_GIROPAY, + PAYMENT_METHOD_NAME_IDEAL, + PAYMENT_METHOD_NAME_P24, + PAYMENT_METHOD_NAME_SEPA, + PAYMENT_METHOD_NAME_SOFORT, + PAYMENT_METHOD_NAME_AFFIRM, + PAYMENT_METHOD_NAME_AFTERPAY, + PAYMENT_METHOD_NAME_KLARNA, +} from '../constants.js'; +import { getDeferredIntentCreationUPEFields } from './payment-elements'; import { handleWooPayEmailInput } from '../woopay/email-input-iframe'; import wooPayExpressCheckoutPaymentMethod from '../woopay/express-button/woopay-express-checkout-payment-method'; import { isPreviewing } from '../preview'; +const upeMethods = { + card: PAYMENT_METHOD_NAME_CARD, + bancontact: PAYMENT_METHOD_NAME_BANCONTACT, + au_becs_debit: PAYMENT_METHOD_NAME_BECS, + eps: PAYMENT_METHOD_NAME_EPS, + giropay: PAYMENT_METHOD_NAME_GIROPAY, + ideal: PAYMENT_METHOD_NAME_IDEAL, + p24: PAYMENT_METHOD_NAME_P24, + sepa_debit: PAYMENT_METHOD_NAME_SEPA, + sofort: PAYMENT_METHOD_NAME_SOFORT, + affirm: PAYMENT_METHOD_NAME_AFFIRM, + afterpay_clearpay: PAYMENT_METHOD_NAME_AFTERPAY, + klarna: PAYMENT_METHOD_NAME_KLARNA, +}; + +const enabledPaymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); +const isStripeLinkEnabled = isLinkEnabled( enabledPaymentMethodsConfig ); + // Create an API object, which will be used throughout the checkout. const api = new WCPayAPI( { - publishableKey: getConfig( 'publishableKey' ), - accountId: getConfig( 'accountId' ), - forceNetworkSavedCards: getConfig( 'forceNetworkSavedCards' ), - locale: getConfig( 'locale' ), + publishableKey: getUPEConfig( 'publishableKey' ), + accountId: getUPEConfig( 'accountId' ), + forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ), + locale: getUPEConfig( 'locale' ), + isUPEEnabled: getUPEConfig( 'isUPEEnabled' ), + isUPESplitEnabled: getUPEConfig( 'isUPESplitEnabled' ), + isUPEDeferredEnabled: getUPEConfig( 'isUPEDeferredEnabled' ), + isStripeLinkEnabled, }, request ); - -registerPaymentMethod( { - name: PAYMENT_METHOD_NAME_CARD, - content: , - edit: , - savedTokenComponent: , - canMakePayment: () => !! api.getStripe(), - paymentMethodId: PAYMENT_METHOD_NAME_CARD, - // see .wc-block-checkout__payment-method styles in blocks/style.scss - label: ( - <> - - { __( 'Credit card', 'woocommerce-payments' ) } - { - - - ), - ariaLabel: __( 'Credit card', 'woocommerce-payments' ), - supports: { - showSavedCards: getConfig( 'isSavedCardsEnabled' ) ?? false, - showSaveOption: - ( getConfig( 'isSavedCardsEnabled' ) && - ! getConfig( 'isWooPayEnabled' ) ) ?? - false, - features: getConfig( 'features' ), - }, -} ); +Object.entries( enabledPaymentMethodsConfig ) + .filter( ( [ upeName ] ) => upeName !== 'link' ) + .forEach( ( [ upeName, upeConfig ] ) => { + registerPaymentMethod( { + name: upeMethods[ upeName ], + content: getDeferredIntentCreationUPEFields( + upeName, + upeMethods, + api, + upeConfig.testingInstructions + ), + edit: getDeferredIntentCreationUPEFields( + upeName, + upeMethods, + api, + upeConfig.testingInstructions + ), + savedTokenComponent: , + canMakePayment: ( cartData ) => { + const billingCountry = cartData.billingAddress.country; + const isRestrictedInAnyCountry = !! upeConfig.countries.length; + const isAvailableInTheCountry = + ! isRestrictedInAnyCountry || + upeConfig.countries.includes( billingCountry ); + return ( + isAvailableInTheCountry && !! api.getStripeForUPE( upeName ) + ); + }, + paymentMethodId: upeMethods[ upeName ], + // see .wc-block-checkout__payment-method styles in blocks/style.scss + label: ( + <> + + { upeConfig.title } + { + + + ), + ariaLabel: 'WooPayments', + supports: { + showSavedCards: getUPEConfig( 'isSavedCardsEnabled' ) ?? false, + showSaveOption: upeConfig.showSaveOption ?? false, + features: getUPEConfig( 'features' ), + }, + } ); + } ); // Call handleWooPayEmailInput if woopay is enabled and this is the checkout page. -if ( getConfig( 'isWooPayEnabled' ) ) { +if ( getUPEConfig( 'isWooPayEnabled' ) ) { if ( document.querySelector( '[data-block-name="woocommerce/checkout"]' ) && - getConfig( 'isWooPayEmailInputEnabled' ) && + getUPEConfig( 'isWooPayEmailInputEnabled' ) && ! isPreviewing() ) { handleWooPayEmailInput( '#email', api, true ); } - if ( getConfig( 'isWoopayExpressCheckoutEnabled' ) ) { + if ( getUPEConfig( 'isWoopayExpressCheckoutEnabled' ) ) { registerExpressPaymentMethod( wooPayExpressCheckoutPaymentMethod() ); } } registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) ); - window.addEventListener( 'load', () => { - enqueueFraudScripts( getConfig( 'fraudServices' ) ); + enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); } ); diff --git a/client/checkout/blocks/upe-deferred-intent-creation/payment-elements.js b/client/checkout/blocks/payment-elements.js similarity index 90% rename from client/checkout/blocks/upe-deferred-intent-creation/payment-elements.js rename to client/checkout/blocks/payment-elements.js index 36abca3728f..e33346f4233 100644 --- a/client/checkout/blocks/upe-deferred-intent-creation/payment-elements.js +++ b/client/checkout/blocks/payment-elements.js @@ -1,9 +1,10 @@ /** * Internal dependencies */ -import { getAppearance } from 'wcpay/checkout/upe-styles'; +import './style.scss'; +import { getAppearance, getFontRulesFromPage } from 'wcpay/checkout/upe-styles'; import { getUPEConfig } from 'wcpay/utils/checkout'; -import { useFingerprint } from '../hooks'; +import { useFingerprint } from './hooks'; import { LoadableBlock } from 'wcpay/components/loadable'; import { Elements } from '@stripe/react-stripe-js'; import { useEffect, useState } from 'react'; @@ -16,6 +17,7 @@ const PaymentElements = ( { api, ...props } ) => { const [ appearance, setAppearance ] = useState( getUPEConfig( 'wcBlocksUPEAppearance' ) ); + const [ fontRules ] = useState( getFontRulesFromPage() ); const [ fingerprint, fingerprintErrorMessage ] = useFingerprint(); const amount = Number( getUPEConfig( 'cartTotal' ) ); const currency = getUPEConfig( 'currency' ).toLowerCase(); @@ -55,6 +57,7 @@ const PaymentElements = ( { api, ...props } ) => { paymentMethodCreation: 'manual', paymentMethodTypes: paymentMethodTypes, appearance: appearance, + fonts: fontRules, } } > { return { diff --git a/client/checkout/blocks/upe-deferred-intent-creation/test/payment-processor.test.js b/client/checkout/blocks/test/payment-processor.test.js similarity index 96% rename from client/checkout/blocks/upe-deferred-intent-creation/test/payment-processor.test.js rename to client/checkout/blocks/test/payment-processor.test.js index 421387c1dd6..c744c6d07b2 100644 --- a/client/checkout/blocks/upe-deferred-intent-creation/test/payment-processor.test.js +++ b/client/checkout/blocks/test/payment-processor.test.js @@ -9,17 +9,14 @@ import { useEffect } from 'react'; import PaymentProcessor from '../payment-processor'; import { PaymentElement } from '@stripe/react-stripe-js'; -jest.mock( - 'wcpay/checkout/classic/upe-deferred-intent-creation/payment-processing', - () => ( { - validateElements: jest.fn().mockResolvedValue(), - } ) -); +jest.mock( 'wcpay/checkout/classic/payment-processing', () => ( { + validateElements: jest.fn().mockResolvedValue(), +} ) ); jest.mock( 'wcpay/checkout/utils/upe', () => ( { ...jest.requireActual( 'wcpay/checkout/utils/upe' ), useCustomerData: jest.fn().mockReturnValue( { billingAddress: {} } ), } ) ); -jest.mock( '../../hooks', () => ( { +jest.mock( '../hooks', () => ( { usePaymentCompleteHandler: () => null, } ) ); jest.mock( '@woocommerce/blocks-registry', () => ( { diff --git a/client/checkout/blocks/upe-fields.js b/client/checkout/blocks/upe-fields.js deleted file mode 100644 index 2f8c06a7ccf..00000000000 --- a/client/checkout/blocks/upe-fields.js +++ /dev/null @@ -1,378 +0,0 @@ -/** - * External dependencies - */ -import { - Elements, - useStripe, - useElements, - PaymentElement, -} from '@stripe/react-stripe-js'; -import { - getPaymentMethods, - // eslint-disable-next-line import/no-unresolved -} from '@woocommerce/blocks-registry'; -import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import './style.scss'; -import confirmUPEPayment from './confirm-upe-payment.js'; -import { getConfig } from 'utils/checkout'; -import { - getStripeElementOptions, - useCustomerData, - isLinkEnabled, - blocksShowLinkButtonHandler, -} from '../utils/upe'; -import { decryptClientSecret } from '../utils/encryption'; -import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; -import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; -import { getAppearance, getFontRulesFromPage } from '../upe-styles'; -import { useFingerprint } from './hooks'; -import { - BLOCKS_SHIPPING_ADDRESS_FIELDS, - BLOCKS_BILLING_ADDRESS_FIELDS, -} from '../constants'; - -const WCPayUPEFields = ( { - api, - activePaymentMethod, - billing: { billingData }, - shippingData, - eventRegistration: { onPaymentSetup, onCheckoutSuccess }, - emitResponse, - paymentIntentId, - paymentIntentSecret, - errorMessage, - shouldSavePayment, - fingerprint, -} ) => { - const stripe = useStripe(); - const elements = useElements(); - - const [ isUPEComplete, setIsUPEComplete ] = useState( false ); - const [ selectedUPEPaymentType, setSelectedUPEPaymentType ] = useState( - '' - ); - const [ paymentCountry, setPaymentCountry ] = useState( null ); - - const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' ); - const testMode = getConfig( 'testMode' ); - const testCopy = ( -

- Test mode: use the test VISA card 4242424242424242 - with any expiry date and CVC. -

- ); - - const gatewayConfig = getPaymentMethods()[ PAYMENT_METHOD_NAME_CARD ]; - const customerData = useCustomerData(); - - useEffect( () => { - if ( isLinkEnabled( paymentMethodsConfig ) ) { - enableStripeLinkPaymentMethod( { - api: api, - elements: elements, - emailId: 'email', - fill_field_method: ( address, nodeId, key ) => { - const setAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.setShippingAddress - : customerData.setBillingData || - customerData.setBillingAddress; - const customerAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.shippingAddress - : customerData.billingData || - customerData.billingAddress; - - if ( key === 'line1' ) { - customerAddress.address_1 = address.address[ key ]; - } else if ( key === 'line2' ) { - customerAddress.address_2 = address.address[ key ]; - } else if ( key === 'postal_code' ) { - customerAddress.postcode = address.address[ key ]; - } else { - customerAddress[ key ] = address.address[ key ]; - } - - setAddress( customerAddress ); - - function getEmail() { - return document.getElementById( 'email' ).value; - } - - if ( customerData.billingData ) { - customerData.billingData.email = getEmail(); - customerData.setBillingData( customerData.billingData ); - } else { - customerData.billingAddress.email = getEmail(); - customerData.setBillingAddress( - customerData.billingAddress - ); - } - }, - show_button: blocksShowLinkButtonHandler, - complete_shipping: () => { - return ( - document.getElementById( 'shipping-address_1' ) !== null - ); - }, - shipping_fields: BLOCKS_SHIPPING_ADDRESS_FIELDS, - billing_fields: BLOCKS_BILLING_ADDRESS_FIELDS, - complete_billing: () => { - return ( - document.getElementById( 'billing-address_1' ) !== null - ); - }, - } ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ elements ] ); - - // When it's time to process the payment, generate a Stripe payment method object. - useEffect( - () => - onPaymentSetup( () => { - if ( PAYMENT_METHOD_NAME_CARD !== activePaymentMethod ) { - return; - } - - if ( ! isUPEComplete ) { - return { - type: 'error', - message: __( - 'Your payment information is incomplete.', - 'woocommerce-payments' - ), - }; - } - - if ( errorMessage ) { - return { - type: 'error', - message: errorMessage, - }; - } - - if ( - gatewayConfig.supports.showSaveOption && - shouldSavePayment && - ! paymentMethodsConfig[ selectedUPEPaymentType ].isReusable - ) { - return { - type: 'error', - message: __( - 'This payment method can not be saved for future use.', - 'woocommerce-payments' - ), - }; - } - - const fraudPreventionToken = document - .querySelector( '#wcpay-fraud-prevention-token' ) - ?.getAttribute( 'value' ); - - return { - type: 'success', - meta: { - paymentMethodData: { - paymentMethod: PAYMENT_METHOD_NAME_CARD, - wc_payment_intent_id: paymentIntentId, - wcpay_selected_upe_payment_type: selectedUPEPaymentType, - 'wcpay-fraud-prevention-token': - fraudPreventionToken ?? '', - 'wcpay-fingerprint': fingerprint, - }, - }, - }; - } ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - activePaymentMethod, - isUPEComplete, - selectedUPEPaymentType, - shouldSavePayment, - fingerprint, - ] - ); - - // Once the server has completed payment processing, confirm the intent if necessary. - useEffect( - () => - onCheckoutSuccess( - ( { orderId, processingResponse: { paymentDetails } } ) => { - async function updateIntent() { - if ( api.handleDuplicatePayments( paymentDetails ) ) { - return; - } - - await api.updateIntent( - paymentIntentId, - orderId, - shouldSavePayment ? 'yes' : 'no', - selectedUPEPaymentType, - paymentCountry, - fingerprint - ); - - return confirmUPEPayment( - api, - paymentDetails.redirect_url, - paymentDetails.payment_needed, - paymentIntentSecret, - elements, - billingData, - shippingData, - emitResponse, - selectedUPEPaymentType - ); - } - - return updateIntent(); - } - ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - elements, - stripe, - api, - paymentIntentId, - shouldSavePayment, - selectedUPEPaymentType, - paymentCountry, - ] - ); - - // Checks whether there are errors within a field, and saves them for later reporting. - const upeOnChange = ( event ) => { - // Update WC Blocks gateway config based on selected UPE payment method. - gatewayConfig.supports.showSaveOption = - paymentMethodsConfig[ event.value.type ].showSaveOption; - - setIsUPEComplete( event.complete ); - setSelectedUPEPaymentType( event.value.type ); - setPaymentCountry( event.value.country ); - }; - - return ( - <> - { testMode ? testCopy : '' } - - - ); -}; - -/** - * Wraps WCPayFields within the necessary Stripe consumer components. - * - * @param {Object} props All props given by WooCommerce Blocks. - * @return {Object} The wrapped React element. - */ -const ConsumableWCPayFields = ( { api, ...props } ) => { - const stripe = api.getStripe(); - const [ paymentIntentId, setPaymentIntentId ] = useState( null ); - const [ clientSecret, setClientSecret ] = useState( null ); - const [ hasRequestedIntent, setHasRequestedIntent ] = useState( false ); - const [ errorMessage, setErrorMessage ] = useState( null ); - const [ appearance, setAppearance ] = useState( - getConfig( 'wcBlocksUPEAppearance' ) - ); - const [ fontRules ] = useState( getFontRulesFromPage() ); - const [ fingerprint, fingerprintErrorMessage ] = useFingerprint(); - - useEffect( () => { - async function generateUPEAppearance() { - // Generate UPE input styles. - const upeAppearance = getAppearance( true ); - await api.saveUPEAppearance( upeAppearance, 'true' ); - - // Update appearance state - setAppearance( upeAppearance ); - } - if ( ! appearance ) { - generateUPEAppearance(); - } - - if ( fingerprintErrorMessage ) { - setErrorMessage( fingerprintErrorMessage ); - return; - } - - if ( paymentIntentId || hasRequestedIntent || ! fingerprint ) { - return; - } - - async function createIntent() { - try { - const response = await api.createIntent( fingerprint ); - setPaymentIntentId( response.id ); - setClientSecret( response.client_secret ); - } catch ( error ) { - setErrorMessage( - error.message - ? error.message - : 'There was an error loading the payment gateway.' - ); - } - } - setHasRequestedIntent( true ); - createIntent(); - }, [ - paymentIntentId, - hasRequestedIntent, - api, - errorMessage, - appearance, - fingerprint, - fingerprintErrorMessage, - ] ); - - if ( ! clientSecret ) { - if ( errorMessage ) { - return ( -
-
- { errorMessage } -
-
- ); - } - - return null; - } - - return ( - - - - ); -}; - -export default ConsumableWCPayFields; diff --git a/client/checkout/blocks/upe-split-fields.js b/client/checkout/blocks/upe-split-fields.js deleted file mode 100644 index ff75fd8ea81..00000000000 --- a/client/checkout/blocks/upe-split-fields.js +++ /dev/null @@ -1,436 +0,0 @@ -/** - * External dependencies - */ -import { - Elements, - useStripe, - useElements, - PaymentElement, -} from '@stripe/react-stripe-js'; -import { - getPaymentMethods, - // eslint-disable-next-line import/no-unresolved -} from '@woocommerce/blocks-registry'; -import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import './style.scss'; -import confirmUPEPayment from './confirm-upe-payment.js'; -import { getUPEConfig } from 'utils/checkout'; -import { - getPaymentIntentFromSession, - getCookieValue, - getStripeElementOptions, - getBlocksEmailValue, - blocksShowLinkButtonHandler, - useCustomerData, - isLinkEnabled, -} from '../utils/upe'; -import { decryptClientSecret } from '../utils/encryption'; -import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; -import { getAppearance, getFontRulesFromPage } from '../upe-styles'; -import { useFingerprint } from './hooks'; -import { LoadableBlock } from '../../components/loadable'; -import { - BLOCKS_SHIPPING_ADDRESS_FIELDS, - BLOCKS_BILLING_ADDRESS_FIELDS, -} from '../constants'; - -const WCPayUPEFields = ( { - api, - activePaymentMethod, - testingInstructions, - billing: { billingData }, - shippingData, - eventRegistration: { onPaymentSetup, onCheckoutSuccess }, - emitResponse, - paymentMethodId, - upeMethods, - paymentIntentId, - paymentIntentSecret, - errorMessage, - shouldSavePayment, - fingerprint, -} ) => { - const stripe = useStripe(); - const elements = useElements(); - - const [ isUPEComplete, setIsUPEComplete ] = useState( false ); - const [ selectedUPEPaymentType, setSelectedUPEPaymentType ] = useState( - '' - ); - const [ paymentCountry, setPaymentCountry ] = useState( null ); - - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - const isTestMode = getUPEConfig( 'testMode' ); - const testingInstructionsIfAppropriate = isTestMode - ? testingInstructions - : ''; - const gatewayConfig = getPaymentMethods()[ upeMethods[ paymentMethodId ] ]; - const customerData = useCustomerData(); - - useEffect( () => { - if ( isLinkEnabled( paymentMethodsConfig ) ) { - enableStripeLinkPaymentMethod( { - api: api, - elements: elements, - emailId: 'email', - fill_field_method: ( address, nodeId, key ) => { - const setAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.setShippingAddress - : customerData.setBillingData || - customerData.setBillingAddress; - const customerAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.shippingAddress - : customerData.billingData || - customerData.billingAddress; - - if ( key === 'line1' ) { - customerAddress.address_1 = address.address[ key ]; - } else if ( key === 'line2' ) { - customerAddress.address_2 = address.address[ key ]; - } else if ( key === 'postal_code' ) { - customerAddress.postcode = address.address[ key ]; - } else { - customerAddress[ key ] = address.address[ key ]; - } - - setAddress( customerAddress ); - - if ( customerData.billingData ) { - customerData.billingData.email = getBlocksEmailValue(); - customerData.setBillingData( customerData.billingData ); - } else { - customerData.billingAddress.email = getBlocksEmailValue(); - customerData.setBillingAddress( - customerData.billingAddress - ); - } - }, - show_button: blocksShowLinkButtonHandler, - complete_shipping: () => { - return ( - document.getElementById( 'shipping-address_1' ) !== null - ); - }, - shipping_fields: BLOCKS_SHIPPING_ADDRESS_FIELDS, - billing_fields: BLOCKS_BILLING_ADDRESS_FIELDS, - complete_billing: () => { - return ( - document.getElementById( 'billing-address_1' ) !== null - ); - }, - } ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ elements ] ); - - // When it's time to process the payment, generate a Stripe payment method object. - useEffect( - () => - onPaymentSetup( () => { - if ( upeMethods[ paymentMethodId ] !== activePaymentMethod ) { - return; - } - - if ( ! isUPEComplete ) { - return { - type: 'error', - message: __( - 'Your payment information is incomplete.', - 'woocommerce-payments' - ), - }; - } - - if ( errorMessage ) { - return { - type: 'error', - message: errorMessage, - }; - } - - if ( - gatewayConfig.supports.showSaveOption && - shouldSavePayment && - ! paymentMethodsConfig[ selectedUPEPaymentType ].isReusable - ) { - return { - type: 'error', - message: __( - 'This payment method can not be saved for future use.', - 'woocommerce-payments' - ), - }; - } - - const fraudPreventionToken = document - .querySelector( '#wcpay-fraud-prevention-token' ) - ?.getAttribute( 'value' ); - - return { - type: 'success', - meta: { - paymentMethodData: { - paymentMethod: paymentMethodId, - wc_payment_intent_id: paymentIntentId, - wcpay_selected_upe_payment_type: selectedUPEPaymentType, - 'wcpay-fraud-prevention-token': - fraudPreventionToken ?? '', - 'wcpay-fingerprint': fingerprint, - }, - }, - }; - } ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - activePaymentMethod, - isUPEComplete, - selectedUPEPaymentType, - shouldSavePayment, - fingerprint, - ] - ); - - // Once the server has completed payment processing, confirm the intent if necessary. - useEffect( - () => - onCheckoutSuccess( - ( { orderId, processingResponse: { paymentDetails } } ) => { - async function updateIntent() { - if ( api.handleDuplicatePayments( paymentDetails ) ) { - return; - } - - await api.updateIntent( - paymentIntentId, - orderId, - shouldSavePayment ? 'yes' : 'no', - selectedUPEPaymentType, - paymentCountry, - fingerprint - ); - - return confirmUPEPayment( - api, - paymentDetails.redirect_url, - paymentDetails.payment_needed, - paymentIntentSecret, - elements, - billingData, - shippingData, - emitResponse, - selectedUPEPaymentType - ); - } - - return updateIntent(); - } - ), - // not sure if we need to disable this, but kept it as-is to ensure nothing breaks. Please consider passing all the deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - elements, - stripe, - api, - paymentIntentId, - shouldSavePayment, - selectedUPEPaymentType, - paymentCountry, - ] - ); - - // Checks whether there are errors within a field, and saves them for later reporting. - const upeOnChange = ( event ) => { - // Update WC Blocks gateway config based on selected UPE payment method. - const paymentType = - event.value.type !== 'link' ? event.value.type : 'card'; - gatewayConfig.supports.showSaveOption = - paymentMethodsConfig[ paymentType ].showSaveOption; - - setIsUPEComplete( event.complete ); - setSelectedUPEPaymentType( paymentType ); - setPaymentCountry( event.value.country ); - }; - - return ( - <> -

- - - ); -}; - -/** - * Wraps WCPayFields within the necessary Stripe consumer components. - * - * @param {Object} props All props given by WooCommerce Blocks. - * @return {Object} The wrapped React element. - */ -const ConsumableWCPayFields = ( { api, ...props } ) => { - const stripe = api.getStripe(); - const [ paymentIntentId, setPaymentIntentId ] = useState( null ); - const [ clientSecret, setClientSecret ] = useState( null ); - const [ hasRequestedIntent, setHasRequestedIntent ] = useState( false ); - const [ errorMessage, setErrorMessage ] = useState( null ); - const [ appearance, setAppearance ] = useState( - getUPEConfig( 'wcBlocksUPEAppearance' ) - ); - const [ fontRules ] = useState( getFontRulesFromPage() ); - const [ fingerprint, fingerprintErrorMessage ] = useFingerprint(); - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - - useEffect( () => { - async function generateUPEAppearance() { - // Generate UPE input styles. - const upeAppearance = getAppearance( true ); - await api.saveUPEAppearance( upeAppearance, 'true' ); - - // Update appearance state - setAppearance( upeAppearance ); - } - - if ( ! appearance ) { - generateUPEAppearance(); - } - - if ( fingerprintErrorMessage ) { - setErrorMessage( fingerprintErrorMessage ); - return; - } - - if ( paymentIntentId || hasRequestedIntent || ! fingerprint ) { - return; - } - async function createIntent( paymentMethodType ) { - try { - const response = await api.createIntent( { - fingerprint, - paymentMethodType, - } ); - const cartHash = getCookieValue( 'woocommerce_cart_hash' ); - if ( cartHash ) { - paymentMethodsConfig[ - paymentMethodType - ].upePaymentIntentData = - cartHash + - '-' + - response.id + - '-' + - response.client_secret; - } - setPaymentIntentId( response.id ); - setClientSecret( response.client_secret ); - } catch ( error ) { - setErrorMessage( - error.message - ? error.message - : 'There was an error loading the payment gateway.' - ); - } - } - - function getOrCreateIntent( paymentMethodId ) { - const { - intentId, - clientSecret: paymentClientSecret, - } = getPaymentIntentFromSession( - paymentMethodsConfig, - paymentMethodId - ); - if ( ! intentId ) { - createIntent( paymentMethodId ); - } else { - setPaymentIntentId( intentId ); - setClientSecret( paymentClientSecret ); - } - } - - setHasRequestedIntent( true ); - getOrCreateIntent( props.paymentMethodId ); - }, [ - props.paymentMethodId, - paymentIntentId, - paymentMethodsConfig, - hasRequestedIntent, - api, - errorMessage, - appearance, - fingerprint, - fingerprintErrorMessage, - ] ); - - if ( ! clientSecret ) { - if ( errorMessage ) { - return ( -
-
- { errorMessage } -
-
- ); - } - - return null; - } - - return ( - - - - - - ); -}; - -export const getSplitUPEFields = ( - upeName, - upeMethods, - api, - testingInstructions -) => { - return ( - - ); -}; diff --git a/client/checkout/blocks/upe-split.js b/client/checkout/blocks/upe-split.js deleted file mode 100644 index 6166bdf907a..00000000000 --- a/client/checkout/blocks/upe-split.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * External dependencies - */ -// Handled as an external dependency: see '/webpack.config.js:83' -import { - registerPaymentMethod, - registerExpressPaymentMethod, - // eslint-disable-next-line import/no-unresolved -} from '@woocommerce/blocks-registry'; - -/** - * Internal dependencies - */ -import { getUPEConfig } from 'utils/checkout'; -import { isLinkEnabled } from '../utils/upe'; -import WCPayAPI from './../api'; -import { SavedTokenHandler } from './saved-token-handler'; -import request from '../utils/request'; -import enqueueFraudScripts from 'fraud-scripts'; -import paymentRequestPaymentMethod from '../../payment-request/blocks'; -import { - PAYMENT_METHOD_NAME_CARD, - PAYMENT_METHOD_NAME_BANCONTACT, - PAYMENT_METHOD_NAME_BECS, - PAYMENT_METHOD_NAME_EPS, - PAYMENT_METHOD_NAME_GIROPAY, - PAYMENT_METHOD_NAME_IDEAL, - PAYMENT_METHOD_NAME_P24, - PAYMENT_METHOD_NAME_SEPA, - PAYMENT_METHOD_NAME_SOFORT, - PAYMENT_METHOD_NAME_AFFIRM, - PAYMENT_METHOD_NAME_AFTERPAY, - PAYMENT_METHOD_NAME_KLARNA, -} from '../constants.js'; -import { getSplitUPEFields } from './upe-split-fields'; -import { getDeferredIntentCreationUPEFields } from './upe-deferred-intent-creation/payment-elements'; - -const upeMethods = { - card: PAYMENT_METHOD_NAME_CARD, - bancontact: PAYMENT_METHOD_NAME_BANCONTACT, - au_becs_debit: PAYMENT_METHOD_NAME_BECS, - eps: PAYMENT_METHOD_NAME_EPS, - giropay: PAYMENT_METHOD_NAME_GIROPAY, - ideal: PAYMENT_METHOD_NAME_IDEAL, - p24: PAYMENT_METHOD_NAME_P24, - sepa_debit: PAYMENT_METHOD_NAME_SEPA, - sofort: PAYMENT_METHOD_NAME_SOFORT, - affirm: PAYMENT_METHOD_NAME_AFFIRM, - afterpay_clearpay: PAYMENT_METHOD_NAME_AFTERPAY, - klarna: PAYMENT_METHOD_NAME_KLARNA, -}; - -const enabledPaymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); -const isStripeLinkEnabled = isLinkEnabled( enabledPaymentMethodsConfig ); - -// Create an API object, which will be used throughout the checkout. -const api = new WCPayAPI( - { - publishableKey: getUPEConfig( 'publishableKey' ), - accountId: getUPEConfig( 'accountId' ), - forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ), - locale: getUPEConfig( 'locale' ), - isUPEEnabled: getUPEConfig( 'isUPEEnabled' ), - isUPESplitEnabled: getUPEConfig( 'isUPESplitEnabled' ), - isUPEDeferredEnabled: getUPEConfig( 'isUPEDeferredEnabled' ), - isStripeLinkEnabled, - }, - request -); -const getUPEFields = getUPEConfig( 'isUPEDeferredEnabled' ) - ? getDeferredIntentCreationUPEFields - : getSplitUPEFields; -Object.entries( enabledPaymentMethodsConfig ) - .filter( ( [ upeName ] ) => upeName !== 'link' ) - .forEach( ( [ upeName, upeConfig ] ) => { - registerPaymentMethod( { - name: upeMethods[ upeName ], - content: getUPEFields( - upeName, - upeMethods, - api, - upeConfig.testingInstructions - ), - edit: getUPEFields( - upeName, - upeMethods, - api, - upeConfig.testingInstructions - ), - savedTokenComponent: , - canMakePayment: ( cartData ) => { - const billingCountry = cartData.billingAddress.country; - const isRestrictedInAnyCountry = !! upeConfig.countries.length; - const isAvailableInTheCountry = - ! isRestrictedInAnyCountry || - upeConfig.countries.includes( billingCountry ); - return ( - isAvailableInTheCountry && !! api.getStripeForUPE( upeName ) - ); - }, - paymentMethodId: upeMethods[ upeName ], - // see .wc-block-checkout__payment-method styles in blocks/style.scss - label: ( - <> - - { upeConfig.title } - { - - - ), - ariaLabel: 'WooPayments', - supports: { - showSavedCards: getUPEConfig( 'isSavedCardsEnabled' ) ?? false, - showSaveOption: upeConfig.showSaveOption ?? false, - features: getUPEConfig( 'features' ), - }, - } ); - } ); - -registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) ); -window.addEventListener( 'load', () => { - enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); -} ); diff --git a/client/checkout/blocks/upe.js b/client/checkout/blocks/upe.js deleted file mode 100644 index c0b1e28ad65..00000000000 --- a/client/checkout/blocks/upe.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * External dependencies - */ -// Handled as an external dependency: see '/webpack.config.js:83' -import { - registerPaymentMethod, - registerExpressPaymentMethod, - // eslint-disable-next-line import/no-unresolved -} from '@woocommerce/blocks-registry'; - -/** - * Internal dependencies - */ -import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; -import { getConfig, getCustomGatewayTitle } from 'utils/checkout'; -import WCPayAPI from './../api'; -import WCPayUPEFields from './upe-fields.js'; -import { SavedTokenHandler } from './saved-token-handler'; -import request from '../utils/request'; -import enqueueFraudScripts from 'fraud-scripts'; -import paymentRequestPaymentMethod from '../../payment-request/blocks'; -import { isLinkEnabled } from '../utils/upe.js'; - -const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' ); -const isStripeLinkEnabled = isLinkEnabled( paymentMethodsConfig ); - -// Create an API object, which will be used throughout the checkout. -const api = new WCPayAPI( - { - publishableKey: getConfig( 'publishableKey' ), - accountId: getConfig( 'accountId' ), - forceNetworkSavedCards: getConfig( 'forceNetworkSavedCards' ), - locale: getConfig( 'locale' ), - isUPEEnabled: getConfig( 'isUPEEnabled' ), - isStripeLinkEnabled, - }, - request -); - -registerPaymentMethod( { - name: PAYMENT_METHOD_NAME_CARD, - content: , - edit: , - savedTokenComponent: , - canMakePayment: () => !! api.getStripe(), - paymentMethodId: PAYMENT_METHOD_NAME_CARD, - label: getCustomGatewayTitle( getConfig( 'paymentMethodsConfig' ) ), - ariaLabel: 'WooPayments', - supports: { - showSavedCards: getConfig( 'isSavedCardsEnabled' ) ?? false, - showSaveOption: - ( getConfig( 'isSavedCardsEnabled' ) && - ! getConfig( 'cartContainsSubscription' ) ) ?? - false, - features: getConfig( 'features' ), - }, -} ); - -registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) ); -window.addEventListener( 'load', () => { - enqueueFraudScripts( getConfig( 'fraudServices' ) ); -} ); diff --git a/client/checkout/classic/upe-deferred-intent-creation/3ds-flow-handling.js b/client/checkout/classic/3ds-flow-handling.js similarity index 100% rename from client/checkout/classic/upe-deferred-intent-creation/3ds-flow-handling.js rename to client/checkout/classic/3ds-flow-handling.js diff --git a/client/checkout/classic/upe-deferred-intent-creation/event-handlers.js b/client/checkout/classic/event-handlers.js similarity index 97% rename from client/checkout/classic/upe-deferred-intent-creation/event-handlers.js rename to client/checkout/classic/event-handlers.js index 0f5c833e2c1..dbf292944a5 100644 --- a/client/checkout/classic/upe-deferred-intent-creation/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -3,6 +3,7 @@ /** * Internal dependencies */ +import './style.scss'; import { getUPEConfig } from 'wcpay/utils/checkout'; import { generateCheckoutEventNames, @@ -11,7 +12,7 @@ import { isPaymentMethodRestrictedToLocation, isUsingSavedPaymentMethod, togglePaymentMethodForCountry, -} from '../../utils/upe'; +} from '../utils/upe'; import { processPayment, mountStripePaymentElement, @@ -22,7 +23,7 @@ import { import enqueueFraudScripts from 'fraud-scripts'; import { showAuthenticationModalIfRequired } from './3ds-flow-handling'; import WCPayAPI from 'wcpay/checkout/api'; -import apiRequest from '../../utils/request'; +import apiRequest from '../utils/request'; import { handleWooPayEmailInput } from 'wcpay/checkout/woopay/email-input-iframe'; import { isPreviewing } from 'wcpay/checkout/preview'; diff --git a/client/checkout/classic/index.js b/client/checkout/classic/index.js deleted file mode 100644 index 2a421453337..00000000000 --- a/client/checkout/classic/index.js +++ /dev/null @@ -1,561 +0,0 @@ -/* global jQuery */ - -/** - * Internal dependencies - */ -import './style.scss'; -import { PAYMENT_METHOD_NAME_CARD } from '../constants.js'; -import { getConfig } from 'utils/checkout'; -import { handleWooPayEmailInput } from '../woopay/email-input-iframe'; -import WCPayAPI from './../api'; -import enqueueFraudScripts from 'fraud-scripts'; -import { isWCPayChosen } from '../utils/upe'; -import { isPreviewing } from '../preview'; -import { - getFingerprint, - appendFingerprintInputToForm, -} from '../utils/fingerprint'; - -jQuery( function ( $ ) { - enqueueFraudScripts( getConfig( 'fraudServices' ) ); - - let fingerprint = ''; - const publishableKey = getConfig( 'publishableKey' ); - - if ( ! publishableKey ) { - // If no configuration is present, probably this is not the checkout page. - return; - } - - // Create an API object, which will be used throughout the checkout. - const api = new WCPayAPI( - { - publishableKey, - accountId: getConfig( 'accountId' ), - forceNetworkSavedCards: getConfig( 'forceNetworkSavedCards' ), - locale: getConfig( 'locale' ), - }, - // A promise-based interface to jQuery.post. - ( url, args ) => { - return new Promise( ( resolve, reject ) => { - jQuery.post( url, args ).then( resolve ).fail( reject ); - } ); - } - ); - - const elements = api.getStripe().elements(); - - // Customer information for Pay for Order and Save Payment method. - /* global wcpayCustomerData */ - const preparedCustomerData = - typeof wcpayCustomerData !== 'undefined' ? wcpayCustomerData : {}; - - // Create a card element. - const cardElement = elements.create( 'card', { - hidePostalCode: true, - classes: { base: 'wcpay-card-mounted' }, - } ); - - const cardPayment = { - type: 'card', - card: cardElement, - }; - - // Giropay payment method details - const giropayPayment = { - type: 'giropay', - }; - - // Create a SEPA element - const sepaElement = elements.create( 'iban', { - // 'SEPA' Indicates all countries in the Single Euro Payments Area (SEPA). - supportedCountries: [ 'SEPA' ], - classes: { base: 'wcpay-sepa-mounted' }, - } ); - - const sepaPayment = { - type: 'sepa_debit', - sepa_debit: sepaElement, - }; - - // Sofort payment method details - const sofortPayment = { - type: 'sofort', - }; - - /** - * Block UI to indicate processing and avoid duplicate submission. - * - * @param {Object} $form The jQuery object for the form. - */ - const blockUI = ( $form ) => { - $form.addClass( 'processing' ).block( { - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6, - }, - } ); - }; - - // Show error notice at top of checkout form. - const showError = ( errorMessage ) => { - let messageWrapper = ''; - if ( errorMessage.includes( 'woocommerce-error' ) ) { - messageWrapper = errorMessage; - } else { - messageWrapper = - ''; - } - const $container = $( - '.woocommerce-notices-wrapper, form.checkout' - ).first(); - - if ( ! $container.length ) { - return; - } - - // Adapted from WooCommerce core @ ea9aa8c, assets/js/frontend/checkout.js#L514-L529 - $( - '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message' - ).remove(); - $container.prepend( - '
' + - messageWrapper + - '
' - ); - $container - .find( '.input-text, select, input:checkbox' ) - .trigger( 'validate' ) - .blur(); - - let scrollElement = $( '.woocommerce-NoticeGroup-checkout' ); - if ( ! scrollElement.length ) { - scrollElement = $container; - } - - $.scroll_to_notices( scrollElement ); - $( document.body ).trigger( 'checkout_error' ); - }; - - /** - * Check if Giropay payment is being used. - * - * @return {boolean} Boolean indicating whether or not Giropay payment is being used. - */ - const isWCPayGiropayChosen = function () { - return $( '#payment_method_woocommerce_payments_giropay' ).is( - ':checked' - ); - }; - - /** - * Check if SEPA Direct Debit is being used. - * - * @return {boolean} Boolean indicating whether or not SEPA Direct Debit is being used. - */ - const isWCPaySepaChosen = function () { - return $( '#payment_method_woocommerce_payments_sepa' ).is( - ':checked' - ); - }; - - /** - * Check if Sofort payment method is being used. - * - * @return {boolean} Boolean indicating whether or not Sofort payment method is being used. - */ - const isWCPaySofortChosen = function () { - return $( '#payment_method_woocommerce_payments_sofort' ).is( - ':checked' - ); - }; - - // Only attempt to mount the card element once that section of the page has loaded. We can use the updated_checkout - // event for this. This part of the page can also reload based on changes to checkout details, so we call unmount - // first to ensure the card element is re-mounted correctly. - $( document.body ).on( 'updated_checkout', async () => { - // If the card element selector doesn't exist, then do nothing (for example, when a 100% discount coupon is applied). - // We also don't re-mount if already mounted in DOM. - if ( - $( '#wcpay-card-element' ).length && - ! $( '#wcpay-card-element' ).children().length - ) { - cardElement.unmount(); - - if ( ! fingerprint ) { - try { - const { visitorId } = await getFingerprint(); - fingerprint = visitorId; - } catch ( error ) { - // Do not mount element if fingerprinting is not available - showError( error.message ); - - return; - } - } - - cardElement.mount( '#wcpay-card-element' ); - } - - if ( $( '#wcpay-sepa-element' ).length ) { - sepaElement.mount( '#wcpay-sepa-element' ); - } - } ); - - if ( - $( 'form#add_payment_method' ).length || - $( 'form#order_review' ).length - ) { - if ( - $( '#wcpay-card-element' ).length && - ! $( '#wcpay-card-element' ).children().length - ) { - cardElement.mount( '#wcpay-card-element' ); - } - - if ( $( '#wcpay-sepa-element' ).length ) { - sepaElement.mount( '#wcpay-sepa-element' ); - } - - /* - * Trigger this event to ensure the tokenization-form.js init - * is executed. - * - * This script handles the radio input interaction when toggling - * between the user's saved card / entering new card details. - * - * Ref: https://github.com/woocommerce/woocommerce/blob/2429498/assets/js/frontend/tokenization-form.js#L109 - */ - $( document.body ).trigger( 'wc-credit-card-form-init' ); - } - - // Update the validation state based on the element's state. - cardElement.addEventListener( 'change', ( event ) => { - const displayError = $( '#wcpay-errors' ); - if ( event.error ) { - displayError - .html( '
' ) - .find( 'li' ) - .text( event.error.message ); - } else { - displayError.empty(); - } - } ); - - sepaElement.addEventListener( 'change', ( event ) => { - const displayError = $( '#wcpay-sepa-errors' ); - if ( event.error ) { - displayError - .html( '
' ) - .find( 'li' ) - .text( event.error.message ); - } else { - displayError.empty(); - } - } ); - - // Create payment method on submission. - let paymentMethodGenerated; - - /** - * Creates and authorizes a setup intent, saves its ID in a hidden input, and re-submits the form. - * - * @param {Object} $form The jQuery object for the form. - * @param {Object} paymentMethod Payment method object. - */ - const handleAddCard = ( $form, paymentMethod ) => { - api.setupIntent( paymentMethod.id ) - .then( function ( confirmedSetupIntent ) { - // Populate form with the setup intent and re-submit. - $form.append( - $( '' ) - .attr( 'id', 'wcpay-setup-intent' ) - .attr( 'name', 'wcpay-setup-intent' ) - .val( confirmedSetupIntent.id ) - ); - - // WC core calls block() when add_payment_form is submitted, so we need to enable the ignore flag here to avoid - // the overlay blink when the form is blocked twice. We can restore its default value once the form is submitted. - const defaultIgnoreIfBlocked = - $.blockUI.defaults.ignoreIfBlocked; - $.blockUI.defaults.ignoreIfBlocked = true; - - // Re-submit the form. - $form.removeClass( 'processing' ).submit(); - - // Restore default value for ignoreIfBlocked. - $.blockUI.defaults.ignoreIfBlocked = defaultIgnoreIfBlocked; - } ) - .catch( function ( error ) { - paymentMethodGenerated = null; - $form.removeClass( 'processing' ).unblock(); - if ( error.responseJSON && ! error.responseJSON.success ) { - showError( error.responseJSON.data.error.message ); - } else if ( error.message ) { - showError( error.message ); - } - } ); - }; - - /** - * Saves the payment method ID in a hidden input, and re-submits the form. - * - * @param {Object} $form The jQuery object for the form. - * @param {Object} paymentMethod Payment method object. - */ - const handleOrderPayment = ( $form, { id } ) => { - const paymentSelector = isWCPaySepaChosen() - ? '#wcpay-payment-method-sepa' - : '#wcpay-payment-method'; - - // Populate form with the payment method. - $( paymentSelector ).val( id ); - - // Re-submit the form. - $form.removeClass( 'processing' ).submit(); - }; - - /** - * Generates a payment method, saves its ID in a hidden input, and re-submits the form. - * - * @param {Object} $form The jQuery object for the form. - * @param {Function} successHandler Callback to be executed when payment method is generated. - * @param {Object} paymentMethodDetails { type: 'card' | 'sepa_debit', card? | sepa_debit? : Stripe element }. - * @return {boolean} A flag for the event handler. - */ - const handlePaymentMethodCreation = ( - $form, - successHandler, - paymentMethodDetails - ) => { - // We'll resubmit the form after populating our payment method, so if this is the second time this event - // is firing we should let the form submission happen. - if ( paymentMethodGenerated ) { - paymentMethodGenerated = null; - return; - } - - blockUI( $form ); - const request = api.generatePaymentMethodRequest( - paymentMethodDetails, - preparedCustomerData - ); - - // Populate payment method owner details. - const billingName = $( '#billing_first_name' ).length - ? ( - $( '#billing_first_name' ).val() + - ' ' + - $( '#billing_last_name' ).val() - ).trim() - : undefined; - - request.setBillingDetail( 'name', billingName ); - request.setBillingDetail( 'email', $( '#billing_email' ).val() ); - request.setBillingDetail( 'phone', $( '#billing_phone' ).val() ); - request.setAddressDetail( 'city', $( '#billing_city' ).val() ); - request.setAddressDetail( 'country', $( '#billing_country' ).val() ); - request.setAddressDetail( 'line1', $( '#billing_address_1' ).val() ); - request.setAddressDetail( 'line2', $( '#billing_address_2' ).val() ); - request.setAddressDetail( - 'postal_code', - $( '#billing_postcode' ).val() - ); - request.setAddressDetail( 'state', $( '#billing_state' ).val() ); - - request - .send() - .then( ( { paymentMethod } ) => { - // Flag that the payment method has been successfully generated so that we can allow the form - // submission next time. - paymentMethodGenerated = true; - - successHandler( $form, paymentMethod ); - } ) - .catch( ( error ) => { - $form.removeClass( 'processing' ).unblock(); - showError( error.message ); - } ); - - // Prevent form submission so that we can fire it once a payment method has been generated. - return false; - }; - - /** - * Displays the authentication modal to the user if needed. - */ - const maybeShowAuthenticationModal = () => { - let url = window.location.href; - - const intentSecret = getConfig( 'intentSecret' ); - if ( intentSecret ) { - const hash = - '#wcpay-confirm-pi:0:' + - intentSecret + - ':' + - getConfig( 'updateOrderNonce' ); - url = window.location.pathname + window.location.search + hash; - } - - const paymentMethodId = isWCPaySepaChosen() - ? $( '#wcpay-payment-method-sepa' ).val() - : $( '#wcpay-payment-method' ).val(); - - const savePaymentMethod = $( - '#wc-woocommerce_payments-new-payment-method' - ).is( ':checked' ); - const confirmation = api.confirmIntent( - url, - savePaymentMethod ? paymentMethodId : null - ); - - // Boolean `true` means that there is nothing to confirm. - if ( confirmation === true ) { - return; - } - - const { request, isOrderPage } = confirmation; - - if ( isOrderPage ) { - blockUI( $( '#order_review' ) ); - $( '#payment' ).hide( 500 ); - } - - // Cleanup the URL. - // https://stackoverflow.com/a/5298684 - history.replaceState( - '', - document.title, - window.location.pathname + window.location.search - ); - - request - .then( ( redirectUrl ) => { - window.location = redirectUrl; - } ) - .catch( ( error ) => { - $( 'form.checkout' ).removeClass( 'processing' ).unblock(); - $( '#order_review' ).removeClass( 'processing' ).unblock(); - $( '#payment' ).show( 500 ); - - let errorMessage = error.message; - - // If this is a generic error, we probably don't want to display the error message to the user, - // so display a generic message instead. - if ( error instanceof Error ) { - errorMessage = getConfig( 'genericErrorMessage' ); - } - - showError( errorMessage ); - } ); - }; - - /** - * Checks if the customer is using a saved payment method. - * - * @return {boolean} Boolean indicating whether or not a saved payment method is being used. - */ - function isUsingSavedPaymentMethod() { - if ( isWCPayGiropayChosen() ) { - // Giropay does not use saved payment methods at this time - return false; - } - - if ( isWCPaySepaChosen() ) { - return ( - $( '#wc-woocommerce_payments_sepa-payment-token-new' ).length && - ! $( '#wc-woocommerce_payments_sepa-payment-token-new' ).is( - ':checked' - ) - ); - } - return ( - $( '#wc-woocommerce_payments-payment-token-new' ).length && - ! $( '#wc-woocommerce_payments-payment-token-new' ).is( ':checked' ) - ); - } - - // Handle the checkout form when WooPayments is chosen. - const wcpayPaymentMethods = [ PAYMENT_METHOD_NAME_CARD ]; - const checkoutEvents = wcpayPaymentMethods - .map( ( method ) => `checkout_place_order_${ method }` ) - .join( ' ' ); - $( 'form.checkout' ).on( checkoutEvents, function () { - appendFingerprintInputToForm( $( this ), fingerprint ); - - if ( ! isUsingSavedPaymentMethod() ) { - const paymentMethodDetails = cardPayment; - return handlePaymentMethodCreation( - $( this ), - handleOrderPayment, - paymentMethodDetails - ); - } - } ); - - // Handle the Pay for Order form if WooPayments is chosen. - $( '#order_review' ).on( 'submit', () => { - if ( - isUsingSavedPaymentMethod() || - ( ! isWCPayChosen() && ! isWCPaySepaChosen() ) - ) { - return; - } - - return handlePaymentMethodCreation( - $( '#order_review' ), - handleOrderPayment, - isWCPaySepaChosen() ? sepaPayment : cardPayment - ); - } ); - - // Handle the add payment method form for WooPayments. - $( 'form#add_payment_method' ).on( 'submit', function () { - if ( - $( - "#add_payment_method input:checked[name='payment_method']" - ).val() !== 'woocommerce_payments' - ) { - return; - } - - if ( ! $( '#wcpay-setup-intent' ).val() ) { - let paymentMethodDetails = cardPayment; - if ( isWCPaySepaChosen() ) { - paymentMethodDetails = sepaPayment; - } else if ( isWCPayGiropayChosen() ) { - paymentMethodDetails = giropayPayment; - } else if ( isWCPaySofortChosen() ) { - paymentMethodDetails = sofortPayment; - } - - return handlePaymentMethodCreation( - $( 'form#add_payment_method' ), - handleAddCard, - paymentMethodDetails - ); - } - } ); - - // On every page load, check to see whether we should display the authentication - // modal and display it if it should be displayed. - maybeShowAuthenticationModal(); - - // Handle hash change - used when authenticating payment with SCA on checkout page. - window.addEventListener( 'hashchange', () => { - if ( window.location.hash.startsWith( '#wcpay-confirm-' ) ) { - maybeShowAuthenticationModal(); - } - } ); - - if ( - getConfig( 'isWooPayEnabled' ) && - getConfig( 'isWooPayEmailInputEnabled' ) && - ! isPreviewing() - ) { - handleWooPayEmailInput( '#billing_email', api ); - } -} ); diff --git a/client/checkout/classic/upe-deferred-intent-creation/payment-processing.js b/client/checkout/classic/payment-processing.js similarity index 99% rename from client/checkout/classic/upe-deferred-intent-creation/payment-processing.js rename to client/checkout/classic/payment-processing.js index 513a0b87a5e..6d47f327882 100644 --- a/client/checkout/classic/upe-deferred-intent-creation/payment-processing.js +++ b/client/checkout/classic/payment-processing.js @@ -2,7 +2,7 @@ * Internal dependencies */ import { getUPEConfig } from 'wcpay/utils/checkout'; -import { getAppearance, getFontRulesFromPage } from '../../upe-styles'; +import { getAppearance, getFontRulesFromPage } from '../upe-styles'; import showErrorCheckout from 'wcpay/checkout/utils/show-error-checkout'; import { appendFingerprintInputToForm, @@ -20,7 +20,7 @@ import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; import { SHORTCODE_SHIPPING_ADDRESS_FIELDS, SHORTCODE_BILLING_ADDRESS_FIELDS, -} from '../../constants'; +} from '../constants'; // It looks like on file import there are some side effects. Should probably be fixed. const gatewayUPEComponents = {}; diff --git a/client/checkout/classic/style.scss b/client/checkout/classic/style.scss index 606feadfdf0..1a9773719f2 100644 --- a/client/checkout/classic/style.scss +++ b/client/checkout/classic/style.scss @@ -1,31 +1,7 @@ -#payment - .payment_methods - li - .payment_box.payment_method_woocommerce_payments - fieldset { - padding: 7px 7px; -} - -#payment .payment_method_woocommerce_payments > fieldset > legend { - padding-top: 0; -} - #payment .payment_method_woocommerce_payments .testmode-info { margin-bottom: 0.5em; } -#payment .payment_method_woocommerce_payments .woocommerce-error { - margin: 1em 0 0; -} - -#wcpay-card-element, -#wcpay-sepa-element { - border: 1px solid #ddd; - padding: 5px 7px; - min-height: 29px; - margin-bottom: 0.5em; -} - #wcpay-upe-element, .wcpay-upe-element { padding: 7px 7px; @@ -36,12 +12,6 @@ } } -.wcpay-card-mounted, -.wcpay-upe-mounted, -.wcpay-sepa-mounted { - background-color: #fff; -} - .wcpay-checkout-email-field { position: relative; } diff --git a/client/checkout/classic/upe-deferred-intent-creation/test/3ds-flow-handling.test.js b/client/checkout/classic/test/3ds-flow-handling.test.js similarity index 100% rename from client/checkout/classic/upe-deferred-intent-creation/test/3ds-flow-handling.test.js rename to client/checkout/classic/test/3ds-flow-handling.test.js diff --git a/client/checkout/classic/upe-deferred-intent-creation/test/payment-processing.test.js b/client/checkout/classic/test/payment-processing.test.js similarity index 99% rename from client/checkout/classic/upe-deferred-intent-creation/test/payment-processing.test.js rename to client/checkout/classic/test/payment-processing.test.js index 05970174879..984adb378a6 100644 --- a/client/checkout/classic/upe-deferred-intent-creation/test/payment-processing.test.js +++ b/client/checkout/classic/test/payment-processing.test.js @@ -7,14 +7,14 @@ import { processPayment, renderTerms, } from '../payment-processing'; -import { getAppearance } from '../../../upe-styles'; +import { getAppearance } from '../../upe-styles'; import { getUPEConfig } from 'wcpay/utils/checkout'; import { getFingerprint } from 'wcpay/checkout/utils/fingerprint'; import showErrorCheckout from 'wcpay/checkout/utils/show-error-checkout'; import { waitFor } from '@testing-library/react'; import { getSelectedUPEGatewayPaymentMethod } from 'wcpay/checkout/utils/upe'; -jest.mock( '../../../upe-styles' ); +jest.mock( '../../upe-styles' ); jest.mock( 'wcpay/checkout/utils/upe' ); diff --git a/client/checkout/classic/test/upe-split.test.js b/client/checkout/classic/test/upe-split.test.js deleted file mode 100644 index 0b8434bb7d3..00000000000 --- a/client/checkout/classic/test/upe-split.test.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Internal dependencies - */ -import * as CheckoutUtils from 'utils/checkout'; -import { getSetupIntentFromSession } from '../upe-split'; - -import { - getSelectedUPEGatewayPaymentMethod, - isUsingSavedPaymentMethod, -} from 'wcpay/checkout/utils/upe'; - -describe( 'UPE split checkout', () => { - describe( 'isUsingSavedPaymentMethod', () => { - let container; - - beforeAll( () => { - container = document.createElement( 'div' ); - container.innerHTML = ` - - - `; - document.body.appendChild( container ); - } ); - - afterAll( () => { - document.body.removeChild( container ); - container = null; - } ); - - test( 'new CC is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments-payment-token-new' - ); - input.checked = true; - const paymentMethodType = 'card'; - - expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( - false - ); - } ); - - test( 'saved CC is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments-payment-token-new' - ); - input.checked = false; - const paymentMethodType = 'card'; - - expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( - true - ); - } ); - - test( 'new SEPA is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments_sepa_debit-payment-token-new' - ); - input.checked = true; - const paymentMethodType = 'sepa_debit'; - - expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( - false - ); - } ); - - test( 'saved SEPA is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments_sepa_debit-payment-token-new' - ); - input.checked = false; - const paymentMethodType = 'sepa_debit'; - - expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( - true - ); - } ); - - test( 'non-tokenized payment gateway is selected', () => { - const paymentMethodType = 'sofort'; - - expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( - false - ); - } ); - } ); - - describe( 'getSelectedUPEGatewayPaymentMethod', () => { - let container; - let input; - - beforeAll( () => { - container = document.createElement( 'div' ); - container.innerHTML = ` -
    -
  • - -
  • -
  • - -
  • -
- `; - document.body.appendChild( container ); - } ); - - beforeEach( () => { - const spy = jest.spyOn( CheckoutUtils, 'getUPEConfig' ); - spy.mockImplementation( ( param ) => { - if ( param === 'paymentMethodsConfig' ) { - return { card: {}, bancontact: {} }; - } - if ( param === 'gatewayId' ) { - return 'woocommerce_payments'; - } - } ); - } ); - - afterEach( () => { - input.checked = false; - jest.clearAllMocks(); - } ); - - afterAll( () => { - document.body.removeChild( container ); - container = null; - } ); - - test( 'Selected UPE Payment Method is card', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments' - ); - input.checked = true; - - expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'card' ); - } ); - - test( 'Selected UPE Payment Method is bancontact', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments_bancontact' - ); - input.checked = true; - - expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'bancontact' ); - } ); - } ); - - describe( 'getSetupIntentFromSession', () => { - beforeEach( () => { - const spy = jest.spyOn( CheckoutUtils, 'getUPEConfig' ); - spy.mockReturnValue( { - card: { - upeSetupIntentData: 'card-1234', - }, - bancontact: { - upeSetupIntentData: 'bancontact-5678', - }, - eps: { - upeSetupIntentData: null, - }, - } ); - } ); - - afterEach( () => { - jest.clearAllMocks(); - } ); - - const cardData = { clientSecret: '1234', intentId: 'card' }; - const bancontactData = { clientSecret: '5678', intentId: 'bancontact' }; - - test( 'Get setup intent data from for Card method', () => { - expect( getSetupIntentFromSession( 'card' ) ).toMatchObject( - cardData - ); - } ); - - test( 'Get setup intent data from for UPE method', () => { - expect( getSetupIntentFromSession( 'bancontact' ) ).toMatchObject( - bancontactData - ); - } ); - - test( 'Get null setup intent data returns empty object', () => { - expect( getSetupIntentFromSession( 'eps' ) ).toMatchObject( {} ); - } ); - } ); -} ); diff --git a/client/checkout/classic/test/upe.test.js b/client/checkout/classic/test/upe.test.js deleted file mode 100644 index d8586776039..00000000000 --- a/client/checkout/classic/test/upe.test.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Internal dependencies - */ -import { isUsingSavedPaymentMethod } from '../upe'; - -describe( 'UPE checkout', () => { - describe( 'isUsingSavedPaymentMethod', () => { - let container; - - beforeAll( () => { - container = document.createElement( 'div' ); - container.innerHTML = ` - - `; - document.body.appendChild( container ); - } ); - - afterAll( () => { - document.body.removeChild( container ); - container = null; - } ); - - test( 'new CC is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments-payment-token-new' - ); - input.checked = true; - - expect( isUsingSavedPaymentMethod() ).toBe( false ); - } ); - - test( 'saved CC is selected', () => { - const input = document.querySelector( - '#wc-woocommerce_payments-payment-token-new' - ); - input.checked = false; - - expect( isUsingSavedPaymentMethod() ).toBe( true ); - } ); - } ); -} ); diff --git a/client/checkout/classic/upe-split.js b/client/checkout/classic/upe-split.js deleted file mode 100644 index 05ad9f1de49..00000000000 --- a/client/checkout/classic/upe-split.js +++ /dev/null @@ -1,772 +0,0 @@ -/* global jQuery */ - -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import './style.scss'; -import { - PAYMENT_METHOD_NAME_BANCONTACT, - PAYMENT_METHOD_NAME_BECS, - PAYMENT_METHOD_NAME_CARD, - PAYMENT_METHOD_NAME_EPS, - PAYMENT_METHOD_NAME_GIROPAY, - PAYMENT_METHOD_NAME_IDEAL, - PAYMENT_METHOD_NAME_P24, - PAYMENT_METHOD_NAME_SEPA, - PAYMENT_METHOD_NAME_SOFORT, - PAYMENT_METHOD_NAME_AFFIRM, - PAYMENT_METHOD_NAME_AFTERPAY, - PAYMENT_METHOD_NAME_KLARNA, - SHORTCODE_SHIPPING_ADDRESS_FIELDS, - SHORTCODE_BILLING_ADDRESS_FIELDS, -} from '../constants'; -import { getUPEConfig } from 'utils/checkout'; -import WCPayAPI from '../api'; -import enqueueFraudScripts from 'fraud-scripts'; -import { getFontRulesFromPage, getAppearance } from '../upe-styles'; -import { - getTerms, - getPaymentIntentFromSession, - getSelectedUPEGatewayPaymentMethod, - getBillingDetails, - getShippingDetails, - getUpeSettings, - isUsingSavedPaymentMethod, - isLinkEnabled, -} from '../utils/upe'; -import { decryptClientSecret } from '../utils/encryption'; -import enableStripeLinkPaymentMethod from '../stripe-link'; -import apiRequest from '../utils/request'; -import showErrorCheckout from '../utils/show-error-checkout'; -import { - getFingerprint, - appendFingerprintInputToForm, -} from '../utils/fingerprint'; -import PAYMENT_METHOD_IDS from 'wcpay/payment-methods/constants'; - -jQuery( function ( $ ) { - enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); - - const publishableKey = getUPEConfig( 'publishableKey' ); - const isChangingPayment = getUPEConfig( 'isChangingPayment' ); - const isUPEEnabled = getUPEConfig( 'isUPEEnabled' ); - const isUPESplitEnabled = getUPEConfig( 'isUPESplitEnabled' ); - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - const isStripeLinkEnabled = isLinkEnabled( paymentMethodsConfig ); - - const gatewayUPEComponents = {}; - for ( const paymentMethodType in paymentMethodsConfig ) { - gatewayUPEComponents[ paymentMethodType ] = { - elements: null, - upeElement: null, - paymentIntentId: null, - paymentIntentClientSecret: null, - isUPEComplete: null, - country: null, - }; - } - - if ( ! publishableKey ) { - // If no configuration is present, probably this is not the checkout page. - return; - } - - // Create an API object, which will be used throughout the checkout. - const api = new WCPayAPI( - { - publishableKey, - accountId: getUPEConfig( 'accountId' ), - forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ), - locale: getUPEConfig( 'locale' ), - isUPEEnabled, - isUPESplitEnabled, - isStripeLinkEnabled, - }, - apiRequest - ); - let fingerprint = null; - - /** - * Block UI to indicate processing and avoid duplicate submission. - * - * @param {Object} $form The jQuery object for the form. - */ - const blockUI = ( $form ) => { - $form.addClass( 'processing' ).block( { - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6, - }, - } ); - }; - - /** - * Unblock UI to remove overlay and loading icon - * - * @param {Object} $form The jQuery object for the form. - */ - const unblockUI = ( $form ) => { - $form.removeClass( 'processing' ).unblock(); - }; - - /** - * Mounts Stripe UPE element if feature is enabled. - * - * @param {string} paymentMethodType Stripe payment method type. - * @param {Object} upeDOMElement DOM element or HTML selector to use to mount UPE payment element. - * @param {boolean} isSetupIntent Set to true if we are on My Account adding a payment method. - */ - const mountUPEElement = async function ( - paymentMethodType, - upeDOMElement, - isSetupIntent = false - ) { - // Do not mount UPE twice. - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - let upeElement = upeComponents.upeElement; - const paymentIntentId = upeComponents.paymentIntentId; - if ( upeElement || paymentIntentId ) { - return; - } - - if ( ! fingerprint ) { - try { - const { visitorId } = await getFingerprint(); - fingerprint = visitorId; - } catch ( error ) { - // Do not mount element if fingerprinting is not available - showErrorCheckout( error.message ); - - return; - } - } - - /* - * Trigger this event to ensure the tokenization-form.js init - * is executed. - * - * This script handles the radio input interaction when toggling - * between the user's saved card / entering new card details. - * - * Ref: https://github.com/woocommerce/woocommerce/blob/2429498/assets/js/frontend/tokenization-form.js#L109 - */ - $( document.body ).trigger( 'wc-credit-card-form-init' ); - - // If paying from order, we need to create Payment Intent from order not cart. - const isOrderPay = getUPEConfig( 'isOrderPay' ); - let orderId; - if ( isOrderPay ) { - orderId = getUPEConfig( 'orderId' ); - } - - let { intentId, clientSecret } = isSetupIntent - ? getSetupIntentFromSession( paymentMethodType ) - : getPaymentIntentFromSession( - paymentMethodsConfig, - paymentMethodType - ); - - const $upeContainer = $( upeDOMElement ); - blockUI( $upeContainer ); - - if ( ! intentId ) { - try { - const newIntent = isSetupIntent - ? await api.initSetupIntent( paymentMethodType ) - : await api.createIntent( { - fingerprint, - paymentMethodType, - orderId, - } ); - intentId = newIntent.id; - clientSecret = newIntent.client_secret; - } catch ( error ) { - unblockUI( $upeContainer ); - showErrorCheckout( error.message ); - const gatewayErrorMessage = __( - 'An error was encountered when preparing the payment form. Please try again later.', - 'woocommerce-payments' - ); - $( '.payment_box.payment_method_woocommerce_payments' ).html( - `
${ gatewayErrorMessage }
` - ); - } - } - - // I repeat, do NOT mount UPE twice. - if ( upeElement || paymentIntentId ) { - unblockUI( $upeContainer ); - return; - } - - gatewayUPEComponents[ paymentMethodType ].paymentIntentId = intentId; - - let appearance = getUPEConfig( 'upeAppearance' ); - - if ( ! appearance ) { - appearance = getAppearance(); - api.saveUPEAppearance( appearance ); - } - - const elements = api.getStripe().elements( { - clientSecret: decryptClientSecret( clientSecret ), - appearance, - fonts: getFontRulesFromPage(), - loader: 'never', - } ); - gatewayUPEComponents[ paymentMethodType ].elements = elements; - - if ( isStripeLinkEnabled ) { - enableStripeLinkPaymentMethod( { - api: api, - elements: elements, - emailId: 'billing_email', - complete_billing: () => { - return true; - }, - complete_shipping: () => { - return ( - document.getElementById( - 'ship-to-different-address-checkbox' - ) && - document.getElementById( - 'ship-to-different-address-checkbox' - ).checked - ); - }, - shipping_fields: SHORTCODE_SHIPPING_ADDRESS_FIELDS, - billing_fields: SHORTCODE_BILLING_ADDRESS_FIELDS, - } ); - } - - upeElement = elements.create( 'payment', { - ...getUpeSettings(), - wallets: { - applePay: 'never', - googlePay: 'never', - }, - } ); - upeElement.mount( upeDOMElement ); - unblockUI( $upeContainer ); - upeElement.on( 'change', ( event ) => { - const selectedUPEPaymentType = - event.value.type !== 'link' ? event.value.type : 'card'; - gatewayUPEComponents[ selectedUPEPaymentType ].country = - event.value.country; - gatewayUPEComponents[ selectedUPEPaymentType ].isUPEComplete = - event.complete; - } ); - gatewayUPEComponents[ paymentMethodType ].upeElement = upeElement; - }; - - function togglePaymentMethodForCountry( - paymentMethodType, - supportedCountries - ) { - const billingCountry = document.querySelector( '#billing_country' ) - .value; - const upeContainer = document.querySelector( - '#payment_method_woocommerce_payments_' + paymentMethodType - ).parentElement; - if ( supportedCountries.includes( billingCountry ) ) { - upeContainer.style.display = 'block'; - } else { - upeContainer.style.display = 'none'; - } - } - - function restrictPaymentMethodToLocation( paymentMethodType ) { - const isRestrictedInAnyCountry = !! paymentMethodsConfig[ - paymentMethodType - ].countries.length; - - if ( isRestrictedInAnyCountry ) { - togglePaymentMethodForCountry( - paymentMethodType, - paymentMethodsConfig[ paymentMethodType ].countries - ); - $( '#billing_country' ).on( 'change', function () { - togglePaymentMethodForCountry( - paymentMethodType, - paymentMethodsConfig[ paymentMethodType ].countries - ); - } ); - } - } - - // Only attempt to mount the card element once that section of the page has loaded. We can use the updated_checkout - // event for this. This part of the page can also reload based on changes to checkout details, so we call unmount - // first to ensure the card element is re-mounted correctly. - $( document.body ).on( 'updated_checkout', () => { - // If the card element selector doesn't exist, then do nothing (for example, when a 100% discount coupon is applied). - // We also don't re-mount if already mounted in DOM. - if ( - $( '.wcpay-upe-element' ).length && - ! $( '.wcpay-upe-element' ).children().length && - isUPEEnabled - ) { - const upeDOMElements = $( '.wcpay-upe-element' ); - for ( let i = 0; i < upeDOMElements.length; i++ ) { - const upeDOMElement = upeDOMElements[ i ]; - const paymentMethodType = $( upeDOMElement ).attr( - 'data-payment-method-type' - ); - - const upeElement = - gatewayUPEComponents[ paymentMethodType ].upeElement; - if ( upeElement ) { - upeElement.mount( upeDOMElement ); - } else { - mountUPEElement( paymentMethodType, upeDOMElement ); - } - - restrictPaymentMethodToLocation( paymentMethodType ); - } - } - } ); - - if ( - $( 'form#add_payment_method' ).length || - $( 'form#order_review' ).length - ) { - if ( - $( '.wcpay-upe-element' ).length && - ! $( '.wcpay-upe-element' ).children().length && - isUPEEnabled - ) { - // We use a setup intent if we are on the screens to add a new payment method or to change a subscription payment. - const useSetUpIntent = - $( 'form#add_payment_method' ).length || isChangingPayment; - - if ( isChangingPayment && getUPEConfig( 'newTokenFormId' ) ) { - // Changing the method for a subscription takes two steps: - // 1. Create the new payment method that will redirect back. - // 2. Select the new payment method and resubmit the form to update the subscription. - const token = getUPEConfig( 'newTokenFormId' ); - $( token ).prop( 'selected', true ).trigger( 'click' ); - $( 'form#order_review' ).submit(); - } - const upeDOMElements = $( '.wcpay-upe-element' ); - for ( let i = 0; i < upeDOMElements.length; i++ ) { - const upeDOMElement = upeDOMElements[ i ]; - const paymentMethodType = $( upeDOMElement ).attr( - 'data-payment-method-type' - ); - mountUPEElement( - paymentMethodType, - upeDOMElement, - useSetUpIntent - ); - } - } - } - - /** - * Checks if UPE form is filled out. Displays errors if not. - * - * @param {Object} $form The jQuery object for the form. - * @param {string} returnUrl The `return_url` param. Defaults to '#' (optional) - * @return {boolean} false if incomplete. - */ - const checkUPEForm = async ( $form, returnUrl = '#' ) => { - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - const upeElement = upeComponents.upeElement; - const elements = upeComponents.elements; - const isUPEComplete = upeComponents.isUPEComplete; - - if ( ! upeElement ) { - showErrorCheckout( - __( - 'Your payment information is incomplete.', - 'woocommerce-payments' - ) - ); - return false; - } - if ( ! isUPEComplete ) { - // If UPE fields are not filled, confirm payment to trigger validation errors - const { error } = await api.handlePaymentConfirmation( - elements, - { - return_url: returnUrl, - }, - null - ); - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - return false; - } - return true; - }; - /** - * Submits the confirmation of the intent to Stripe on Pay for Order page. - * Stripe redirects to Order Thank you page on sucess. - * - * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. - */ - const handleUPEOrderPay = async ( $form ) => { - const isSavingPaymentMethod = $( - '#wc-woocommerce_payments-new-payment-method' - ).is( ':checked' ); - const savePaymentMethod = isSavingPaymentMethod ? 'yes' : 'no'; - - const returnUrl = - getUPEConfig( 'orderReturnURL' ) + - `&save_payment_method=${ savePaymentMethod }`; - - const orderId = getUPEConfig( 'orderId' ); - - const isUPEFormValid = await checkUPEForm( - $( '#order_review' ), - returnUrl - ); - if ( ! isUPEFormValid ) { - return; - } - blockUI( $form ); - - try { - // Update payment intent with level3 data, customer and maybe setup for future use. - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - const updateResponse = await api.updateIntent( - upeComponents.paymentIntentId, - orderId, - savePaymentMethod, - paymentMethodType, - upeComponents.country - ); - - if ( updateResponse.data ) { - if ( updateResponse.data.error ) { - throw updateResponse.data.error; - } - - if ( api.handleDuplicatePayments( updateResponse.data ) ) { - return; - } - } - - const { error } = await api.handlePaymentConfirmation( - upeComponents.elements, - { - return_url: returnUrl, - }, - getPaymentIntentSecret( paymentMethodType ) - ); - if ( error ) { - throw error; - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Submits the confirmation of the setup intent to Stripe on Add Payment Method page. - * Stripe redirects to Payment Methods page on sucess. - * - * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. - */ - const handleUPEAddPayment = async ( $form ) => { - const returnUrl = getUPEConfig( 'addPaymentReturnURL' ); - const isUPEFormValid = await checkUPEForm( $form, returnUrl ); - - if ( ! isUPEFormValid ) { - return; - } - - blockUI( $form ); - - try { - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - const { error } = await api.getStripe().confirmSetup( { - elements: upeComponents.elements, - confirmParams: { - return_url: returnUrl, - }, - } ); - if ( error ) { - throw error; - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Submits checkout form via AJAX to create order and uses custom - * redirect URL in AJAX response to request payment confirmation from UPE - * - * @param {Object} $form The jQuery object for the form. - * @param {string} paymentMethodType Stripe payment method type ID. - * @return {boolean} A flag for the event handler. - */ - const handleUPECheckout = async ( $form, paymentMethodType ) => { - const isUPEFormValid = await checkUPEForm( $form ); - if ( ! isUPEFormValid ) { - return; - } - blockUI( $form ); - // Create object where keys are form field names and keys are form field values - const formFields = $form.serializeArray().reduce( ( obj, field ) => { - obj[ field.name ] = field.value; - return obj; - }, {} ); - - try { - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - formFields.wcpay_payment_country = upeComponents.country; - const response = await api.processCheckout( - upeComponents.paymentIntentId, - formFields, - fingerprint ? fingerprint : '' - ); - const redirectUrl = response.redirect_url; - const upeConfig = { - elements: upeComponents.elements, - confirmParams: { - return_url: redirectUrl, - payment_method_data: { - billing_details: getBillingDetails( formFields ), - }, - }, - }; - // Afterpay requires shipping details to be passed. Not needed by other payment methods. - if ( PAYMENT_METHOD_IDS.AFTERPAY_CLEARPAY === paymentMethodType ) { - upeConfig.confirmParams.shipping = getShippingDetails( - formFields - ); - } - let error; - if ( response.payment_needed ) { - ( { error } = await api.handlePaymentConfirmation( - upeComponents.elements, - upeConfig.confirmParams, - getPaymentIntentSecret( paymentMethodType ) - ) ); - } else { - ( { error } = await api.getStripe().confirmSetup( upeConfig ) ); - } - if ( error ) { - // Log payment errors on charge and then throw the error. - const logError = await api.logPaymentError( error.charge ); - if ( logError ) { - throw error; - } - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Displays the authentication modal to the user if needed. - */ - const maybeShowAuthenticationModal = () => { - const paymentMethodId = $( '#wcpay-payment-method' ).val(); - - const savePaymentMethod = $( - '#wc-woocommerce_payments-new-payment-method' - ).is( ':checked' ); - const confirmation = api.confirmIntent( - window.location.href, - savePaymentMethod ? paymentMethodId : null - ); - - // Boolean `true` means that there is nothing to confirm. - if ( confirmation === true ) { - return; - } - - const { request, isOrderPage } = confirmation; - - if ( isOrderPage ) { - blockUI( $( '#order_review' ) ); - $( '#payment' ).hide( 500 ); - } - - // Cleanup the URL. - // https://stackoverflow.com/a/5298684 - history.replaceState( - '', - document.title, - window.location.pathname + window.location.search - ); - - request - .then( ( redirectUrl ) => { - window.location = redirectUrl; - } ) - .catch( ( error ) => { - $( 'form.checkout' ).removeClass( 'processing' ).unblock(); - $( '#order_review' ).removeClass( 'processing' ).unblock(); - $( '#payment' ).show( 500 ); - - let errorMessage = error.message; - - // If this is a generic error, we probably don't want to display the error message to the user, - // so display a generic message instead. - if ( error instanceof Error ) { - errorMessage = getUPEConfig( 'genericErrorMessage' ); - } - - showErrorCheckout( errorMessage ); - } ); - }; - - /** - * Returns stripe intent secret that will be used to confirm payment - * - * @param {string} paymentMethodType Stripe payment method type ID. - * @return {string | null} The intent secret required to confirm payment during the rate limit error. - */ - function getPaymentIntentSecret( paymentMethodType ) { - const upeComponents = gatewayUPEComponents[ paymentMethodType ]; - if ( upeComponents.paymentIntentClientSecret ) { - return upeComponents.paymentIntentClientSecret; - } - const { clientSecret } = getPaymentIntentFromSession( - paymentMethodsConfig, - paymentMethodType - ); - return clientSecret ? clientSecret : null; - } - - // Handle the checkout form when WooPayments is chosen. - const wcpayPaymentMethods = [ - PAYMENT_METHOD_NAME_BANCONTACT, - PAYMENT_METHOD_NAME_BECS, - PAYMENT_METHOD_NAME_EPS, - PAYMENT_METHOD_NAME_GIROPAY, - PAYMENT_METHOD_NAME_IDEAL, - PAYMENT_METHOD_NAME_P24, - PAYMENT_METHOD_NAME_SEPA, - PAYMENT_METHOD_NAME_SOFORT, - PAYMENT_METHOD_NAME_AFFIRM, - PAYMENT_METHOD_NAME_AFTERPAY, - PAYMENT_METHOD_NAME_KLARNA, - paymentMethodsConfig.card !== undefined && PAYMENT_METHOD_NAME_CARD, - ].filter( Boolean ); - const checkoutEvents = wcpayPaymentMethods - .map( ( method ) => `checkout_place_order_${ method }` ) - .join( ' ' ); - $( 'form.checkout' ).on( checkoutEvents, function () { - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - if ( ! isUsingSavedPaymentMethod( paymentMethodType ) ) { - const paymentIntentId = - gatewayUPEComponents[ paymentMethodType ].paymentIntentId; - if ( isUPEEnabled && paymentIntentId ) { - handleUPECheckout( $( this ), paymentMethodType ); - return false; - } - } - - appendFingerprintInputToForm( $( this ), fingerprint ); - } ); - - // Handle the add payment method form for WooPayments. - $( 'form#add_payment_method' ).on( 'submit', function () { - // Skip adding legacy cards as UPE payment methods. - if ( - $( - "#add_payment_method input:checked[name='payment_method']" - ).val() === 'woocommerce_payments' && - isUPESplitEnabled === '0' - ) { - return; - } - if ( ! $( '#wcpay-setup-intent' ).val() ) { - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - const paymentIntentId = - gatewayUPEComponents[ paymentMethodType ].paymentIntentId; - if ( isUPEEnabled && paymentIntentId ) { - handleUPEAddPayment( $( this ) ); - return false; - } - } - } ); - - // Handle the Pay for Order form if WooPayments is chosen. - $( '#order_review' ).on( 'submit', () => { - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - if ( - ! isUsingSavedPaymentMethod( paymentMethodType ) && - paymentMethodType !== null - ) { - if ( isChangingPayment ) { - handleUPEAddPayment( $( '#order_review' ) ); - return false; - } - handleUPEOrderPay( $( '#order_review' ) ); - return false; - } - } ); - - // Add terms parameter to UPE if save payment information checkbox is checked. - $( document ).on( - 'change', - '#wc-woocommerce_payments-new-payment-method', - () => { - const value = $( '#wc-woocommerce_payments-new-payment-method' ).is( - ':checked' - ) - ? 'always' - : 'never'; - const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); - if ( ! paymentMethodType ) { - return; - } - const upeElement = - gatewayUPEComponents[ paymentMethodType ].upeElement; - if ( isUPEEnabled && upeElement ) { - upeElement.update( { - terms: getTerms( paymentMethodsConfig, value ), - } ); - } - } - ); - - // On every page load, check to see whether we should display the authentication - // modal and display it if it should be displayed. - maybeShowAuthenticationModal(); - - // Handle hash change - used when authenticating payment with SCA on checkout page. - window.addEventListener( 'hashchange', () => { - if ( window.location.hash.startsWith( '#wcpay-confirm-' ) ) { - maybeShowAuthenticationModal(); - } - } ); -} ); - -/** - * Returns the cached setup intent. - * - * @param {string} paymentMethodType Stripe payment method type ID. - * @return {Object} The intent id and client secret required for mounting the UPE element. - */ -export function getSetupIntentFromSession( paymentMethodType ) { - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - const upeSetupIntentData = - paymentMethodsConfig[ paymentMethodType ].upeSetupIntentData; - if ( upeSetupIntentData ) { - const intentId = upeSetupIntentData.split( '-' )[ 0 ]; - const clientSecret = upeSetupIntentData.split( '-' )[ 1 ]; - return { intentId, clientSecret }; - } - - return {}; -} diff --git a/client/checkout/classic/upe.js b/client/checkout/classic/upe.js deleted file mode 100644 index 519a6f39b80..00000000000 --- a/client/checkout/classic/upe.js +++ /dev/null @@ -1,759 +0,0 @@ -/* global jQuery */ - -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import './style.scss'; -import { - PAYMENT_METHOD_NAME_CARD, - PAYMENT_METHOD_NAME_UPE, - SHORTCODE_SHIPPING_ADDRESS_FIELDS, - SHORTCODE_BILLING_ADDRESS_FIELDS, -} from '../constants'; -import { getConfig, getCustomGatewayTitle } from 'utils/checkout'; -import WCPayAPI from '../api'; -import enqueueFraudScripts from 'fraud-scripts'; -import { getFontRulesFromPage, getAppearance } from '../upe-styles'; -import { - getTerms, - getCookieValue, - isWCPayChosen, - isLinkEnabled, - getBillingDetails, - getShippingDetails, -} from '../utils/upe'; -import { decryptClientSecret } from '../utils/encryption'; -import enableStripeLinkPaymentMethod from '../stripe-link'; -import apiRequest from '../utils/request'; -import showErrorCheckout from '../utils/show-error-checkout'; -import { - getFingerprint, - appendFingerprintInputToForm, -} from '../utils/fingerprint'; -import PAYMENT_METHOD_IDS from 'wcpay/payment-methods/constants'; - -jQuery( function ( $ ) { - enqueueFraudScripts( getConfig( 'fraudServices' ) ); - - const publishableKey = getConfig( 'publishableKey' ); - const isChangingPayment = getConfig( 'isChangingPayment' ); - const isUPEEnabled = getConfig( 'isUPEEnabled' ); - const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' ); - const enabledBillingFields = getConfig( 'enabledBillingFields' ); - const upePaymentIntentData = getConfig( 'upePaymentIntentData' ); - const upeSetupIntentData = getConfig( 'upeSetupIntentData' ); - const isStripeLinkEnabled = isLinkEnabled( paymentMethodsConfig ); - - if ( ! publishableKey ) { - // If no configuration is present, probably this is not the checkout page. - return; - } - - // Create an API object, which will be used throughout the checkout. - const api = new WCPayAPI( - { - publishableKey, - accountId: getConfig( 'accountId' ), - forceNetworkSavedCards: getConfig( 'forceNetworkSavedCards' ), - locale: getConfig( 'locale' ), - isUPEEnabled, - isStripeLinkEnabled, - }, - apiRequest - ); - - let elements = null; - let upeElement = null; - let paymentIntentId = null; - let paymentIntentClientSecret = null; - let isUPEComplete = false; - let fingerprint = null; - - const hiddenBillingFields = { - name: - enabledBillingFields.includes( 'billing_first_name' ) || - enabledBillingFields.includes( 'billing_last_name' ) - ? 'never' - : 'auto', - email: enabledBillingFields.includes( 'billing_email' ) - ? 'never' - : 'auto', - phone: enabledBillingFields.includes( 'billing_phone' ) - ? 'never' - : 'auto', - address: { - country: enabledBillingFields.includes( 'billing_country' ) - ? 'never' - : 'auto', - line1: enabledBillingFields.includes( 'billing_address_1' ) - ? 'never' - : 'auto', - line2: enabledBillingFields.includes( 'billing_address_2' ) - ? 'never' - : 'auto', - city: enabledBillingFields.includes( 'billing_city' ) - ? 'never' - : 'auto', - state: enabledBillingFields.includes( 'billing_state' ) - ? 'never' - : 'auto', - postalCode: enabledBillingFields.includes( 'billing_postcode' ) - ? 'never' - : 'auto', - }, - }; - - /** - * Block UI to indicate processing and avoid duplicate submission. - * - * @param {Object} $form The jQuery object for the form. - */ - const blockUI = ( $form ) => { - $form.addClass( 'processing' ).block( { - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6, - }, - } ); - }; - - /** - * Unblock UI to remove overlay and loading icon - * - * @param {Object} $form The jQuery object for the form. - */ - const unblockUI = ( $form ) => { - $form.removeClass( 'processing' ).unblock(); - }; - - // Show or hide save payment information checkbox - const showNewPaymentMethodCheckbox = ( show = true ) => { - if ( show ) { - $( '.woocommerce-SavedPaymentMethods-saveNew' ).show(); - } else { - $( '.woocommerce-SavedPaymentMethods-saveNew' ).hide(); - $( 'input#wc-woocommerce_payments-new-payment-method' ).prop( - 'checked', - false - ); - $( 'input#wc-woocommerce_payments-new-payment-method' ).trigger( - 'change' - ); - } - }; - - // Set the selected UPE payment type field - const setSelectedUPEPaymentType = ( paymentType ) => { - $( '#wcpay_selected_upe_payment_type' ).val( paymentType ); - }; - - // Get the selected UPE payment type field - const getSelectedUPEPaymentType = () => { - return $( '#wcpay_selected_upe_payment_type' ).val(); - }; - - // Set the payment country field - const setPaymentCountry = ( country ) => { - $( '#wcpay_payment_country' ).val( country ); - }; - - /** - * Mounts Stripe UPE element if feature is enabled. - * - * @param {boolean} isSetupIntent {Boolean} isSetupIntent Set to true if we are on My Account adding a payment method. - */ - const mountUPEElement = async function ( isSetupIntent = false ) { - // Do not mount UPE twice. - if ( upeElement || paymentIntentId ) { - return; - } - - if ( ! fingerprint ) { - try { - const { visitorId } = await getFingerprint(); - fingerprint = visitorId; - } catch ( error ) { - // Do not mount element if fingerprinting is not available - showErrorCheckout( error.message ); - - return; - } - } - - /* - * Trigger this event to ensure the tokenization-form.js init - * is executed. - * - * This script handles the radio input interaction when toggling - * between the user's saved card / entering new card details. - * - * Ref: https://github.com/woocommerce/woocommerce/blob/2429498/assets/js/frontend/tokenization-form.js#L109 - */ - $( document.body ).trigger( 'wc-credit-card-form-init' ); - - // If paying from order, we need to create Payment Intent from order not cart. - const isOrderPay = getConfig( 'isOrderPay' ); - const isCheckout = getConfig( 'isCheckout' ); - let orderId; - if ( isOrderPay ) { - orderId = getConfig( 'orderId' ); - } - - let { intentId, clientSecret } = isSetupIntent - ? getSetupIntentFromSession() - : getPaymentIntentFromSession(); - - const $upeContainer = $( '#wcpay-upe-element' ); - blockUI( $upeContainer ); - - if ( ! intentId ) { - try { - const newIntent = isSetupIntent - ? await api.initSetupIntent() - : await api.createIntent( { fingerprint, orderId } ); - intentId = newIntent.id; - clientSecret = newIntent.client_secret; - } catch ( error ) { - unblockUI( $upeContainer ); - showErrorCheckout( error.message ); - const gatewayErrorMessage = __( - 'An error was encountered when preparing the payment form. Please try again later.', - 'woocommerce-payments' - ); - $( '.payment_box.payment_method_woocommerce_payments' ).html( - `
${ gatewayErrorMessage }
` - ); - } - } - - // I repeat, do NOT mount UPE twice. - if ( upeElement || paymentIntentId ) { - unblockUI( $upeContainer ); - return; - } - - paymentIntentId = intentId; - paymentIntentClientSecret = clientSecret; - - let appearance = getConfig( 'upeAppearance' ); - - if ( ! appearance ) { - appearance = getAppearance(); - api.saveUPEAppearance( appearance ); - } - - elements = api.getStripe().elements( { - clientSecret: decryptClientSecret( clientSecret ), - appearance, - fonts: getFontRulesFromPage(), - loader: 'never', - } ); - - if ( isStripeLinkEnabled ) { - enableStripeLinkPaymentMethod( { - api: api, - elements: elements, - emailId: 'billing_email', - complete_billing: () => { - return true; - }, - complete_shipping: () => { - return ( - document.getElementById( - 'ship-to-different-address-checkbox' - ) && - document.getElementById( - 'ship-to-different-address-checkbox' - ).checked - ); - }, - shipping_fields: SHORTCODE_SHIPPING_ADDRESS_FIELDS, - billing_fields: SHORTCODE_BILLING_ADDRESS_FIELDS, - } ); - } - - const upeSettings = {}; - if ( getConfig( 'cartContainsSubscription' ) ) { - upeSettings.terms = getTerms( paymentMethodsConfig, 'always' ); - } - if ( isCheckout && ! ( isOrderPay || isChangingPayment ) ) { - upeSettings.fields = { - billingDetails: hiddenBillingFields, - }; - } - - upeElement = elements.create( 'payment', { - ...upeSettings, - wallets: { - applePay: 'never', - googlePay: 'never', - }, - } ); - upeElement.mount( '#wcpay-upe-element' ); - unblockUI( $upeContainer ); - upeElement.on( 'change', ( event ) => { - const selectedUPEPaymentType = event.value.type; - const isPaymentMethodReusable = - paymentMethodsConfig[ selectedUPEPaymentType ].isReusable; - showNewPaymentMethodCheckbox( isPaymentMethodReusable ); - setSelectedUPEPaymentType( selectedUPEPaymentType ); - setPaymentCountry( event.value.country ); - isUPEComplete = event.complete; - } ); - }; - - const renameGatewayTitle = () => - $( 'label[for=payment_method_woocommerce_payments]' ).text( - getCustomGatewayTitle( paymentMethodsConfig ) - ); - - // Only attempt to mount the card element once that section of the page has loaded. We can use the updated_checkout - // event for this. This part of the page can also reload based on changes to checkout details, so we call unmount - // first to ensure the card element is re-mounted correctly. - $( document.body ).on( 'updated_checkout', () => { - // If the card element selector doesn't exist, then do nothing (for example, when a 100% discount coupon is applied). - // We also don't re-mount if already mounted in DOM. - if ( - $( '#wcpay-upe-element' ).length && - ! $( '#wcpay-upe-element' ).children().length && - isUPEEnabled - ) { - if ( upeElement ) { - upeElement.mount( '#wcpay-upe-element' ); - } else { - mountUPEElement(); - } - renameGatewayTitle(); - } - } ); - - if ( - $( 'form#add_payment_method' ).length || - $( 'form#order_review' ).length - ) { - if ( - $( '#wcpay-upe-element' ).length && - ! $( '#wcpay-upe-element' ).children().length && - isUPEEnabled && - ! upeElement - ) { - renameGatewayTitle(); - - // We use a setup intent if we are on the screens to add a new payment method or to change a subscription payment. - const useSetUpIntent = - $( 'form#add_payment_method' ).length || isChangingPayment; - - if ( isChangingPayment && getConfig( 'newTokenFormId' ) ) { - // Changing the method for a subscription takes two steps: - // 1. Create the new payment method that will redirect back. - // 2. Select the new payment method and resubmit the form to update the subscription. - const token = getConfig( 'newTokenFormId' ); - $( token ).prop( 'selected', true ).trigger( 'click' ); - $( 'form#order_review' ).submit(); - } - mountUPEElement( useSetUpIntent ); - } - } - - /** - * Checks if UPE form is filled out. Displays errors if not. - * - * @param {Object} $form The jQuery object for the form. - * @param {string} returnUrl The `return_url` param. Defaults to '#' (optional) - * @return {boolean} false if incomplete. - */ - const checkUPEForm = async ( $form, returnUrl = '#' ) => { - if ( ! upeElement ) { - showErrorCheckout( - __( - 'Your payment information is incomplete.', - 'woocommerce-payments' - ) - ); - return false; - } - if ( ! isUPEComplete ) { - // If UPE fields are not filled, confirm payment to trigger validation errors - const { error } = await api.handlePaymentConfirmation( - elements, - { - return_url: returnUrl, - }, - null - ); - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - return false; - } - return true; - }; - /** - * Submits the confirmation of the intent to Stripe on Pay for Order page. - * Stripe redirects to Order Thank you page on sucess. - * - * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. - */ - const handleUPEOrderPay = async ( $form ) => { - const isSavingPaymentMethod = $( - '#wc-woocommerce_payments-new-payment-method' - ).is( ':checked' ); - const savePaymentMethod = isSavingPaymentMethod ? 'yes' : 'no'; - - const returnUrl = - getConfig( 'orderReturnURL' ) + - `&save_payment_method=${ savePaymentMethod }`; - - const orderId = getConfig( 'orderId' ); - - const isUPEFormValid = await checkUPEForm( - $( '#order_review' ), - returnUrl - ); - if ( ! isUPEFormValid ) { - return; - } - blockUI( $form ); - - try { - // Update payment intent with level3 data, customer and maybe setup for future use. - const updateResponse = await api.updateIntent( - paymentIntentId, - orderId, - savePaymentMethod, - getSelectedUPEPaymentType(), - $( '#wcpay_payment_country' ).val() - ); - - if ( updateResponse.data ) { - if ( updateResponse.data.error ) { - throw updateResponse.data.error; - } - - if ( api.handleDuplicatePayments( updateResponse.data ) ) { - return; - } - } - - const { error } = await api.handlePaymentConfirmation( - elements, - { - return_url: returnUrl, - }, - getPaymentIntentSecret() - ); - if ( error ) { - throw error; - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Submits the confirmation of the setup intent to Stripe on Add Payment Method page. - * Stripe redirects to Payment Methods page on sucess. - * - * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. - */ - const handleUPEAddPayment = async ( $form ) => { - const returnUrl = getConfig( 'addPaymentReturnURL' ); - const isUPEFormValid = await checkUPEForm( $form, returnUrl ); - - if ( ! isUPEFormValid ) { - return; - } - - blockUI( $form ); - - try { - const { error } = await api.getStripe().confirmSetup( { - elements, - confirmParams: { - return_url: returnUrl, - }, - } ); - if ( error ) { - throw error; - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Submits checkout form via AJAX to create order and uses custom - * redirect URL in AJAX response to request payment confirmation from UPE - * - * @param {Object} $form The jQuery object for the form. - * @return {boolean} A flag for the event handler. - */ - const handleUPECheckout = async ( $form ) => { - const isUPEFormValid = await checkUPEForm( $form ); - if ( ! isUPEFormValid ) { - return; - } - - blockUI( $form ); - // Create object where keys are form field names and keys are form field values - const formFields = $form.serializeArray().reduce( ( obj, field ) => { - obj[ field.name ] = field.value; - return obj; - }, {} ); - try { - const response = await api.processCheckout( - paymentIntentId, - formFields, - fingerprint ? fingerprint : '' - ); - - if ( api.handleDuplicatePayments( response ) ) { - return; - } - - const redirectUrl = response.redirect_url; - const upeConfig = { - elements, - confirmParams: { - return_url: redirectUrl, - payment_method_data: { - billing_details: getBillingDetails( formFields ), - }, - }, - }; - const paymentMethodType = getSelectedUPEPaymentType(); - // Afterpay requires shipping details to be passed. Not needed by other payment methods. - if ( PAYMENT_METHOD_IDS.AFTERPAY_CLEARPAY === paymentMethodType ) { - upeConfig.confirmParams.shipping = getShippingDetails( - formFields - ); - } - let error; - if ( response.payment_needed ) { - ( { error } = await api.handlePaymentConfirmation( - elements, - upeConfig.confirmParams, - getPaymentIntentSecret() - ) ); - } else { - ( { error } = await api.getStripe().confirmSetup( upeConfig ) ); - } - if ( error ) { - // Log payment errors on charge and then throw the error. - const logError = await api.logPaymentError( error.charge ); - if ( logError ) { - throw error; - } - } - } catch ( error ) { - $form.removeClass( 'processing' ).unblock(); - showErrorCheckout( error.message ); - } - }; - - /** - * Displays the authentication modal to the user if needed. - */ - const maybeShowAuthenticationModal = () => { - const paymentMethodId = $( '#wcpay-payment-method' ).val(); - - const savePaymentMethod = $( - '#wc-woocommerce_payments-new-payment-method' - ).is( ':checked' ); - const confirmation = api.confirmIntent( - window.location.href, - savePaymentMethod ? paymentMethodId : null - ); - - // Boolean `true` means that there is nothing to confirm. - if ( confirmation === true ) { - return; - } - - const { request, isOrderPage } = confirmation; - - if ( isOrderPage ) { - blockUI( $( '#order_review' ) ); - $( '#payment' ).hide( 500 ); - } - - // Cleanup the URL. - // https://stackoverflow.com/a/5298684 - history.replaceState( - '', - document.title, - window.location.pathname + window.location.search - ); - - request - .then( ( redirectUrl ) => { - window.location = redirectUrl; - } ) - .catch( ( error ) => { - $( 'form.checkout' ).removeClass( 'processing' ).unblock(); - $( '#order_review' ).removeClass( 'processing' ).unblock(); - $( '#payment' ).show( 500 ); - - let errorMessage = error.message; - - // If this is a generic error, we probably don't want to display the error message to the user, - // so display a generic message instead. - if ( error instanceof Error ) { - errorMessage = getConfig( 'genericErrorMessage' ); - } - - showErrorCheckout( errorMessage ); - } ); - }; - - /** - * Returns the cached payment intent for the current cart state. - * - * @return {Object} The intent id and client secret required for mounting the UPE element. - */ - function getPaymentIntentFromSession() { - const cartHash = getCookieValue( 'woocommerce_cart_hash' ); - - if ( - cartHash && - upePaymentIntentData && - upePaymentIntentData.startsWith( cartHash ) - ) { - const intentId = upePaymentIntentData.split( '-' )[ 1 ]; - const clientSecret = upePaymentIntentData.split( '-' )[ 2 ]; - return { intentId, clientSecret }; - } - - return {}; - } - - /** - * Returns the cached setup intent. - * - * @return {Object} The intent id and client secret required for mounting the UPE element. - */ - function getSetupIntentFromSession() { - if ( upeSetupIntentData ) { - const intentId = upeSetupIntentData.split( '-' )[ 0 ]; - const clientSecret = upeSetupIntentData.split( '-' )[ 1 ]; - return { intentId, clientSecret }; - } - - return {}; - } - - /** - * Returns stripe intent secret that will be used to confirm payment - * - * @return {string | null} The intent secret required to confirm payment during the rate limit error. - */ - function getPaymentIntentSecret() { - if ( paymentIntentClientSecret ) { - return paymentIntentClientSecret; - } - const { clientSecret } = getPaymentIntentFromSession(); - return clientSecret ? clientSecret : null; - } - - // Handle the checkout form when WooPayments is chosen. - const wcpayPaymentMethods = [ - PAYMENT_METHOD_NAME_CARD, - PAYMENT_METHOD_NAME_UPE, - ]; - const checkoutEvents = wcpayPaymentMethods - .map( ( method ) => `checkout_place_order_${ method }` ) - .join( ' ' ); - $( 'form.checkout' ).on( checkoutEvents, function () { - if ( ! isUsingSavedPaymentMethod() ) { - if ( isUPEEnabled && paymentIntentId ) { - handleUPECheckout( $( this ) ); - return false; - } - } - - appendFingerprintInputToForm( $( this ), fingerprint ); - } ); - - // Handle the add payment method form for WooPayments. - $( 'form#add_payment_method' ).on( 'submit', function () { - if ( - $( - "#add_payment_method input:checked[name='payment_method']" - ).val() !== 'woocommerce_payments' - ) { - return; - } - - if ( ! $( '#wcpay-setup-intent' ).val() ) { - if ( isUPEEnabled && paymentIntentId ) { - handleUPEAddPayment( $( this ) ); - return false; - } - } - } ); - - // Handle the Pay for Order form if WooPayments is chosen. - $( '#order_review' ).on( 'submit', () => { - if ( ! isUsingSavedPaymentMethod() && isWCPayChosen() ) { - if ( isChangingPayment ) { - handleUPEAddPayment( $( '#order_review' ) ); - return false; - } - handleUPEOrderPay( $( '#order_review' ) ); - return false; - } - } ); - - // Add terms parameter to UPE if save payment information checkbox is checked. - $( document ).on( - 'change', - '#wc-woocommerce_payments-new-payment-method', - () => { - const value = $( '#wc-woocommerce_payments-new-payment-method' ).is( - ':checked' - ) - ? 'always' - : 'never'; - if ( isUPEEnabled && upeElement ) { - upeElement.update( { - terms: getTerms( paymentMethodsConfig, value ), - } ); - } - } - ); - - // On every page load, check to see whether we should display the authentication - // modal and display it if it should be displayed. - maybeShowAuthenticationModal(); - - // Handle hash change - used when authenticating payment with SCA on checkout page. - window.addEventListener( 'hashchange', () => { - if ( window.location.hash.startsWith( '#wcpay-confirm-' ) ) { - maybeShowAuthenticationModal(); - } - } ); -} ); - -/** - * Checks if the customer is using a saved payment method. - * - * @return {boolean} Boolean indicating whether or not a saved payment method is being used. - */ -export function isUsingSavedPaymentMethod() { - return ( - document.querySelector( - '#wc-woocommerce_payments-payment-token-new' - ) !== null && - ! document.querySelector( '#wc-woocommerce_payments-payment-token-new' ) - .checked - ); -} diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index 6251b97bb82..c6bbd8e726e 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -10,6 +10,8 @@ import { getUpeSettings, getStripeElementOptions, blocksShowLinkButtonHandler, + getSelectedUPEGatewayPaymentMethod, + isUsingSavedPaymentMethod, } from '../upe'; import { getPaymentMethodsConstants } from '../../constants'; import { getUPEConfig } from 'wcpay/utils/checkout'; @@ -23,6 +25,66 @@ jest.mock( '../../constants', () => { } ); describe( 'UPE checkout utils', () => { + describe( 'getSelectedUPEGatewayPaymentMethod', () => { + let container; + let input; + + beforeAll( () => { + container = document.createElement( 'div' ); + container.innerHTML = ` +
    +
  • + +
  • +
  • + +
  • +
+ `; + document.body.appendChild( container ); + } ); + + beforeEach( () => { + getUPEConfig.mockImplementation( ( argument ) => { + if ( argument === 'paymentMethodsConfig' ) { + return { card: {}, bancontact: {} }; + } + + if ( argument === 'gatewayId' ) { + return 'woocommerce_payments'; + } + } ); + } ); + + afterEach( () => { + input.checked = false; + jest.clearAllMocks(); + } ); + + afterAll( () => { + document.body.removeChild( container ); + container = null; + } ); + + test( 'Selected UPE Payment Method is card', () => { + input = document.querySelector( + '#payment_method_woocommerce_payments' + ); + input.checked = true; + + expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'card' ); + } ); + + test( 'Selected UPE Payment Method is bancontact', () => { + input = document.querySelector( + '#payment_method_woocommerce_payments_bancontact' + ); + input.checked = true; + + expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'bancontact' ); + } ); + } ); + describe( 'getTerms', () => { const paymentMethods = { card: { @@ -467,3 +529,73 @@ describe( 'blocksShowLinkButtonHandler', () => { expect( stripeLinkButton.style.display ).toEqual( 'inline-block' ); } ); } ); + +describe( 'isUsingSavedPaymentMethod', () => { + let container; + + beforeAll( () => { + container = document.createElement( 'div' ); + container.innerHTML = ` + + + `; + document.body.appendChild( container ); + } ); + + afterAll( () => { + document.body.removeChild( container ); + container = null; + } ); + + test( 'new CC is selected', () => { + const input = document.querySelector( + '#wc-woocommerce_payments-payment-token-new' + ); + input.checked = true; + const paymentMethodType = 'card'; + + expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( false ); + } ); + + test( 'saved CC is selected', () => { + const input = document.querySelector( + '#wc-woocommerce_payments-payment-token-new' + ); + input.checked = false; + const paymentMethodType = 'card'; + + expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( true ); + } ); + + test( 'new SEPA is selected', () => { + const input = document.querySelector( + '#wc-woocommerce_payments_sepa_debit-payment-token-new' + ); + input.checked = true; + const paymentMethodType = 'sepa_debit'; + + expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( false ); + } ); + + test( 'saved SEPA is selected', () => { + const input = document.querySelector( + '#wc-woocommerce_payments_sepa_debit-payment-token-new' + ); + input.checked = false; + const paymentMethodType = 'sepa_debit'; + + expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( true ); + } ); + + test( 'non-tokenized payment gateway is selected', () => { + const paymentMethodType = 'sofort'; + + expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( false ); + } ); +} ); diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 8815afe74f7..29ec2f85bbc 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -91,8 +91,6 @@ public function __construct( */ public function init_hooks() { add_action( 'wc_payments_add_payment_fields', [ $this, 'payment_fields' ] ); - - add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts_for_zero_order_total' ], 11 ); } @@ -104,33 +102,6 @@ public function enqueue_payment_scripts() { wp_enqueue_script( 'WCPAY_CHECKOUT' ); } - /** - * Registers all scripts, necessary for the gateway. - */ - public function register_scripts() { - // Register Stripe's JavaScript using the same ID as the Stripe Gateway plugin. This prevents this JS being - // loaded twice in the event a site has both plugins enabled. We still run the risk of different plugins - // loading different versions however. If Stripe release a v4 of their JavaScript, we could consider - // changing the ID to stripe_v4. This would allow older plugins to keep using v3 while we used any new - // feature in v4. Stripe have allowed loading of 2 different versions of stripe.js in the past ( - // https://stripe.com/docs/stripe-js/elements/migrating). - wp_register_script( - 'stripe', - 'https://js.stripe.com/v3/', - [], - '3.0', - true - ); - - $script_dependencies = [ 'stripe', 'wc-checkout' ]; - - if ( $this->gateway->supports( 'tokenization' ) ) { - $script_dependencies[] = 'woocommerce-tokenization-form'; - } - WC_Payments::register_script_with_dependencies( 'WCPAY_CHECKOUT', 'dist/checkout', $script_dependencies ); - wp_set_script_translations( 'WCPAY_CHECKOUT', 'woocommerce-payments' ); - } - /** * Registers scripts necessary for the gateway, even when cart order total is 0. * This is done so that if the cart is modified via AJAX on checkout, diff --git a/includes/class-wc-payments-upe-checkout.php b/includes/class-wc-payments-upe-checkout.php index 3ab6045a033..4096b0d068d 100644 --- a/includes/class-wc-payments-upe-checkout.php +++ b/includes/class-wc-payments-upe-checkout.php @@ -132,7 +132,7 @@ public function register_scripts() { $script_dependencies[] = 'woocommerce-tokenization-form'; } - $script = 'dist/upe_with_deferred_intent_creation_checkout'; + $script = 'dist/checkout'; WC_Payments::register_script_with_dependencies( 'wcpay-upe-checkout', $script, $script_dependencies ); } diff --git a/webpack/shared.js b/webpack/shared.js index f340f52766a..02bff20e196 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -12,17 +12,10 @@ module.exports = { index: './client/index.js', settings: './client/settings/index.js', 'blocks-checkout': './client/checkout/blocks/index.js', - 'upe-blocks-checkout': './client/checkout/blocks/upe.js', - 'upe-split-blocks-checkout': - './client/checkout/blocks/upe-split.js', woopay: './client/checkout/woopay/index.js', 'woopay-express-button': './client/checkout/woopay/express-button/index.js', - checkout: './client/checkout/classic/index.js', - upe_checkout: './client/checkout/classic/upe.js', - upe_split_checkout: './client/checkout/classic/upe-split.js', - upe_with_deferred_intent_creation_checkout: - './client/checkout/classic/upe-deferred-intent-creation/event-handlers.js', + checkout: './client/checkout/classic/event-handlers.js', 'payment-request': './client/payment-request/index.js', 'subscription-edit-page': './client/subscription-edit-page.js', tos: './client/tos/index.js', From 3fdc62bcb028b8f4dfce457d48a1a19a963ecc20 Mon Sep 17 00:00:00 2001 From: Dan Paun <82826872+dpaun1985@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:11:02 +0200 Subject: [PATCH 04/56] Fix missing order numbers from transaction report CSV (#7769) Co-authored-by: Dan Paun Co-authored-by: Cvetan Cvetanov --- .../fix-7592-update-transaction-order-id | 4 +++ .../class-wc-payments-invoice-service.php | 26 ++++++++++++++++++- ...c-payments-subscriptions-event-handler.php | 5 +++- .../class-wc-payments-api-client.php | 17 ++++++++++++ ...c-payments-subscriptions-event-handler.php | 5 ++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-7592-update-transaction-order-id diff --git a/changelog/fix-7592-update-transaction-order-id b/changelog/fix-7592-update-transaction-order-id new file mode 100644 index 00000000000..0ef8cffd17f --- /dev/null +++ b/changelog/fix-7592-update-transaction-order-id @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix missing order number in transaction reports CSV diff --git a/includes/subscriptions/class-wc-payments-invoice-service.php b/includes/subscriptions/class-wc-payments-invoice-service.php index 429f46dda0e..9e2ba40f997 100644 --- a/includes/subscriptions/class-wc-payments-invoice-service.php +++ b/includes/subscriptions/class-wc-payments-invoice-service.php @@ -314,9 +314,12 @@ public function get_and_attach_intent_info_to_order( $order, $intent_id ) { * Sends a request to server to record the store's context for an invoice payment. * * @param string $invoice_id The subscription invoice ID. + * + * @return array + * @throws API_Exception */ public function record_subscription_payment_context( string $invoice_id ) { - $this->payments_api_client->update_invoice( + return $this->payments_api_client->update_invoice( $invoice_id, [ 'subscription_context' => class_exists( 'WC_Subscriptions' ) && WC_Payments_Features::is_stripe_billing_enabled() ? 'stripe_billing' : 'legacy_wcpay_subscription', @@ -324,6 +327,27 @@ public function record_subscription_payment_context( string $invoice_id ) { ); } + /** + * Update a charge with the order id from invoice. + * + * @param array $invoice + * @param int $order_id + * + * @return void + * @throws API_Exception + */ + public function update_charge_details( array $invoice, int $order_id ) { + if ( ! isset( $invoice['charge'] ) ) { + return; + } + $this->payments_api_client->update_charge( + $invoice['charge'], + [ + 'metadata' => ['order_id' => $order_id ], + ] + ); + } + /** * Sets the subscription last invoice ID meta for WC subscription. * diff --git a/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php b/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php index d91fa30405e..e1046c7cf6b 100644 --- a/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php +++ b/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php @@ -190,7 +190,10 @@ public function handle_invoice_paid( array $body ) { $this->invoice_service->mark_pending_invoice_paid_for_subscription( $subscription ); // Record the store's Stripe Billing environment context on the payment intent. - $this->invoice_service->record_subscription_payment_context( $wcpay_invoice_id ); + $invoice = $this->invoice_service->record_subscription_payment_context( $wcpay_invoice_id ); + + // Update charge and transaction metadata - add order id for Stripe Billing + $this->invoice_service->update_charge_details( $invoice, $order->get_id() ); } /** 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 4416906767d..c6e7d3c9c84 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -1157,6 +1157,23 @@ public function update_invoice( string $invoice_id, array $data = [] ) { ); } + /** + * Updates a charge. + * + * @param string $charge_id ID of the charge to update. + * @param array $data arameters to send to the transaction endpoint. Optional. Default is an empty array. + * + * @return array + * @throws API_Exception + */ + public function update_charge( string $charge_id, array $data = [] ) { + return $this->request( + $data, + self::CHARGES_API . '/' . $charge_id, + self::POST + ); + } + /** * Fetch a WCPay subscription. * diff --git a/tests/unit/subscriptions/test-class-wc-payments-subscriptions-event-handler.php b/tests/unit/subscriptions/test-class-wc-payments-subscriptions-event-handler.php index 20daf144e88..a80ad37a69f 100644 --- a/tests/unit/subscriptions/test-class-wc-payments-subscriptions-event-handler.php +++ b/tests/unit/subscriptions/test-class-wc-payments-subscriptions-event-handler.php @@ -238,6 +238,11 @@ function ( $subscription ) use ( $mock_renewal_order ) { ->with( $mock_renewal_order, $wcpay_intent_id ) ->willReturn( null ); + $this->mock_invoice_service->expects( $this->once() ) + ->method( 'record_subscription_payment_context' ) + ->with( $wcpay_invoiceid ) + ->willReturn( [ 'charge' => 'ch_testxxx' ] ); + $this->subscriptions_event_handler->handle_invoice_paid( $test_body ); } From ab16d2f2e538ce127216ec2123db51a6a4391a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Costa?= <10233985+cesarcosta99@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:31:32 -0500 Subject: [PATCH 05/56] Add e2e tests for the currency switcher widget (#7822) --- changelog/e2e-7349-currency-switcher-widget | 4 + package-lock.json | 12 +- .../merchant-admin-multi-currency.spec.js | 41 +++- .../shopper-multi-currency-widget.spec.js | 180 ++++++++++++++++++ tests/e2e/utils/flows.js | 60 ++++++ 5 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 changelog/e2e-7349-currency-switcher-widget create mode 100644 tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js diff --git a/changelog/e2e-7349-currency-switcher-widget b/changelog/e2e-7349-currency-switcher-widget new file mode 100644 index 00000000000..1bf9af1d6c3 --- /dev/null +++ b/changelog/e2e-7349-currency-switcher-widget @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Add e2e tests for the currency switcher widget. diff --git a/package-lock.json b/package-lock.json index 10e611e1e2f..478b3900307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19308,9 +19308,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001488", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", - "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", "dev": true, "funding": [ { @@ -58890,9 +58890,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001488", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", - "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", "dev": true }, "capital-case": { diff --git a/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency.spec.js index a7878ee79b6..ddc5d62c01a 100644 --- a/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency.spec.js +++ b/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency.spec.js @@ -1,16 +1,26 @@ /** * External dependencies */ -const { merchant } = require( '@woocommerce/e2e-utils' ); +const { merchant, WP_ADMIN_DASHBOARD } = require( '@woocommerce/e2e-utils' ); /** * Internal dependencies */ -import { merchantWCP, takeScreenshot } from '../../../utils'; +import { merchantWCP, takeScreenshot, uiLoaded } from '../../../utils'; describe( 'Admin Multi-Currency', () => { + let wasMulticurrencyEnabled; + beforeAll( async () => { await merchant.login(); + wasMulticurrencyEnabled = await merchantWCP.activateMulticurrency(); + } ); + + afterAll( async () => { + if ( ! wasMulticurrencyEnabled ) { + await merchantWCP.deactivateMulticurrency(); + } + await merchant.logout(); } ); it( 'page should load without any errors', async () => { @@ -20,4 +30,31 @@ describe( 'Admin Multi-Currency', () => { } ); await takeScreenshot( 'merchant-admin-multi-currency' ); } ); + + it( 'should be possible to add the currency switcher to the sidebar', async () => { + await merchantWCP.addMulticurrencyWidget(); + } ); + + it( 'should be possible to add the currency switcher to a post/page', async () => { + await page.goto( `${ WP_ADMIN_DASHBOARD }post-new.php` ); + await uiLoaded(); + + const closeWelcomeModal = await page.$( 'button[aria-label="Close"]' ); + if ( closeWelcomeModal ) { + await closeWelcomeModal.click(); + } + + await page.click( 'button[aria-label="Add block"]' ); + + const searchInput = await page.waitForSelector( + 'input.components-search-control__input' + ); + searchInput.type( 'switcher', { delay: 20 } ); + + await page.waitForSelector( 'button[role="option"]' ); + await expect( page ).toMatchElement( 'button[role="option"]', { + text: 'Currency Switcher Block', + } ); + await page.waitFor( 1000 ); + } ); } ); diff --git a/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js new file mode 100644 index 00000000000..182802a62ba --- /dev/null +++ b/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js @@ -0,0 +1,180 @@ +/** + * External dependencies + */ +const { merchant, shopper } = require( '@woocommerce/e2e-utils' ); + +/** + * Internal dependencies + */ +import config from 'config'; +import { merchantWCP, shopperWCP } from '../../../utils'; +import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; + +const PAY_FOR_ORDER_LINK_SELECTOR = '.wc-order-status a'; +const pagesTable = [ + [ + 'home page', + async () => { + await shopper.goToShop(); + }, + 'shop', + ], + [ + 'product page', + async () => { + await page.click( 'li.product > a > img[src*="beanie"' ); + }, + 'product/beanie', + ], + [ + 'cart', + async () => { + await shopperWCP.addToCartBySlug( 'beanie' ); + await shopper.goToCart(); + }, + 'cart', + ], + [ + 'checkout', + async () => { + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + }, + 'checkout', + ], +]; + +describe( 'Shopper Multi-Currency widget', () => { + let wasMulticurrencyEnabled; + + beforeAll( async () => { + await merchant.login(); + wasMulticurrencyEnabled = await merchantWCP.activateMulticurrency(); + await merchantWCP.addCurrency( 'EUR' ); + } ); + + afterAll( async () => { + if ( ! wasMulticurrencyEnabled ) { + await merchant.login(); + await merchantWCP.deactivateMulticurrency(); + await merchant.logout(); + } + } ); + + it( 'should display currency switcher widget if multi-currency is enabled', async () => { + await merchantWCP.addMulticurrencyWidget(); + await shopper.goToShop(); + await page.waitForSelector( '.widget select[name=currency]', { + visible: true, + timeout: 5000, + } ); + } ); + + it( 'should not display currency switcher widget if multi-currency is disabled', async () => { + await merchantWCP.openWCPSettings(); + await merchantWCP.deactivateMulticurrency(); + await shopper.goToShop(); + + const currencySwitcher = await page.$( + '.widget select[name=currency]' + ); + expect( currencySwitcher ).toBeNull(); + + // Activate it again for the other tests. + await merchantWCP.activateMulticurrency(); + await merchant.logout(); + } ); + + describe.each( pagesTable )( + 'Should allow shopper to switch currency', + ( pageName, setupTest, url ) => { + it( `at the ${ pageName }`, async () => { + await setupTest(); + await page.waitForSelector( '.widget select[name=currency]', { + visible: true, + } ); + await page.select( '.widget select[name=currency]', 'EUR' ); + await expect( page.url() ).toContain( + `${ url }/?currency=EUR` + ); + await page.waitForSelector( + '.widget select[name=currency] option[value=EUR][selected]' + ); + // Change it back to USD for the other tests. + await page.select( '.widget select[name=currency]', 'USD' ); + } ); + } + ); + + it( 'should not affect prices when currency switching on My account > Orders', async () => { + await shopper.login(); + await page.select( '.widget select[name=currency]', 'USD' ); + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + await fillCardDetails( page, config.get( 'cards.basic' ) ); + await shopper.placeOrder(); + await expect( page ).toMatch( 'Order received' ); + + const orderId = await page.evaluate( + () => document.querySelector( 'li.order strong' ).innerText + ); + const orderTotal = Number( + await page.evaluate( () => + document + .querySelector( 'li.total strong' ) + .innerText.replace( /[^\d.]/g, '' ) + ) + ); + + await shopperWCP.goToOrders(); + await page.select( '.widget select[name=currency]', 'EUR' ); + await page.waitForSelector( + '.widget select[name=currency] option[value=EUR][selected]' + ); + await expect( page ).toMatch( `#${ orderId }` ); + await expect( page ).toMatch( `${ orderTotal.toFixed( 2 ) } USD` ); + } ); + + it( 'should not affect prices when currency switching at the order received page', async () => { + await page.select( '.widget select[name=currency]', 'USD' ); + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + await fillCardDetails( page, config.get( 'cards.basic' ) ); + await shopper.placeOrder(); + await expect( page ).toMatch( 'Order received' ); + + const orderId = await page.evaluate( + () => document.querySelector( 'li.order strong' ).innerText + ); + const orderTotal = Number( + await page.evaluate( () => + document + .querySelector( 'li.total strong' ) + .innerText.replace( /[^\d.]/g, '' ) + ) + ); + + await page.select( '.widget select[name=currency]', 'EUR' ); + await page.waitForSelector( + '.widget select[name=currency] option[value=EUR][selected]' + ); + await expect( page ).toMatch( `${ orderId }` ); + await expect( page ).toMatch( `${ orderTotal.toFixed( 2 ) } USD` ); + await page.select( '.widget select[name=currency]', 'USD' ); + await shopper.logout(); + } ); + + it( 'should not display currency switcher on pay for order page', async () => { + await merchant.login(); + await merchantWCP.createPayForOrder(); + await page.click( PAY_FOR_ORDER_LINK_SELECTOR ); + + const currencySwitcher = await page.$( + '.widget select[name=currency]' + ); + expect( currencySwitcher ).toBeNull(); + } ); +} ); diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js index 1f2863e181f..50a0b294a75 100644 --- a/tests/e2e/utils/flows.js +++ b/tests/e2e/utils/flows.js @@ -13,6 +13,7 @@ const { clearAndFillInput, setCheckbox, SHOP_PAGE, + WP_ADMIN_DASHBOARD, } = require( '@woocommerce/e2e-utils' ); const { fillCardDetails, @@ -832,4 +833,63 @@ export const merchantWCP = { } return wasInitiallyEnabled; }, + + addMulticurrencyWidget: async () => { + await page.goto( `${ WP_ADMIN_DASHBOARD }widgets.php`, { + waitUntil: 'networkidle0', + } ); + await uiLoaded(); + + const closeWelcomeModal = await page.$( 'button[aria-label="Close"]' ); + if ( closeWelcomeModal ) { + await closeWelcomeModal.click(); + } + + const isWidgetAdded = await page.$( + '.wp-block iframe[srcdoc*=\'name="currency"\']' + ); + if ( ! isWidgetAdded ) { + await page.click( 'button[aria-label="Add block"]' ); + + const searchInput = await page.waitForSelector( + 'input.components-search-control__input' + ); + searchInput.type( 'switcher', { delay: 20 } ); + + await page.waitForSelector( + 'button.components-button[role="option"]' + ); + await page.click( 'button.components-button[role="option"]' ); + await page.waitFor( 2000 ); + await page.waitForSelector( + '.edit-widgets-header .edit-widgets-header__actions button.is-primary' + ); + await page.click( + '.edit-widgets-header .edit-widgets-header__actions button.is-primary' + ); + await expect( page ).toMatchElement( '.components-snackbar', { + text: 'Widgets saved.', + timeout: 15000, + } ); + } + }, + createPayForOrder: async () => { + await merchant.openNewOrder(); + await page.click( 'button.add-line-item' ); + await page.click( 'button.add-order-item' ); + await page.click( 'select.wc-product-search' ); + await page.type( + '.select2-search--dropdown > input', + config.get( 'products.simple.name' ), + { + delay: 20, + } + ); + await page.waitFor( 2000 ); + await page.click( '.select2-results .select2-results__option' ); + await page.click( '#btn-ok' ); + await page.waitFor( 2000 ); + await page.click( 'button.save_order' ); + await page.waitForNavigation( { waitUntil: 'networkidle0' } ); + }, }; From eb9e2b101583b7d5194eea44ae56dce7df5b7f26 Mon Sep 17 00:00:00 2001 From: Allie Mims <60988591+allie500@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:15:35 -0500 Subject: [PATCH 06/56] Update Fraud & Risk docs link text in settings (#7826) --- changelog/update-6325-fraud-risk-link-text-in-settings | 4 ++++ client/settings/settings-manager/index.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/update-6325-fraud-risk-link-text-in-settings diff --git a/changelog/update-6325-fraud-risk-link-text-in-settings b/changelog/update-6325-fraud-risk-link-text-in-settings new file mode 100644 index 00000000000..3f37d6371ca --- /dev/null +++ b/changelog/update-6325-fraud-risk-link-text-in-settings @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Updates the anchor text for the fraud and risk tools documentation link on the Payments Settings page. diff --git a/client/settings/settings-manager/index.js b/client/settings/settings-manager/index.js index 6fe528c443b..ebd1c73085f 100644 --- a/client/settings/settings-manager/index.js +++ b/client/settings/settings-manager/index.js @@ -126,7 +126,7 @@ const FraudProtectionDescription = () => {

{ __( - 'Learn more about risk filtering', + 'Learn more about fraud protection', 'woocommerce-payments' ) } From cb2f67d604dd0abfe2ddbe4f25cfe6d4285d48bc Mon Sep 17 00:00:00 2001 From: Dan Paun <82826872+dpaun1985@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:53:27 +0200 Subject: [PATCH 07/56] Fix missing customer data from transactions report (#7790) Co-authored-by: Dan Paun Co-authored-by: Cvetan Cvetanov --- changelog/fix-7595-reports-customers | 4 +++ .../class-wc-payments-invoice-service.php | 31 +++++++++++++++++ ...c-payments-subscriptions-event-handler.php | 3 ++ .../class-wc-payments-api-client.php | 33 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 changelog/fix-7595-reports-customers diff --git a/changelog/fix-7595-reports-customers b/changelog/fix-7595-reports-customers new file mode 100644 index 00000000000..e4ab131cb21 --- /dev/null +++ b/changelog/fix-7595-reports-customers @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix missing customer data from transactions report diff --git a/includes/subscriptions/class-wc-payments-invoice-service.php b/includes/subscriptions/class-wc-payments-invoice-service.php index 9e2ba40f997..c4d0cd38fb5 100644 --- a/includes/subscriptions/class-wc-payments-invoice-service.php +++ b/includes/subscriptions/class-wc-payments-invoice-service.php @@ -327,6 +327,37 @@ public function record_subscription_payment_context( string $invoice_id ) { ); } + /** + * Sends a request to server to update transaction details. + * + * @param array $invoice Invoice details. + * @param WC_Order $order Order details. + * + * @return void + * @throws API_Exception + */ + public function update_transaction_details( array $invoice, WC_Order $order ) { + if ( ! isset( $invoice['charge'] ) ) { + return; + } + + $charge = $this->payments_api_client->get_charge( $invoice['charge'] ); + if ( !isset( $charge['balance_transaction'] ) || !isset( $charge['balance_transaction']['id'] ) ) { + return; + } + + $this->payments_api_client->update_transaction( + $charge['balance_transaction']['id'], + [ + 'customer_first_name' => $order->get_billing_first_name(), + 'customer_last_name' => $order->get_billing_last_name(), + 'customer_email' => $order->get_billing_email(), + 'customer_country' => $order->get_billing_country(), + ] + ); + + } + /** * Update a charge with the order id from invoice. * diff --git a/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php b/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php index e1046c7cf6b..a3b1ddbf76e 100644 --- a/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php +++ b/includes/subscriptions/class-wc-payments-subscriptions-event-handler.php @@ -194,6 +194,9 @@ public function handle_invoice_paid( array $body ) { // Update charge and transaction metadata - add order id for Stripe Billing $this->invoice_service->update_charge_details( $invoice, $order->get_id() ); + + // Update transaction customer details for Stripe Billing + $this->invoice_service->update_transaction_details( $invoice, $order ); } /** 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 c6e7d3c9c84..f6cd60bfb87 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -1174,6 +1174,39 @@ public function update_charge( string $charge_id, array $data = [] ) { ); } + /** + * Fetch a charge by id. + * + * @param string $charge_id Charge id. + * + * @return array + * @throws API_Exception + */ + public function get_charge( string $charge_id ) { + return $this->request( + [], + self::CHARGES_API . '/' . $charge_id, + self::GET + ); + } + + /** + * Updates a transaction. + * + * @param string $transaction_id Transaction id. + * @param array $data Data to be updated. + * + * @return array + * @throws API_Exception + */ + public function update_transaction( string $transaction_id, array $data = [] ) { + return $this->request( + $data, + self::TRANSACTIONS_API . '/' . $transaction_id, + self::POST + ); + } + /** * Fetch a WCPay subscription. * From 4a667b85caa3f97572d055ca07107a51afd8201c Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 7 Dec 2023 14:17:58 +0100 Subject: [PATCH 08/56] Remove unused factor flag for deferred UPE (#7835) Co-authored-by: Timur Karimov Co-authored-by: Brett Shumaker --- changelog/cleanup-devtools-flags | 4 ++++ includes/class-wc-payment-gateway-wcpay.php | 4 ---- src/Internal/Payment/Factor.php | 8 -------- tests/unit/src/Internal/Payment/FactorTest.php | 1 - 4 files changed, 4 insertions(+), 13 deletions(-) create mode 100644 changelog/cleanup-devtools-flags diff --git a/changelog/cleanup-devtools-flags b/changelog/cleanup-devtools-flags new file mode 100644 index 00000000000..adbf7f71958 --- /dev/null +++ b/changelog/cleanup-devtools-flags @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +remove unused factor flag for deferred UPE diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index df4116e1cec..4053c9d5454 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -811,10 +811,6 @@ function_exists( 'wcs_order_contains_subscription' ) $factors[] = Factor::WCPAY_SUBSCRIPTION_SIGNUP(); } - if ( $this instanceof UPE_Split_Payment_Gateway ) { - $factors[] = Factor::DEFERRED_INTENT_SPLIT_UPE(); - } - if ( defined( 'WCPAY_PAYMENT_REQUEST_CHECKOUT' ) && WCPAY_PAYMENT_REQUEST_CHECKOUT ) { $factors[] = Factor::PAYMENT_REQUEST(); } diff --git a/src/Internal/Payment/Factor.php b/src/Internal/Payment/Factor.php index 594683f67a4..3a63e895738 100644 --- a/src/Internal/Payment/Factor.php +++ b/src/Internal/Payment/Factor.php @@ -94,13 +94,6 @@ class Factor extends Base_Constant { */ const STRIPE_LINK = 'STRIPE_LINK'; - /** - * Deferred UPE requires very little extra code (for both card and LPMs), but thorough testing. - * Will become a condition, once there is the one true gateway. - * Type: Entry point - */ - const DEFERRED_INTENT_SPLIT_UPE = 'DEFERRED_INTENT_SPLIT_UPE'; - /** * Payment request buttons (Google Pay and Apple Pay) * Type: Entry point @@ -127,7 +120,6 @@ public static function get_all_factors() { static::WCPAY_SUBSCRIPTION_SIGNUP(), static::IPP_CAPTURE(), static::STRIPE_LINK(), - static::DEFERRED_INTENT_SPLIT_UPE(), static::PAYMENT_REQUEST(), ]; } diff --git a/tests/unit/src/Internal/Payment/FactorTest.php b/tests/unit/src/Internal/Payment/FactorTest.php index 768718d860b..e1e1e14ba71 100644 --- a/tests/unit/src/Internal/Payment/FactorTest.php +++ b/tests/unit/src/Internal/Payment/FactorTest.php @@ -35,7 +35,6 @@ public function test_get_all_factors() { 'WCPAY_SUBSCRIPTION_SIGNUP', 'IPP_CAPTURE', 'STRIPE_LINK', - 'DEFERRED_INTENT_SPLIT_UPE', 'PAYMENT_REQUEST', ]; From 2338bb0d74bef780e9420fcc30bca481079decf0 Mon Sep 17 00:00:00 2001 From: Allie Mims <60988591+allie500@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:26:20 -0500 Subject: [PATCH 09/56] Update international IP address rule card behavior when the rule is being affected by another configuration (#7827) --- ...-card-behavior-if-affected-by-other-config | 4 + .../allow-countries-notice.tsx | 10 - .../cards/international-ip-address.tsx | 34 +- .../international-ip-address.test.tsx.snap | 407 +++++++++++++++--- .../test/international-ip-address.test.tsx | 55 ++- .../allow-countries-notice.test.tsx.snap | 174 -------- .../test/__snapshots__/index.test.tsx.snap | 198 +++++---- .../test/allow-countries-notice.test.tsx | 11 - .../advanced-settings/test/index.test.tsx | 11 +- 9 files changed, 550 insertions(+), 354 deletions(-) create mode 100644 changelog/update-6320-rule-card-behavior-if-affected-by-other-config diff --git a/changelog/update-6320-rule-card-behavior-if-affected-by-other-config b/changelog/update-6320-rule-card-behavior-if-affected-by-other-config new file mode 100644 index 00000000000..3755be13f4f --- /dev/null +++ b/changelog/update-6320-rule-card-behavior-if-affected-by-other-config @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Updates the behavior and display of the international IP address rule card if the rule is being affected by the WooCommerce core selling locations general option. diff --git a/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx b/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx index ebbca1e2686..3f9664de54c 100644 --- a/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx +++ b/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx @@ -57,16 +57,6 @@ const AllowedCountriesNotice: React.FC< AllowedCountriesNoticeProps > = ( { const supportedCountriesType = getSupportedCountriesType(); const settingCountries = getSettingCountries(); - if ( 'all' === supportedCountriesType ) { - return ( - - { __( - 'Enabling this filter will not have any effect because you are selling to all countries.', - 'woocommerce-payments' - ) } - - ); - } return ( { getNoticeText( supportedCountriesType, isBlocking ) } diff --git a/client/settings/fraud-protection/advanced-settings/cards/international-ip-address.tsx b/client/settings/fraud-protection/advanced-settings/cards/international-ip-address.tsx index af6763a1933..34b6e342994 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/international-ip-address.tsx +++ b/client/settings/fraud-protection/advanced-settings/cards/international-ip-address.tsx @@ -11,11 +11,15 @@ import interpolateComponents from '@automattic/interpolate-components'; */ import FraudProtectionRuleCard from '../rule-card'; import FraudProtectionRuleDescription from '../rule-description'; +import FraudProtectionRuleCardNotice from '../rule-card-notice'; import FraudProtectionRuleToggle from '../rule-toggle'; import AllowedCountriesNotice from '../allow-countries-notice'; import { getAdminUrl } from 'wcpay/utils'; +import { getSupportedCountriesType } from '../utils'; const InternationalIPAddressRuleCard: React.FC = () => { + const supportsAllCountries = 'all' === getSupportedCountriesType(); + return ( { } ) } id="international-ip-address-card" > - + { supportsAllCountries && ( + + { __( + "This filter is disabled because you're currently selling to all countries.", + 'woocommerce-payments' + ) } + + ) } + { ! supportsAllCountries && ( + + ) } { __( 'You should be especially wary when a customer has an international IP address but uses domestic billing and ' + @@ -60,7 +74,11 @@ const InternationalIPAddressRuleCard: React.FC = () => { 'woocommerce-payments' ) } - + { ! supportsAllCountries && ( + + ) } ); }; diff --git a/client/settings/fraud-protection/advanced-settings/cards/test/__snapshots__/international-ip-address.test.tsx.snap b/client/settings/fraud-protection/advanced-settings/cards/test/__snapshots__/international-ip-address.test.tsx.snap index 55d7b04c106..2084e77f831 100644 --- a/client/settings/fraud-protection/advanced-settings/cards/test/__snapshots__/international-ip-address.test.tsx.snap +++ b/client/settings/fraud-protection/advanced-settings/cards/test/__snapshots__/international-ip-address.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`International IP address card renders correctly 1`] = ` +exports[`International IP address card renders correctly when enabled 1`] = `

- When enabled, the payment will be blocked. + The payment will be blocked.

+
+
+
+
+
+ +
+
+ Orders from outside of the following countries will be blocked by the filter: + + Canada, United States + +
+
+
+
+
+
+ + +`; + +exports[`International IP address card renders correctly when enabled and checked 1`] = ` +
+
+
+
+
+

+ International IP Address +

+

+ This filter screens for + + IP addresses + + outside of your + + supported countries + + . +

+
+
+
+
+
+ + Enable filtering + +
+
+ + + + + + +
+

+ The payment will be blocked. +

+
+
+
+
+ + How does this filter protect me? + +

+ You should be especially wary when a customer has an international IP address but uses domestic billing and shipping information. Fraudsters often pretend to live in one location, but live and shop from another. +

+
+
+
+
+
+ +
+
+ Orders from outside of the following countries will be blocked by the filter: + + Canada, United States + +
+
+
+
+
+
+
+ +`; + +exports[`International IP address card renders correctly when woocommerce_allowed_countries is all 1`] = ` +
+
+
+
+
+

+ International IP Address +

+

+ This filter screens for + + IP addresses + + outside of your + + supported countries + + . +

+
+
+
+
@@ -137,7 +416,7 @@ exports[`International IP address card renders correctly 1`] = ` data-wp-c16t="true" data-wp-component="FlexItem" > - Enabling this filter will not have any effect because you are selling to all countries. + This filter is disabled because you're currently selling to all countries.
+
+ + How does this filter protect me? + +

+ You should be especially wary when a customer has an international IP address but uses domestic billing and shipping information. Fraudsters often pretend to live in one location, but live and shop from another. +

+
`; -exports[`International IP address card renders correctly when enabled 1`] = ` +exports[`International IP address card renders correctly when woocommerce_allowed_countries is all_except 1`] = `
- The payment will be blocked. + When enabled, the payment will be blocked.

-
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from the following countries will be blocked by the filter: + + Canada, United States +
`; -exports[`International IP address card renders correctly when enabled and checked 1`] = ` +exports[`International IP address card renders correctly when woocommerce_allowed_countries is specific 1`] = `

- The payment will be blocked. + When enabled, the payment will be blocked.

-
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +

When enabled, the payment will be blocked.

@@ -594,7 +885,7 @@ exports[`International IP address card renders like disabled when checked, but n

- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
{ }, }, }, + countries: { + CA: 'Canada', + US: 'United States', + }, }; global.wcpaySettings = { isFRTReviewFeatureActive: false, }; - test( 'renders correctly', () => { + test( 'renders correctly when woocommerce_allowed_countries is all', () => { + const { container } = render( + + + + ); + expect( container ).toMatchSnapshot(); + } ); + test( 'renders correctly when woocommerce_allowed_countries is specific', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'specific'; + global.wcSettings.admin.preloadSettings.general.woocommerce_specific_allowed_countries = [ + 'CA', + 'US', + ]; + const { container } = render( + + + + ); + expect( container ).toMatchSnapshot(); + } ); + test( 'renders correctly when woocommerce_allowed_countries is all_except', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'all_except'; + global.wcSettings.admin.preloadSettings.general.woocommerce_all_except_countries = [ + 'CA', + 'US', + ]; const { container } = render( @@ -64,6 +99,12 @@ describe( 'International IP address card', () => { expect( container ).toMatchSnapshot(); } ); test( 'renders correctly when enabled', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'specific'; + global.wcSettings.admin.preloadSettings.general.woocommerce_specific_allowed_countries = [ + 'CA', + 'US', + ]; settings.international_ip_address.enabled = true; const { container } = render( @@ -73,6 +114,12 @@ describe( 'International IP address card', () => { expect( container ).toMatchSnapshot(); } ); test( 'renders correctly when enabled and checked', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'specific'; + global.wcSettings.admin.preloadSettings.general.woocommerce_specific_allowed_countries = [ + 'CA', + 'US', + ]; settings.international_ip_address.enabled = true; settings.international_ip_address.block = true; const { container } = render( @@ -83,6 +130,12 @@ describe( 'International IP address card', () => { expect( container ).toMatchSnapshot(); } ); test( 'renders like disabled when checked, but not enabled', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'specific'; + global.wcSettings.admin.preloadSettings.general.woocommerce_specific_allowed_countries = [ + 'CA', + 'US', + ]; settings.international_ip_address.enabled = false; settings.international_ip_address.block = true; const { container } = render( diff --git a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap index af5c6da749b..3a725cde716 100644 --- a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap +++ b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap @@ -1,179 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Allowed countries rule card notice tests renders correctly when all countries are allowed 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -

- Notifications -

-
-
- Enabling this filter will not have any effect because you are selling to all countries. -
-
-
-
-
-
- - - - - -
-
- Enabling this filter will not have any effect because you are selling to all countries. -
-
-
-
-
-
- , - "container":
-
-
-
-
- - - - - -
-
- Enabling this filter will not have any effect because you are selling to all countries. -
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - exports[`Allowed countries rule card notice tests renders correctly when countries except some are allowed, others will be blocked 1`] = ` Object { "asFragment": [Function], 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 c9fced5b9dd..931fa17bd69 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 @@ -302,7 +302,7 @@ Object {

- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- For security, this filter is enabled and cannot be modified. Payments failing CVC verification will be blocked. Learn more + Orders from outside of the following countries will be blocked by the filter: Canada, United States
@@ -2363,7 +2367,7 @@ Object {

- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- For security, this filter is enabled and cannot be modified. Payments failing CVC verification will be blocked. Learn more + Orders from outside of the following countries will be blocked by the filter: Canada, United States
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- For security, this filter is enabled and cannot be modified. Payments failing CVC verification will be blocked. Learn more + Orders from outside of the following countries will be blocked by the filter: Canada, United States
@@ -5856,7 +5868,7 @@ Object {

- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
- Enabling this filter will not have any effect because you are selling to all countries. + Orders from outside of the following countries will be blocked by the filter: + + Canada, United States +
{ }, }; } ); - test( 'renders correctly when all countries are allowed', () => { - const container = render( - - - - ); - expect( container ).toMatchSnapshot(); - expect( container.baseElement ).toHaveTextContent( - /Enabling this filter will not have any effect because you are selling to all countries\./i - ); - } ); test( 'renders correctly when specific countries are allowed, others will be hold', () => { global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = 'specific'; 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 070526b32bb..60fe89c8d07 100644 --- a/client/settings/fraud-protection/advanced-settings/test/index.test.tsx +++ b/client/settings/fraud-protection/advanced-settings/test/index.test.tsx @@ -51,6 +51,9 @@ declare const global: { }; }; }; + countries: { + [ key: string ]: string; + }; }; wcpaySettings: { storeCurrency: string; @@ -110,12 +113,16 @@ describe( 'Advanced fraud protection settings', () => { admin: { preloadSettings: { general: { - woocommerce_allowed_countries: 'all', + woocommerce_allowed_countries: 'specific', woocommerce_all_except_countries: [], - woocommerce_specific_allowed_countries: [], + woocommerce_specific_allowed_countries: [ 'CA', 'US' ], }, }, }, + countries: { + CA: 'Canada', + US: 'United States', + }, }; global.wcpaySettings = { From 377429fc43cbd5f8ba0b5ebed76760f69c19b8dc Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:07:49 +1000 Subject: [PATCH 10/56] Add Deposits API documentation (#7840) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- .gitignore | 3 + changelog/fix-7839-deposits-rest-api-docs | 4 + docs/rest-api/README.md | 12 + .../source/includes/wp-api-v3/deposits.md | 584 ++++++++++++++++++ docs/rest-api/source/index.html.md | 1 + 5 files changed, 604 insertions(+) create mode 100644 changelog/fix-7839-deposits-rest-api-docs create mode 100644 docs/rest-api/README.md create mode 100644 docs/rest-api/source/includes/wp-api-v3/deposits.md diff --git a/.gitignore b/.gitignore index 9749d5a27ba..4aabb410301 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,6 @@ tests/e2e/screenshots # E2E Performance test results tests/e2e/reports + +# Slate docs +docs/rest-api/build/* diff --git a/changelog/fix-7839-deposits-rest-api-docs b/changelog/fix-7839-deposits-rest-api-docs new file mode 100644 index 00000000000..b50f1644a81 --- /dev/null +++ b/changelog/fix-7839-deposits-rest-api-docs @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Added documentation for deposits REST API endpoints. diff --git a/docs/rest-api/README.md b/docs/rest-api/README.md new file mode 100644 index 00000000000..1296a86b062 --- /dev/null +++ b/docs/rest-api/README.md @@ -0,0 +1,12 @@ +# REST API Docs + +- Generated by [Slate](https://github.com/slatedocs/slate). +- Published via the [`deploy-api-docs.yml` GH action](https://github.com/Automattic/woocommerce-payments/actions/workflows/deploy-api-docs.yml) to https://automattic.github.io/woocommerce-payments/ once merged to `develop`. + +### Local development + +- Requires Docker and Node.js. +- `cd docs/rest-api` to change to this directory. +- `./build.sh` to build the docs via a Docker container. +- `npx serve build` to serve the built HTML at `localhost:3000`. +- `npx nodemon -w ./source -e "md" --exec ./build.sh` to watch and rebuild the docs on change. diff --git a/docs/rest-api/source/includes/wp-api-v3/deposits.md b/docs/rest-api/source/includes/wp-api-v3/deposits.md new file mode 100644 index 00000000000..f075242ea5f --- /dev/null +++ b/docs/rest-api/source/includes/wp-api-v3/deposits.md @@ -0,0 +1,584 @@ +# Deposits + +The Deposits API endpoints provide access to an account's deposits data, including an overview of account balances, deposit schedule and deposit history. + +## Deposit object + +```json +{ + "id": "po_1OJ466CBu6Jj8nBr38JRxdNE", + "date": 1701648000000, + "type": "deposit", + "amount": 802872, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701648000 +} +``` + +### Properties + +- `id` _string_ - The deposit ID. +- `date` _int_ - The arrival date of the deposit in unix timestamp milliseconds. +- `type` _string_ - The type of deposit. `deposit` `withdrawal` +- `amount` _int_ - The amount of the deposit. +- `status` _string_ - The status of the deposit. `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `bankAccount` _string_ - The bank account the deposit was/will be paid to. +- `currency` _string_ - The currency of the deposit. E.g. `eur` +- `automatic` _bool_ - Returns `true` if the payout is created by an automated schedule and `false` if it’s requested manually. +- `fee` _int_ - The fee amount of the deposit. +- `fee_percentage` _int_ - The fee percentage of the deposit. +- `created` _int_ - The arrival date of the deposit in unix timestamp seconds. + +## Get deposits overview for all account deposit currencies + +Fetch an overview of account deposits for all deposit currencies. This includes details for the last paid deposit, next scheduled deposit, and last manual deposits. + +### HTTP request + +
+
+ GET +
/wp-json/wc/v3/payments/deposits/overview-all
+
+
+ +### Returns + +- `deposit` _object_ + - `last_paid` _array_ of [**Deposit**](#deposit-object) - The last deposit that has been paid for each deposit currency. + - `next_scheduled` _array_ of [**Deposit**](#deposit-object) - The next scheduled deposit for each deposit currency. + - `last_manual_deposits` _array_ of [**Deposit**](#deposit-object) - Manual deposits that have been paid in the last 24 hours. +- `balance` _object_ + - `pending` _array_ - The pending balance for each deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` + - `deposits_count` _int_ - The number of deposits that make up the balance. + - `available` _array_ - The available balance for each deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` + - `instant` _array_ - The instant balance for each deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `fee` _int_ - The fee amount of the balance. + - `fee_percentage` _int_ - The fee percentage of the balance. + - `net` _int_ - The net amount of the balance. + - `transaction_ids` _array_ - The list of transaction IDs that make up the balance. +- `account` _object_ + - `deposits_enabled` _bool_ - Whether deposits are enabled for the account. + - `deposits_blocked` _bool_ - Whether deposits are blocked for the account. + - `deposits_schedule` _object_ + - `delay_days` _int_ - The number of days after a charge is created that the payment is paid out. + - `interval` _string_ - The interval at which payments are paid out. `manual` `daily` `weekly` `monthly` + - `weekly_anchor` _string_ | _undefined_ - The day of the week that payments are paid out, e.g. `monday`. + - `monthly_anchor` _int_ | _undefined_ - The day of the month that payments are paid out. Specified as a number between 1–31. 29-31 will instead use the last day of a shorter month. + - `default_currency` _string_ - The default currency for the account. + +```shell +curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview-all \ + -u consumer_key:consumer_secret +``` + +> JSON response example: + +```json +{ + "deposit": { + "last_paid": [ + { + "id": "po_1OJ466CBu6Jj8nBr38JRxdNE", + "date": 1701648000000, + "type": "deposit", + "amount": 802872, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701648000 + }, + { + "id": "po_1OHylNCBu6Jj8nBr95tE8scS", + "date": 1701302400000, + "type": "deposit", + "amount": 471784, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 6789 (USD)", + "currency": "usd", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701302400 + } + ], + "next_scheduled": [ + { + "id": "wcpay_estimated_weekly_eur_1702598400", + "date": 1702598400000, + "type": "deposit", + "amount": 458784, + "status": "estimated", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1702598400 + }, + { + "id": "wcpay_estimated_weekly_usd_1701993600", + "date": 1701993600000, + "type": "deposit", + "amount": 823789, + "status": "estimated", + "bankAccount": "STRIPE TEST BANK •••• 6789 (USD)", + "currency": "usd", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701993600 + } + ], + "last_manual_deposits": [] + }, + "balance": { + "pending": [ + { + "amount": -114696, + "currency": "eur", + "source_types": { + "card": -114696 + }, + "deposits_count": 1 + }, + { + "amount": 707676, + "currency": "usd", + "source_types": { + "card": 707676 + }, + "deposits_count": 2 + } + ], + "available": [ + { + "amount": 573480, + "currency": "eur", + "source_types": { + "card": 573480 + } + }, + { + "amount": 587897, + "currency": "usd", + "source_types": { + "card": 587897 + } + } + ], + "instant": [ + { + "amount": 12345, + "currency": "usd", + "fee": 185, + "fee_percentage": 1.5, + "net": 0, + "transaction_ids": [ + "txn_3OHyIxCIHGKp1UAi0aVyDQ5D", + "txn_3OJSuOCIHGKp1UAi1mRA2lL5" + ] + } + ] + }, + "account": { + "deposits_enabled": true, + "deposits_blocked": false, + "deposits_schedule": { + "delay_days": 7, + "interval": "weekly", + "weekly_anchor": "friday" + }, + "default_currency": "eur" + } +} +``` + +## Get deposits overview for single account deposit currency + +Fetch an overview of account deposits for a single deposit currency. This includes details for the last paid deposit, next scheduled deposit, and last manual deposits. + +### HTTP request + +
+
+ GET +
/wp-json/wc/v3/payments/deposits/overview
+
+
+ +### Returns + +- `last_deposit` _object_ [**Deposit**](#deposit-object) | _null_- The last deposit that has been paid for the deposit currency. +- `next_deposit` _object_ [**Deposit**](#deposit-object) | _null_ - The next scheduled deposit for the deposit currency. +- `balance` _object_ + - `pending` _object_ - The pending balance for the deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` + - `deposits_count` _int_ - The number of deposits that make up the balance. + - `available` _object_ - The available balance for the deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` +- `instant_balance` _object_ | _null_ - The instant balance for the deposit currency. + - `amount` _int_ - The amount of the balance. + - `currency` _string_ - The currency of the balance. E.g. `usd`. + - `fee` _int_ - The fee amount of the balance. + - `fee_percentage` _int_ - The fee percentage of the balance. + - `net` _int_ - The net amount of the balance. + - `transaction_ids` _array_ - The list of transaction IDs that make up the balance. +- `account` _object_ + - `deposits_disabled` _bool_ - Whether deposits are enabled for the account. + - `deposits_blocked` _bool_ - Whether deposits are blocked for the account. + - `deposits_schedule` _object_ + - `delay_days` _int_ - The number of days after a charge is created that the payment is paid out. + - `interval` _string_ - The interval at which payments are paid out. `manual` `daily` `weekly` `monthly` + - `weekly_anchor` _string_ | _undefined_ - The day of the week that payments are paid out, e.g. `monday`. + - `monthly_anchor` _int_ | _undefined_ - The day of the month that payments are paid out. Specified as a number between 1–31. 29-31 will instead use the last day of a shorter month. + - `default_currency` _string_ - The default currency for the account. + +```shell +curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview \ + -u consumer_key:consumer_secret +``` + +> JSON response example: + +```json +{ + "last_deposit": { + "id": "po_1OJ466CBu6Jj8nBr38JRxdNE", + "date": 1701648000000, + "type": "deposit", + "amount": 802872, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701648000 + }, + "next_deposit": { + "id": "wcpay_estimated_weekly_eur_1702598400", + "date": 1702598400000, + "type": "deposit", + "amount": 458784, + "status": "estimated", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1702598400 + }, + "balance": { + "available": { + "amount": 573480, + "currency": "eur", + "source_types": { + "card": 573480 + } + }, + "pending": { + "amount": -114696, + "currency": "eur", + "source_types": { + "card": -114696 + }, + "deposits_count": 1 + } + }, + "instant_balance": { + "amount": 0, + "currency": "usd", + "fee": 0, + "fee_percentage": 1.5, + "net": 0, + "transaction_ids": [] + }, + "account": { + "deposits_disabled": false, + "deposits_blocked": false, + "deposits_schedule": { + "delay_days": 7, + "interval": "weekly", + "weekly_anchor": "friday" + }, + "default_currency": "eur" + } +} +``` + +## List deposits + +Fetch a list of deposits. + +### HTTP request + +
+
+ GET +
/wp-json/wc/v3/payments/deposits
+
+
+ +### Required parameters + +- `sort` _string_ - Field on which to sort, e.g. `date` + +### Optional parameters + +- `match` _string_ +- `store_currency_is` _string_ +- `date_before` _string_ +- `date_after` _string_ +- `date_between` _array_ +- `status_is` _string_ `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is_not` _string_ `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `direction` _string_ +- `page` _integer_ +- `pagesize` _integer_ + +### Returns + +- `data` _array_ of [**Deposit**](#deposit-object) - The list of deposits matching the query. +- `total_count` _int_ - The total number of deposits matching the query. + +```shell +curl -X GET https://example.com/wp-json/wc/v3/payments/deposits?sort=date \ + -u consumer_key:consumer_secret +``` + +> JSON response example: + +```json +{ + "data": [ + { + "id": "wcpay_estimated_weekly_eur_1702598400", + "date": 1702598400000, + "type": "deposit", + "amount": 458784, + "status": "estimated", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1702598400 + }, + { + "id": "po_1OJ466CBu6Jj8nBr38JRxdNE", + "date": 1701648000000, + "type": "deposit", + "amount": 802872, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701648000 + }, + { + "id": "po_1OHylNCBu6Jj8nBr95tE8scS", + "date": 1701302400000, + "type": "deposit", + "amount": 471784, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 6789 (USD)", + "currency": "usd", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701302400 + } + ], + "total_count": 3 +} +``` + +## Get deposits summary + +Fetches a summary of deposits matching the query. This includes the total number of deposits matching the query and a list of deposits. + +Useful in combination with the **List deposits** endpoint to get a summary of deposits matching the query without having to fetch the full list of deposits. + +### HTTP request + +
+
+ GET +
/wp-json/wc/v3/payments/deposits/summary
+
+
+ +### Optional parameters + +- `match` _string_ +- `store_currency_is` _string_ +- `date_before` _string_ +- `date_after` _string_ +- `date_between` _array_ +- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` + +### Returns + +- `count` _int_ - The total number of deposits matching the query. +- `store_currencies` _array_ - The currencies of the deposits matching the query. +- `total` _int_ - The total amount of the deposits matching the query. +- `currency` _string_ - The currency as provided by `store_currency_is` or the default currency of the account. + +```shell +curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/summary \ + -u consumer_key:consumer_secret +``` + +> JSON response example: + +```json +{ + "count": 42, + "store_currencies": [ "chf", "eur", "gbp", "nok", "sek", "usd", "dkk" ], + "total": 5744395, + "currency": "eur" +} +``` + +## Get deposit + +Fetches a deposit by ID. + +### HTTP request + +
+
+ GET +
/wp-json/wc/v3/payments/deposits/{deposit_id}
+
+
+ +### Returns + +If a deposit is found for the provided ID, the response will return a [**Deposit**](#deposit-object) object. + +If no deposit is found for the provided ID, the response will be an empty array. + +```shell +curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/po_123abc \ + -u consumer_key:consumer_secret +``` + +> JSON response example: + +```json +{ + "id": "po_1OGAFOCBu6Jj8nBruJbMbGqD", + "date": 1701043200000, + "type": "deposit", + "amount": 114696, + "status": "paid", + "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", + "currency": "eur", + "automatic": true, + "fee": 0, + "fee_percentage": 0, + "created": 1701043200 +} +``` + +## Submit an instant deposit + +Submit an instant deposit for a list of transactions. Only for eligible accounts. See [Instant Deposits with WooPayments](https://woo.com/document/woopayments/deposits/instant-deposits/) for more information. + +### HTTP request + +
+
+ POST +
/wp-json/wc/v3/payments/deposits
+
+
+ +### Required body properties + +- `type`: _string_ - The type of deposit. `instant` +- `transaction_ids`: _array_ - The list of transaction IDs to deposit. + +```shell +curl -X POST 'https://example.com/wp-json/wc/v3/payments/deposits' \ + -u consumer_key:consumer_secret + --data '{ + "type": "instant", + "transaction_ids": [ + "txn_3OHyIxCIHGKp1UAi0aVyDQ5D", + "txn_3OJSuOCIHGKp1UAi1mRA2lL5" + ] + }' +``` + +## Request a CSV export of deposits + +Request a CSV export of deposits matching the query. A link to the exported CSV will be emailed to the provided email address or the account's primary email address if no email address is provided. + +### HTTP request + +
+
+ POST +
/wp-json/wc/v3/payments/deposits/download
+
+
+ +### Optional body properties + +- `user_email`: _string_ - The email address to send the CSV export link to. If not provided, the account's primary email address will be used. + +### Optional parameters + +- `match` _string_ +- `store_currency_is` _string_ +- `date_before` _string_ +- `date_after` _string_ +- `date_between` _array_ +- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` + +### Returns + +- `exported_deposits` _int_ - The number of deposits exported. + +```shell +curl -X POST 'https://example.com/wp-json/wc/v3/payments/deposits/download?status_is=paid' \ + -u consumer_key:consumer_secret + --data '{ + "user_email": "name@example.woo.com", + }' +``` + +> JSON response example: + +```json +{ + "exported_deposits": 42 +} +``` diff --git a/docs/rest-api/source/index.html.md b/docs/rest-api/source/index.html.md index 33ed0f81641..d4ad854df8a 100644 --- a/docs/rest-api/source/index.html.md +++ b/docs/rest-api/source/index.html.md @@ -17,6 +17,7 @@ includes: - wp-api-v3/customer - wp-api-v3/intent - wp-api-v3/reports + - wp-api-v3/deposits search: false --- From fc0f89dbba6038d48c624a9ab1ce47e809311b70 Mon Sep 17 00:00:00 2001 From: Cvetan Cvetanov Date: Fri, 8 Dec 2023 17:42:54 +0200 Subject: [PATCH 11/56] Fix incorrect amounts caused by zero-decimal currencies on JS CSV export (#7863) --- changelog/fix-7834-zero-decimals-csv-export | 4 ++++ client/deposits/list/index.tsx | 4 ++-- client/disputes/index.tsx | 4 ++-- client/payment-details/readers/index.js | 9 +++++++-- client/transactions/list/index.tsx | 16 +++++++++++----- client/utils/currency/index.js | 18 ++++++++++++++++++ client/utils/currency/test/index.js | 7 +++++++ 7 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 changelog/fix-7834-zero-decimals-csv-export diff --git a/changelog/fix-7834-zero-decimals-csv-export b/changelog/fix-7834-zero-decimals-csv-export new file mode 100644 index 00000000000..1e5b2a1cf87 --- /dev/null +++ b/changelog/fix-7834-zero-decimals-csv-export @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix incorrect amounts caused by zero-decimal currencies on Transactions, Deposits and Deposits CSV export diff --git a/client/deposits/list/index.tsx b/client/deposits/list/index.tsx index f9c89ae88df..6dcf942df1b 100644 --- a/client/deposits/list/index.tsx +++ b/client/deposits/list/index.tsx @@ -25,7 +25,7 @@ import { useDispatch } from '@wordpress/data'; */ import { useDeposits, useDepositsSummary } from 'wcpay/data'; import { displayType, displayStatus } from '../strings'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; import DetailsLink, { getDetailsURL } from 'components/details-link'; import ClickableCell from 'components/clickable-cell'; import Page from '../../components/page'; @@ -143,7 +143,7 @@ export const DepositsList = (): JSX.Element => { display: clickable( displayType[ deposit.type ] ), }, amount: { - value: deposit.amount / 100, + value: formatExportAmount( deposit.amount, deposit.currency ), display: clickable( formatExplicitCurrency( deposit.amount, deposit.currency ) ), diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx index ed2524d641a..b1f70a0d1db 100644 --- a/client/disputes/index.tsx +++ b/client/disputes/index.tsx @@ -33,7 +33,7 @@ import Page from 'components/page'; import { TestModeNotice } from 'components/test-mode-notice'; import { reasons } from './strings'; import { formatStringValue } from 'utils'; -import { formatExplicitCurrency } from 'utils/currency'; +import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; import DisputesFilters from './filters'; import DownloadButton from 'components/download-button'; import disputeStatusMapping from 'components/dispute-status-chip/mappings'; @@ -232,7 +232,7 @@ export const DisputesList = (): JSX.Element => { }; } = { amount: { - value: dispute.amount / 100, + value: formatExportAmount( dispute.amount, dispute.currency ), display: clickable( formatExplicitCurrency( dispute.amount, dispute.currency ) ), diff --git a/client/payment-details/readers/index.js b/client/payment-details/readers/index.js index a87aa84b22d..9ff428c94fe 100644 --- a/client/payment-details/readers/index.js +++ b/client/payment-details/readers/index.js @@ -19,7 +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 } from 'utils/currency'; +import { formatExplicitCurrency, formatExportAmount } from 'utils/currency'; const PaymentCardReaderChargeDetails = ( props ) => { const { readers, chargeError, isLoading } = useCardReaderStats( @@ -100,7 +100,12 @@ const RenderPaymentCardReaderChargeDetails = ( props ) => { display: reader.count, }, { - value: reader.fee ? reader.fee.amount / 100 : 0, + value: reader.fee + ? formatExportAmount( + reader.fee.amount, + reader.fee.currency + ) + : 0, display: reader.fee ? formatExplicitCurrency( reader.fee.amount, diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index df9d360b11d..822047a4afd 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -40,7 +40,11 @@ import { getDetailsURL } from 'components/details-link'; import { displayType } from 'transactions/strings'; import { displayStatus as displayDepositStatus } from 'deposits/strings'; import { formatStringValue } from 'utils'; -import { formatCurrency, formatExplicitCurrency } from 'utils/currency'; +import { + formatCurrency, + formatExplicitCurrency, + formatExportAmount, +} from 'utils/currency'; import { getChargeChannel } from 'utils/charge'; import Deposit from './deposit'; import ConvertedAmount from './converted-amount'; @@ -343,7 +347,7 @@ export const TransactionsList = ( const fromAmount = txn.customer_amount ? txn.customer_amount : 0; return { - value: amount / 100, + value: formatExportAmount( amount, currency ), display: clickable( { const isCardReader = txn.metadata && txn.metadata.charge_type === 'card_reader_fee'; - const feeAmount = - ( isCardReader ? txn.amount : txn.fees * -1 ) / 100; + const feeAmount = formatExportAmount( + isCardReader ? txn.amount : txn.fees * -1, + currency + ); return { value: feeAmount, display: clickable( @@ -461,7 +467,7 @@ export const TransactionsList = ( // fees should display as negative. The format $-9.99 is determined by WC-Admin fees: formatFees(), net: { - value: txn.net / 100, + value: formatExportAmount( txn.net, currency ), display: clickable( formatExplicitCurrency( txn.net, currency ) ), diff --git a/client/utils/currency/index.js b/client/utils/currency/index.js index e4ded8bcdd7..44428c5b372 100644 --- a/client/utils/currency/index.js +++ b/client/utils/currency/index.js @@ -81,6 +81,24 @@ export const isZeroDecimalCurrency = ( currencyCode ) => { ); }; +/** + * Formats the amount for CSV export, considering zero-decimal currencies + * + * @param {number} amount Amount + * @param {string} currencyCode Currency code + * + * @return {number} Export amount + */ +export const formatExportAmount = ( amount, currencyCode ) => { + const isZeroDecimal = isZeroDecimalCurrency( currencyCode ); + + if ( ! isZeroDecimal ) { + amount /= 100; + } + + return amount; +}; + /** * Formats amount according to the given currency. * diff --git a/client/utils/currency/test/index.js b/client/utils/currency/test/index.js index 44398147ad3..72a2f35f4e6 100644 --- a/client/utils/currency/test/index.js +++ b/client/utils/currency/test/index.js @@ -109,4 +109,11 @@ describe( 'Currency utilities', () => { expect( utils.formatCurrency( 100000, 'EUR' ) ).toEqual( '€1,000.00' ); } ); + + test( 'format export amounts', () => { + expect( utils.formatExportAmount( 1000, 'USD' ) ).toEqual( 10 ); + expect( utils.formatExportAmount( 1250, 'USD' ) ).toEqual( 12.5 ); + expect( utils.formatExportAmount( 1000, 'JPY' ) ).toEqual( 1000 ); + expect( utils.formatExportAmount( 3450, 'JPY' ) ).toEqual( 3450 ); + } ); } ); From 9192b174cf88a973b943155a6341f0ffb75800ab Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Sat, 9 Dec 2023 19:59:44 +0300 Subject: [PATCH 12/56] Decode HTML entities in international IP filter country list (#7862) --- .../fix-7838-fraud-filters-html-encode-bug | 4 + .../allow-countries-notice.tsx | 17 +- .../allow-countries-notice.test.tsx.snap | 178 ++++++++++++++++++ .../test/allow-countries-notice.test.tsx | 20 ++ 4 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 changelog/fix-7838-fraud-filters-html-encode-bug diff --git a/changelog/fix-7838-fraud-filters-html-encode-bug b/changelog/fix-7838-fraud-filters-html-encode-bug new file mode 100644 index 00000000000..1b358176fe8 --- /dev/null +++ b/changelog/fix-7838-fraud-filters-html-encode-bug @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix country names with accents not showing correctly on international country fraud filter diff --git a/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx b/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx index 3f9664de54c..bd03bea132c 100644 --- a/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx +++ b/client/settings/fraud-protection/advanced-settings/allow-countries-notice.tsx @@ -11,6 +11,7 @@ import { __ } from '@wordpress/i18n'; import FraudPreventionSettingsContext from './context'; import FraudProtectionRuleCardNotice from './rule-card-notice'; import { getSettingCountries, getSupportedCountriesType } from './utils'; +import { decodeEntities } from '@wordpress/html-entities'; const getNoticeText = ( filterType: string, blocking: boolean ) => { if ( 'all_except' === filterType ) { @@ -61,13 +62,15 @@ const AllowedCountriesNotice: React.FC< AllowedCountriesNoticeProps > = ( { { getNoticeText( supportedCountriesType, isBlocking ) } - { settingCountries - .map( - ( countryCode ) => - wcSettings.countries[ countryCode ] ?? false - ) - .filter( ( element ) => element ) - .join( ', ' ) } + { decodeEntities( + settingCountries + .map( + ( countryCode ) => + wcSettings.countries[ countryCode ] ?? false + ) + .filter( ( element ) => element ) + .join( ', ' ) + ) } ); diff --git a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap index 3a725cde716..da8404cbe8c 100644 --- a/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap +++ b/client/settings/fraud-protection/advanced-settings/test/__snapshots__/allow-countries-notice.test.tsx.snap @@ -711,3 +711,181 @@ Object { "unmount": [Function], } `; + +exports[`Allowed countries rule card notice tests renders html entities correctly 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +

+ Notifications +

+
+
+ Orders from outside of the following countries will be blocked by the filter: São Tomé and Príncipe +
+
+
+
+
+
+ +
+
+ Orders from outside of the following countries will be blocked by the filter: + + São Tomé and Príncipe + +
+
+
+
+
+
+ , + "container":
+
+
+
+
+ +
+
+ Orders from outside of the following countries will be blocked by the filter: + + São Tomé and Príncipe + +
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; 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 e8a4e68e8e3..42f6dd7f1cf 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 @@ -132,4 +132,24 @@ describe( 'Allowed countries rule card notice tests', () => { /Orders from the following countries will be blocked by the filter: Canada, United States/i ); } ); + test( 'renders html entities correctly', () => { + global.wcSettings.admin.preloadSettings.general.woocommerce_allowed_countries = + 'specific'; + global.wcSettings.admin.preloadSettings.general.woocommerce_specific_allowed_countries = [ + 'ST', + ]; + global.wcSettings.countries.ST = + 'São Tomé and Príncipe'; + mockContext.protectionSettingsUI.test_key.block = true; + + const container = render( + + + + ); + expect( container ).toMatchSnapshot(); + expect( container.baseElement ).toHaveTextContent( + /São Tomé and Príncipe/i + ); + } ); } ); From b449de7a483e30de59988d62ddd0cdf8562da117 Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:05:56 +1000 Subject: [PATCH 13/56] Refresh account balance amounts on the Payments Overview screen upon successful instant deposit (#7830) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- ...esh-payments-overview-account-balances-on-instant-deposit | 4 ++++ client/data/deposits/actions.js | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-refresh-payments-overview-account-balances-on-instant-deposit diff --git a/changelog/fix-refresh-payments-overview-account-balances-on-instant-deposit b/changelog/fix-refresh-payments-overview-account-balances-on-instant-deposit new file mode 100644 index 00000000000..59ba1fb0bdd --- /dev/null +++ b/changelog/fix-refresh-payments-overview-account-balances-on-instant-deposit @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Update account balances on the Payments Overview screen when an instant deposit is requested diff --git a/client/data/deposits/actions.js b/client/data/deposits/actions.js index 7a8cbce574e..4513dae4d85 100644 --- a/client/data/deposits/actions.js +++ b/client/data/deposits/actions.js @@ -115,13 +115,16 @@ export function* submitInstantDeposit( transactionIds ) { yield updateInstantDeposit( deposit ); - // Need to invalidate the resolution so that the components will render again. + // Invalidate deposits and deposits overview queries to ensure that the UI is updated with fresh data. yield dispatch( STORE_NAME ).invalidateResolutionForStoreSelector( 'getDeposits' ); yield dispatch( STORE_NAME ).invalidateResolutionForStoreSelector( 'getDepositsOverview' ); + yield dispatch( STORE_NAME ).invalidateResolutionForStoreSelector( + 'getAllDepositsOverviews' + ); yield dispatch( 'core/notices' ).createSuccessNotice( sprintf( From cc87dfac0a125afb57fa5e6ec8bcd4758136e2e7 Mon Sep 17 00:00:00 2001 From: Valery Sukhomlinov <683297+dmvrtx@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:17:44 +0100 Subject: [PATCH 14/56] Remove "Set-up refund policy" Inbox note (#7800) Co-authored-by: Miguel Gasca --- ...-change-setup-refund-policy-note-into-task | 4 ++ includes/class-wc-payments.php | 4 -- ...wc-payments-notes-set-up-refund-policy.php | 51 ------------------- psalm.xml | 6 ++- ...wc-payments-notes-set-up-refund-policy.php | 35 ------------- 5 files changed, 8 insertions(+), 92 deletions(-) create mode 100644 changelog/update-change-setup-refund-policy-note-into-task delete mode 100644 includes/notes/class-wc-payments-notes-set-up-refund-policy.php delete mode 100644 tests/unit/notes/test-class-wc-payments-notes-set-up-refund-policy.php diff --git a/changelog/update-change-setup-refund-policy-note-into-task b/changelog/update-change-setup-refund-policy-note-into-task new file mode 100644 index 00000000000..08cf0d142a9 --- /dev/null +++ b/changelog/update-change-setup-refund-policy-note-into-task @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Remove "Set-up refund policy" Inbox note as superfluous. diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 28775dc9599..0d9137ec4a2 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -1362,10 +1362,8 @@ public static function add_woo_admin_notes() { } if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-up-refund-policy.php'; require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-qualitative-feedback.php'; WC_Payments_Notes_Qualitative_Feedback::possibly_add_note(); - WC_Payments_Notes_Set_Up_Refund_Policy::possibly_add_note(); require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-https-for-checkout.php'; WC_Payments_Notes_Set_Https_For_Checkout::possibly_add_note(); @@ -1418,10 +1416,8 @@ function wcpay_show_old_woocommerce_for_norway_notice() { public static function remove_woo_admin_notes() { if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { self::$remote_note_service->delete_notes(); - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-up-refund-policy.php'; require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-qualitative-feedback.php'; WC_Payments_Notes_Qualitative_Feedback::possibly_delete_note(); - WC_Payments_Notes_Set_Up_Refund_Policy::possibly_delete_note(); require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-https-for-checkout.php'; WC_Payments_Notes_Set_Https_For_Checkout::possibly_delete_note(); diff --git a/includes/notes/class-wc-payments-notes-set-up-refund-policy.php b/includes/notes/class-wc-payments-notes-set-up-refund-policy.php deleted file mode 100644 index 87cf7267957..00000000000 --- a/includes/notes/class-wc-payments-notes-set-up-refund-policy.php +++ /dev/null @@ -1,51 +0,0 @@ -set_title( __( 'Set up refund policy', 'woocommerce-payments' ) ); - $note->set_content( __( 'Protect your merchant account from unauthorized transaction disputes by defining the policy and making it accessible to customers.', 'woocommerce-payments' ) ); - $note->set_content_data( (object) [] ); - $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); - $note->set_name( self::NOTE_NAME ); - $note->set_source( 'woocommerce-payments' ); - $note->add_action( - self::NOTE_NAME, - __( 'Read more', 'woocommerce-payments' ), - self::NOTE_DOCUMENTATION_URL, - 'unactioned', - true - ); - - return $note; - } -} diff --git a/psalm.xml b/psalm.xml index 903fb68a383..064a35fe638 100644 --- a/psalm.xml +++ b/psalm.xml @@ -13,15 +13,17 @@ + - - + + + diff --git a/tests/unit/notes/test-class-wc-payments-notes-set-up-refund-policy.php b/tests/unit/notes/test-class-wc-payments-notes-set-up-refund-policy.php deleted file mode 100644 index 6329bd5ebbd..00000000000 --- a/tests/unit/notes/test-class-wc-payments-notes-set-up-refund-policy.php +++ /dev/null @@ -1,35 +0,0 @@ -=' ) ) { - // Trigger WCPay extension deactivation callback. - wcpay_deactivated(); - - $note_id = WC_Payments_Notes_Set_Up_Refund_Policy::NOTE_NAME; - $this->assertSame( [], ( WC_Data_Store::load( 'admin-note' ) )->get_notes_with_name( $note_id ) ); - } else { - $this->markTestSkipped( 'The used WC components are not backward compatible' ); - } - } - - public function test_adds_note_in_hook() { - if ( version_compare( WC_VERSION, '4.4.0', '>=' ) ) { - // Trigger WCPay extension woo notes hook. - WC_Payments::add_woo_admin_notes(); - - $note_id = WC_Payments_Notes_Set_Up_Refund_Policy::NOTE_NAME; - $this->assertNotSame( [], ( WC_Data_Store::load( 'admin-note' ) )->get_notes_with_name( $note_id ) ); - } else { - $this->markTestSkipped( 'The used WC components are not backward compatible' ); - } - } -} From de517dc3a67d76d691778e5919a7c25192b87b4a Mon Sep 17 00:00:00 2001 From: Brady Valentino Date: Mon, 11 Dec 2023 01:31:14 -0800 Subject: [PATCH 15/56] Update payment assets to match partner brand guidelines (#7860) Co-authored-by: Vladimir Reznichenko --- assets/images/cards/amex.svg | 11 +++++- assets/images/cards/diners.svg | 44 +++++++++++++++++++-- assets/images/cards/discover.svg | 46 ++++++++++++++++++++-- assets/images/cards/visa.svg | 7 +++- assets/images/payment-methods/cc.svg | 58 ++++++++++++++++------------ changelog/fix-update-payment-assets | 4 ++ 6 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 changelog/fix-update-payment-assets diff --git a/assets/images/cards/amex.svg b/assets/images/cards/amex.svg index 292e48811f0..28a7d84ed67 100644 --- a/assets/images/cards/amex.svg +++ b/assets/images/cards/amex.svg @@ -1,4 +1,11 @@ - - + + + + + + + + + diff --git a/assets/images/cards/diners.svg b/assets/images/cards/diners.svg index f585af26c5d..fd3a3867496 100644 --- a/assets/images/cards/diners.svg +++ b/assets/images/cards/diners.svg @@ -1,6 +1,42 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cards/discover.svg b/assets/images/cards/discover.svg index db205b2f836..629a039192d 100644 --- a/assets/images/cards/discover.svg +++ b/assets/images/cards/discover.svg @@ -1,5 +1,45 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cards/visa.svg b/assets/images/cards/visa.svg index 9592cb31f8c..07b0b6592cb 100644 --- a/assets/images/cards/visa.svg +++ b/assets/images/cards/visa.svg @@ -1,4 +1,7 @@ - - + + + + + diff --git a/assets/images/payment-methods/cc.svg b/assets/images/payment-methods/cc.svg index 8c7fd884aa1..afed0263dca 100644 --- a/assets/images/payment-methods/cc.svg +++ b/assets/images/payment-methods/cc.svg @@ -1,26 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/changelog/fix-update-payment-assets b/changelog/fix-update-payment-assets new file mode 100644 index 00000000000..0b45545b774 --- /dev/null +++ b/changelog/fix-update-payment-assets @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Actualized cards-related assets for settings and transactions pages. From 0640c56368ba792b20164a48e8811194b402e299 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Mon, 11 Dec 2023 21:50:24 +0100 Subject: [PATCH 16/56] Clean the code which checks for UPE enablement (#7821) Co-authored-by: Timur Karimov --- changelog/cleanup-is-upe-enabled-logic | 4 + client/additional-methods-setup/index.js | 13 +- client/additional-methods-setup/test/index.js | 42 ---- .../upe-preview-methods-selector/index.js | 14 +- .../setup-complete-task.js | 12 +- .../test/setup-complete-task.test.js | 30 --- client/checkout/api/index.js | 58 ++--- client/checkout/blocks/index.js | 3 - client/checkout/classic/event-handlers.js | 6 +- client/checkout/classic/payment-processing.js | 2 +- .../classic/test/payment-processing.test.js | 6 - client/payment-methods/index.js | 115 ++-------- client/payment-methods/test/index.js | 119 ++-------- client/settings/disable-upe-modal/index.js | 146 ------------- client/settings/disable-upe-modal/style.scss | 11 - client/settings/settings-manager/index.js | 52 ++--- .../settings-manager/test/index.test.js | 31 +-- client/settings/survey-modal/index.js | 26 +-- .../transactions/manual-capture-control.tsx | 5 +- client/settings/wcpay-upe-toggle/context.js | 13 -- client/settings/wcpay-upe-toggle/hook.js | 17 -- client/settings/wcpay-upe-toggle/provider.js | 84 -------- .../wcpay-upe-toggle/test/provider.test.js | 204 ------------------ includes/admin/class-wc-payments-admin.php | 5 - ...ass-wc-rest-upe-flag-toggle-controller.php | 192 ----------------- includes/class-wc-payments-checkout.php | 3 - includes/class-wc-payments-features.php | 43 ---- includes/class-wc-payments-status.php | 11 +- includes/class-wc-payments.php | 24 +-- ...al-payment-methods-admin-notes-removal.php | 48 +++++ .../migrations/class-track-upe-status.php | 61 ------ .../PaymentMethodsCompatibility.php | 5 - ...ments-notes-additional-payment-methods.php | 131 +---------- .../class-wc-payments-notes-loan-approved.php | 2 +- ...ss-wc-payments-notes-set-up-stripelink.php | 5 - .../specs/performance/payment-methods.spec.js | 10 - .../shopper/shopper-bnpls-checkout.spec.js | 2 - ...ferred-intent-creation-upe-enabled.spec.js | 2 - .../shopper-upe-enabled-all-flows.spec.js | 2 - .../shopper-upe-enabled-all-flows.spec.js | 2 - ...nt-payment-settings-manual-capture.spec.js | 19 +- .../shopper-checkout-free-cart.spec.js | 15 +- tests/e2e/utils/flows.js | 186 ---------------- .../admin/test-class-wc-payments-admin.php | 3 - ...s-wc-rest-payments-settings-controller.php | 3 - ...ass-wc-rest-upe-flag-toggle-controller.php | 193 ----------------- tests/unit/bootstrap.php | 2 - .../test-class-track-upe-status.php | 77 ------- ...st-class-payment-methods-compatibility.php | 10 - ...ments-notes-additional-payment-methods.php | 39 ---- ...ss-wc-payments-notes-set-up-stripelink.php | 8 - .../test-class-upe-payment-gateway.php | 5 - .../test-class-upe-split-payment-gateway.php | 4 - .../unit/test-class-wc-payments-features.php | 11 +- .../test-class-wc-payments-token-service.php | 8 - tests/unit/test-class-wc-payments.php | 2 - 56 files changed, 156 insertions(+), 1990 deletions(-) create mode 100644 changelog/cleanup-is-upe-enabled-logic delete mode 100644 client/additional-methods-setup/test/index.js delete mode 100644 client/settings/disable-upe-modal/index.js delete mode 100644 client/settings/disable-upe-modal/style.scss delete mode 100644 client/settings/wcpay-upe-toggle/context.js delete mode 100644 client/settings/wcpay-upe-toggle/hook.js delete mode 100644 client/settings/wcpay-upe-toggle/provider.js delete mode 100644 client/settings/wcpay-upe-toggle/test/provider.test.js delete mode 100644 includes/admin/class-wc-rest-upe-flag-toggle-controller.php create mode 100644 includes/migrations/class-additional-payment-methods-admin-notes-removal.php delete mode 100644 includes/migrations/class-track-upe-status.php delete mode 100644 tests/unit/admin/test-class-wc-rest-upe-flag-toggle-controller.php delete mode 100644 tests/unit/migrations/test-class-track-upe-status.php delete mode 100644 tests/unit/notes/test-class-wc-payments-notes-additional-payment-methods.php diff --git a/changelog/cleanup-is-upe-enabled-logic b/changelog/cleanup-is-upe-enabled-logic new file mode 100644 index 00000000000..79bae6fa471 --- /dev/null +++ b/changelog/cleanup-is-upe-enabled-logic @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Cleanup the deprecated payment gateway processing - part III diff --git a/client/additional-methods-setup/index.js b/client/additional-methods-setup/index.js index 86c9b604e33..06bb77b8f35 100644 --- a/client/additional-methods-setup/index.js +++ b/client/additional-methods-setup/index.js @@ -9,24 +9,13 @@ */ import Page from 'components/page'; import UpePreviewMethodSelector from './upe-preview-methods-selector'; -import WcPayUpeContextProvider from '../settings/wcpay-upe-toggle/provider'; import WCPaySettingsContext from '../settings/wcpay-settings-context'; const AdditionalMethodsPage = () => { - const { - isUpeEnabled, - upeType, - } = window.wcpaySettings.additionalMethodsSetup; - return ( - - - + ); diff --git a/client/additional-methods-setup/test/index.js b/client/additional-methods-setup/test/index.js deleted file mode 100644 index 6a2f6924427..00000000000 --- a/client/additional-methods-setup/test/index.js +++ /dev/null @@ -1,42 +0,0 @@ -/** @format */ - -/** - * External dependencies - */ -import { render, screen } from '@testing-library/react'; -import UpePreviewMethodSelector from '../upe-preview-methods-selector'; - -/** - * Internal dependencies - */ -import AdditionalMethodsPage from '../'; - -jest.mock( '../upe-preview-methods-selector', () => jest.fn() ); - -describe( 'AdditionalMethodsPage', () => { - beforeEach( () => { - UpePreviewMethodSelector.mockReturnValue( -

UPE preview method selector

- ); - } ); - - afterEach( () => { - jest.restoreAllMocks(); - } ); - - describe( 'if UPE settings preview is enabled', () => { - it( 'renders "Boost your sales by accepting new payment methods" page', () => { - global.wcpaySettings = { - additionalMethodsSetup: { - isUpeEnabled: false, - }, - }; - - render( ); - - expect( - screen.queryByText( 'UPE preview method selector' ) - ).toBeInTheDocument(); - } ); - } ); -} ); diff --git a/client/additional-methods-setup/upe-preview-methods-selector/index.js b/client/additional-methods-setup/upe-preview-methods-selector/index.js index 541865d4ebb..fcd8698b0ed 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/index.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/index.js @@ -11,26 +11,14 @@ import Wizard from '../wizard/wrapper'; import WizardTask from '../wizard/task'; import WizardTaskList from '../wizard/task-list'; import SetupCompleteTask from './setup-complete-task'; -import useIsUpeEnabled from '../../settings/wcpay-upe-toggle/hook'; import AddPaymentMethodsTask from './add-payment-methods-task'; import './index.scss'; const UpePreviewMethodsSelector = () => { - const [ isUpeEnabled ] = useIsUpeEnabled(); - return ( - + diff --git a/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js b/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js index f36121320b8..6a5c33b3881 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/setup-complete-task.js @@ -2,7 +2,7 @@ * External dependencies */ import React from 'react'; -import { useEffect, useContext } from '@wordpress/element'; +import { useContext } from '@wordpress/element'; import { __, _n, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; @@ -11,7 +11,6 @@ import { Button } from '@wordpress/components'; */ import CollapsibleBody from '../wizard/collapsible-body'; import WizardTaskItem from '../wizard/task-item'; -import WizardTaskContext from '../wizard/task/context'; import WCPaySettingsContext from '../../settings/wcpay-settings-context'; import { useEnabledPaymentMethodIds } from '../../data'; import WizardContext from '../wizard/wrapper/context'; @@ -70,19 +69,10 @@ const EnabledMethodsList = () => { }; const SetupComplete = () => { - const { isActive } = useContext( WizardTaskContext ); const { featureFlags: { multiCurrency }, } = useContext( WCPaySettingsContext ); - useEffect( () => { - if ( ! isActive ) { - return; - } - - window.wcpaySettings.additionalMethodsSetup.isUpeEnabled = true; - }, [ isActive ] ); - return ( ( { describe( 'SetupComplete', () => { beforeEach( () => { - window.wcpaySettings = { - additionalMethodsSetup: { - isUpeEnabled: false, - }, - }; - useEnabledPaymentMethodIds.mockReturnValue( [ [ 'card', @@ -41,30 +35,6 @@ describe( 'SetupComplete', () => { ] ); } ); - it( 'sets isUpeEnabled if isActive', () => { - render( - - - - ); - - expect( - window.wcpaySettings.additionalMethodsSetup.isUpeEnabled - ).toBeTruthy(); - } ); - - it( 'does not set isUpeEnabled if not isActive', () => { - render( - - - - ); - - expect( - window.wcpaySettings.additionalMethodsSetup.isUpeEnabled - ).toBeFalsy(); - } ); - it( 'renders setup complete messaging when context value is undefined', () => { render( diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index b7deafca8af..3e2dcf1f7af 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -69,16 +69,10 @@ export default class WCPayAPI { accountId, forceNetworkSavedCards, locale, - isUPEEnabled, - isUPEDeferredEnabled, isStripeLinkEnabled, } = this.options; - if ( - forceNetworkSavedCards && - ! forceAccountRequest && - ! ( isUPEEnabled && ! isUPEDeferredEnabled ) - ) { + if ( forceNetworkSavedCards && ! forceAccountRequest ) { if ( ! this.stripePlatform ) { this.stripePlatform = this.createStripe( publishableKey, @@ -89,25 +83,17 @@ export default class WCPayAPI { } if ( ! this.stripe ) { - if ( isUPEEnabled ) { - let betas = [ 'card_country_event_beta_1' ]; - if ( isStripeLinkEnabled ) { - betas = betas.concat( [ 'link_autofill_modal_beta_1' ] ); - } - - this.stripe = this.createStripe( - publishableKey, - locale, - accountId, - betas - ); - } else { - this.stripe = this.createStripe( - publishableKey, - locale, - accountId - ); + let betas = [ 'card_country_event_beta_1' ]; + if ( isStripeLinkEnabled ) { + betas = betas.concat( [ 'link_autofill_modal_beta_1' ] ); } + + this.stripe = this.createStripe( + publishableKey, + locale, + accountId, + betas + ); } return this.stripe; } @@ -352,14 +338,11 @@ export default class WCPayAPI { /** * Creates a setup intent without confirming it. * - * @param {string} paymentMethodType Stripe payment method type ID. * @return {Promise} The final promise for the request to the server. */ - initSetupIntent( paymentMethodType = '' ) { - let path = 'init_setup_intent'; - if ( this.options.isUPESplitEnabled && paymentMethodType ) { - path += `_${ paymentMethodType }`; - } + initSetupIntent() { + const path = 'init_setup_intent'; + return this.request( buildAjaxURL( getConfig( 'wcAjaxUrl' ), path ), { _ajax_nonce: getConfig( 'createSetupIntentNonce' ), } ).then( ( response ) => { @@ -414,16 +397,13 @@ export default class WCPayAPI { * @return {Promise} The final promise for the request to the server. */ createIntent( options ) { - const { fingerprint, paymentMethodType, orderId } = options; - let path = 'create_payment_intent'; + const { fingerprint, orderId } = options; + const path = 'create_payment_intent'; const params = { _ajax_nonce: getConfig( 'createPaymentIntentNonce' ), 'wcpay-fingerprint': fingerprint, }; - if ( this.options.isUPESplitEnabled && paymentMethodType ) { - path += `_${ paymentMethodType }`; - } if ( orderId ) { params.wcpay_order_id = orderId; } @@ -468,10 +448,8 @@ export default class WCPayAPI { paymentCountry, fingerprint ) { - let path = 'update_payment_intent'; - if ( this.options.isUPESplitEnabled ) { - path += `_${ selectedUPEPaymentType }`; - } + const path = 'update_payment_intent'; + return this.request( buildAjaxURL( getConfig( 'wcAjaxUrl' ), path ), { wcpay_order_id: orderId, wc_payment_intent_id: paymentIntentId, diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index 72f1c49b6b9..deed017c0d9 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -62,9 +62,6 @@ const api = new WCPayAPI( accountId: getUPEConfig( 'accountId' ), forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ), locale: getUPEConfig( 'locale' ), - isUPEEnabled: getUPEConfig( 'isUPEEnabled' ), - isUPESplitEnabled: getUPEConfig( 'isUPESplitEnabled' ), - isUPEDeferredEnabled: getUPEConfig( 'isUPEDeferredEnabled' ), isStripeLinkEnabled, }, request diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index dbf292944a5..00a2581b8a9 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -42,11 +42,9 @@ jQuery( function ( $ ) { accountId: getUPEConfig( 'accountId' ), forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ), locale: getUPEConfig( 'locale' ), - isUPEEnabled: getUPEConfig( 'isUPEEnabled' ), isStripeLinkEnabled: isLinkEnabled( getUPEConfig( 'paymentMethodsConfig' ) ), - isUPEDeferredEnabled: getUPEConfig( 'isUPEDeferredEnabled' ), }, apiRequest ); @@ -79,9 +77,7 @@ jQuery( function ( $ ) { $( 'form#add_payment_method' ).length || $( 'form#order_review' ).length ) { - if ( getUPEConfig( 'isUPEEnabled' ) ) { - maybeMountStripePaymentElement(); - } + maybeMountStripePaymentElement(); } $( 'form#add_payment_method' ).on( 'submit', function () { diff --git a/client/checkout/classic/payment-processing.js b/client/checkout/classic/payment-processing.js index 6d47f327882..2b1542792ff 100644 --- a/client/checkout/classic/payment-processing.js +++ b/client/checkout/classic/payment-processing.js @@ -304,7 +304,7 @@ export function renderTerms( event ) { return; } const upeElement = gatewayUPEComponents[ paymentMethodType ].upeElement; - if ( getUPEConfig( 'isUPEEnabled' ) && upeElement ) { + if ( upeElement ) { upeElement.update( { terms: getTerms( getUPEConfig( 'paymentMethodsConfig' ), value ), } ); diff --git a/client/checkout/classic/test/payment-processing.test.js b/client/checkout/classic/test/payment-processing.test.js index 984adb378a6..cb2b2d7b4e0 100644 --- a/client/checkout/classic/test/payment-processing.test.js +++ b/client/checkout/classic/test/payment-processing.test.js @@ -236,15 +236,10 @@ describe( 'Stripe Payment Element mounting', () => { if ( argument === 'currency' ) { return 'eur'; } - - if ( argument === 'isUPEEnabled' ) { - return true; - } } ); getSelectedUPEGatewayPaymentMethod.mockReturnValue( 'card' ); renderTerms( event ); - expect( getUPEConfig ).toHaveBeenCalledWith( 'isUPEEnabled' ); expect( mockUpdateFunction ).toHaveBeenCalled(); } ); @@ -256,7 +251,6 @@ describe( 'Stripe Payment Element mounting', () => { }; getSelectedUPEGatewayPaymentMethod.mockReturnValue( null ); renderTerms( event ); - expect( getUPEConfig ).not.toHaveBeenCalledWith( 'isUPEEnabled' ); expect( mockUpdateFunction ).not.toHaveBeenCalled(); } ); diff --git a/client/payment-methods/index.js b/client/payment-methods/index.js index 816402c854f..950610e6fba 100644 --- a/client/payment-methods/index.js +++ b/client/payment-methods/index.js @@ -3,11 +3,10 @@ /** * External dependencies */ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { __ } from '@wordpress/i18n'; import { Card, CardHeader, DropdownMenu } from '@wordpress/components'; import { moreVertical } from '@wordpress/icons'; -import classNames from 'classnames'; /** * Internal dependencies @@ -21,14 +20,11 @@ import { useUnselectedPaymentMethod, useAccountDomesticCurrency, } from 'wcpay/data'; - -import WcPayUpeContext from '../settings/wcpay-upe-toggle/context'; import PAYMENT_METHOD_IDS from './constants'; // Survey modal imports. import WcPaySurveyContextProvider from '../settings/survey-modal/provider'; import SurveyModal from '../settings/survey-modal'; -import DisableUPEModal from '../settings/disable-upe-modal'; import PaymentMethodsList from 'components/payment-methods-list'; import PaymentMethod from 'components/payment-methods-list/payment-method'; import methodsConfiguration from '../payment-methods-map'; @@ -38,35 +34,19 @@ import ConfirmPaymentMethodActivationModal from './activation-modal'; import ConfirmPaymentMethodDeleteModal from './delete-modal'; import { getPaymentMethodDescription } from 'wcpay/utils/payment-methods'; import CapabilityRequestNotice from './capability-request'; -import InlineNotice from 'wcpay/components/inline-notice'; import { BuildMissingCurrenciesTooltipMessage } from 'wcpay/components/currency-information-for-methods'; const PaymentMethodsDropdownMenu = ( { setOpenModal } ) => { - const { isUpeEnabled, upeType } = useContext( WcPayUpeContext ); - const isDisablePossible = isUpeEnabled && upeType !== 'deferred_intent'; - const label = isDisablePossible - ? __( 'Add feedback or disable', 'woocommerce-payments' ) - : __( 'Add feedback', 'woocommerce-payments' ); - - const buttons = [ - { - title: __( 'Provide feedback', 'woocommerce-payments' ), - onClick: () => setOpenModal( 'survey' ), - }, - ]; - - if ( isDisablePossible ) { - buttons.push( { - title: 'Disable', - onClick: () => setOpenModal( 'disable' ), - } ); - } - return ( setOpenModal( 'survey' ), + }, + ] } /> ); }; @@ -170,28 +150,10 @@ const PaymentMethods = () => { } }; - const { isUpeEnabled, status, upeType } = useContext( WcPayUpeContext ); const [ openModalIdentifier, setOpenModalIdentifier ] = useState( '' ); - const rollbackNoticeForLegacyUPE = __( - // eslint-disable-next-line max-len - 'You have been switched from the new checkout to your previous checkout experience. We will keep you posted on the new checkout availability.', - 'woocommerce-payments' - ); - const rollbackNoticeForLegacyCard = __( - // eslint-disable-next-line max-len - 'You have been switched from the new checkout to your previous card experience. We will keep you posted on the new checkout availability.' - ); return ( <> - { openModalIdentifier === 'disable' ? ( - - setOpenModalIdentifier( 'survey' ) - } - /> - ) : null } { openModalIdentifier === 'survey' ? ( { ) : null } - - { isUpeEnabled && ( - -

- - { __( - 'Payment methods', - 'woocommerce-payments' - ) } - -

- -
- ) } - - { isUpeEnabled && upeType === 'legacy' && ( - - - { rollbackNoticeForLegacyUPE } - - - ) } - - { ! isUpeEnabled && ( - - - { rollbackNoticeForLegacyCard } - - - ) } + + +

+ + { __( 'Payment methods', 'woocommerce-payments' ) } + +

+ +
@@ -292,13 +221,11 @@ const PaymentMethods = () => { } // The card payment method is required when UPE is active, and it can't be disabled/unchecked. required={ - PAYMENT_METHOD_IDS.CARD === id && - isUpeEnabled + PAYMENT_METHOD_IDS.CARD === id } locked={ PAYMENT_METHOD_IDS.CARD === id && - isCreditCardEnabled && - isUpeEnabled + isCreditCardEnabled } Icon={ Icon } status={ diff --git a/client/payment-methods/test/index.js b/client/payment-methods/test/index.js index 7df9aee2b9f..c5e55d47643 100644 --- a/client/payment-methods/test/index.js +++ b/client/payment-methods/test/index.js @@ -21,9 +21,6 @@ import { useSelectedPaymentMethod, useUnselectedPaymentMethod, } from 'wcpay/data'; -import WCPaySettingsContext from '../../settings/wcpay-settings-context'; -import WcPayUpeContextProvider from '../../settings/wcpay-upe-toggle/provider'; -import WcPayUpeContext from '../../settings/wcpay-upe-toggle/context'; import { upeCapabilityStatuses } from 'wcpay/additional-methods-setup/constants'; jest.mock( '@woocommerce/components', () => { @@ -100,11 +97,7 @@ describe( 'PaymentMethods', () => { [ 'card', 'sepa_debit' ], ] ); - render( - - - - ); + render( ); const cc = screen.getByRole( 'checkbox', { name: 'Credit / Debit card', @@ -209,11 +202,7 @@ describe( 'PaymentMethods', () => { }, } ); - render( - - - - ); + render( ); expect( screen.queryAllByText( /Pending /i ).length ).toEqual( 4 ); } ); @@ -235,11 +224,7 @@ describe( 'PaymentMethods', () => { global.wcpaySettings.isBnplAffirmAfterpayEnabled = true; - render( - - - - ); + render( ); const affirm = screen.getByRole( 'checkbox', { name: 'Affirm' } ); const afterpay = screen.getByRole( 'checkbox', { @@ -286,11 +271,7 @@ describe( 'PaymentMethods', () => { } ); const renderPaymentElements = () => { - render( - - - - ); + render( ); }; renderPaymentElements(); @@ -306,80 +287,22 @@ describe( 'PaymentMethods', () => { expect( afterpay ).toBeChecked(); } ); - test.each( [ - [ false, false ], - [ false, true ], - [ true, true ], - ] )( - 'express payments should not rendered when UPE preview = %s and UPE = %s', - ( upeSettingsPreview, upe ) => { - const featureFlagContext = { - featureFlags: { upeSettingsPreview, upe }, - }; - const upeContext = { - isUpeEnabled: upe, - setIsUpeEnabled: () => null, - status: 'resolved', - }; - - render( - - - - - - ); - - const enableWooCommercePaymentText = screen.queryByText( - 'Enable the new WooPayments checkout experience, which will become the default on November 1, 2023' - ); + test( 'renders the payment methods component and feedback button', () => { + render( ); - expect( enableWooCommercePaymentText ).toBeNull(); - } - ); - - test( 'renders the feedback elements when UPE is enabled', () => { - render( - - - - ); - const disableUPEButton = screen.queryByRole( 'button', { - name: 'Add feedback or disable', + const feedbackButton = screen.queryByRole( 'button', { + name: 'Add feedback', } ); - expect( disableUPEButton ).toBeInTheDocument(); + expect( feedbackButton ).toBeInTheDocument(); + expect( screen.queryByText( 'Payment methods' ) ).toBeInTheDocument(); expect( screen.queryByText( 'Payment methods' ).parentElement ).toHaveTextContent( 'Payment methods' ); } ); - test( 'Does not render the feedback elements when UPE is disabled', () => { - render( - - - - ); - - const disableUPEButton = screen.queryByRole( 'button', { - name: 'Add feedback or disable', - } ); - - expect( disableUPEButton ).not.toBeInTheDocument(); - expect( - screen.queryByText( 'Payment methods' ) - ).not.toBeInTheDocument(); - } ); - it( 'should only be able to leave feedback', () => { - render( - - - - ); + render( ); const kebabMenuWithFeedbackOnly = screen.queryByRole( 'button', { name: 'Add feedback', } ); @@ -402,11 +325,7 @@ describe( 'PaymentMethods', () => { }, } ); - render( - - - - ); + render( ); expect( screen.queryByRole( 'checkbox', { name: /Bancontact/ } ) @@ -448,11 +367,7 @@ describe( 'PaymentMethods', () => { }, } ); - render( - - - - ); + render( ); expect( screen.queryByLabelText( 'Bancontact' ) ).toBeInTheDocument(); @@ -492,11 +407,7 @@ describe( 'PaymentMethods', () => { }, } ); - const { container } = render( - - - - ); + const { container } = render( ); // Checkbox shouldn't be rendered. expect( @@ -505,7 +416,7 @@ describe( 'PaymentMethods', () => { const svgIcon = container.querySelectorAll( '.gridicons-notice-outline' - )[ 1 ]; + )[ 0 ]; expect( svgIcon ).toBeInTheDocument(); diff --git a/client/settings/disable-upe-modal/index.js b/client/settings/disable-upe-modal/index.js deleted file mode 100644 index a28bd7e294d..00000000000 --- a/client/settings/disable-upe-modal/index.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * External dependencies - */ -import React, { useContext, useEffect } from 'react'; -import { __, sprintf } from '@wordpress/i18n'; -import { dispatch } from '@wordpress/data'; -import { Button, ExternalLink } from '@wordpress/components'; -import interpolateComponents from '@automattic/interpolate-components'; - -/** - * Internal dependencies - */ -import './style.scss'; -import ConfirmationModal from 'components/confirmation-modal'; -import useIsUpeEnabled from 'settings/wcpay-upe-toggle/hook'; -import WcPayUpeContext from 'settings/wcpay-upe-toggle/context'; -import InlineNotice from 'components/inline-notice'; -import { useEnabledPaymentMethodIds } from '../../data'; -import PaymentMethodIcon from '../payment-method-icon'; - -const NeedHelpBarSection = () => { - return ( - - { interpolateComponents( { - mixedString: __( - 'Need help? Visit {{ docsLink /}} or {{supportLink /}}.', - 'woocommerce-payments' - ), - components: { - docsLink: ( - // eslint-disable-next-line max-len - - { sprintf( - /* translators: %s: WooPayments */ - __( '%s docs', 'woocommerce-payments' ), - 'WooPayments' - ) } - - ), - supportLink: ( - // eslint-disable-next-line max-len - - { __( 'contact support', 'woocommerce-payments' ) } - - ), - }, - } ) } - - ); -}; - -const DisableUpeModalBody = () => { - const [ enabledPaymentMethodIds ] = useEnabledPaymentMethodIds(); - const upePaymentMethods = enabledPaymentMethodIds.filter( - ( method ) => method !== 'card' - ); - - return ( - <> -

- { __( - // eslint-disable-next-line max-len - 'Without the new payments experience, your customers will only be able to pay using credit card / debit card. You will not be able to add other sales-boosting payment methods anymore.', - 'woocommerce-payments' - ) } -

- { upePaymentMethods.length > 0 ? ( - <> -

- { __( - 'Payment methods that require the new payments experience:', - 'woocommerce-payments' - ) } -

-
    - { upePaymentMethods.map( ( method ) => ( -
  • - -
  • - ) ) } -
- - ) : null } - - - ); -}; - -const DisableUpeModal = ( { setOpenModal, triggerAfterDisable } ) => { - const [ isUpeEnabled, setIsUpeEnabled ] = useIsUpeEnabled(); - const { status } = useContext( WcPayUpeContext ); - - useEffect( () => { - if ( ! isUpeEnabled ) { - setOpenModal( '' ); - triggerAfterDisable(); - } - }, [ isUpeEnabled, setOpenModal, triggerAfterDisable ] ); - - useEffect( () => { - if ( status === 'error' ) { - dispatch( 'core/notices' ).createErrorNotice( - __( - 'There was an error disabling the new payment methods.', - 'woocommerce-payments' - ) - ); - } - }, [ status ] ); - - return ( - <> - setOpenModal( '' ) } - actions={ - <> - - - - } - > - - - - ); -}; -export default DisableUpeModal; diff --git a/client/settings/disable-upe-modal/style.scss b/client/settings/disable-upe-modal/style.scss deleted file mode 100644 index 55f2dde2d4f..00000000000 --- a/client/settings/disable-upe-modal/style.scss +++ /dev/null @@ -1,11 +0,0 @@ -.disable-modal-section { - .deactivating-payment-methods-list { - min-height: 150px; - - > * { - &:not( :last-child ) { - margin-bottom: $grid-unit-10; - } - } - } -} diff --git a/client/settings/settings-manager/index.js b/client/settings/settings-manager/index.js index ebd1c73085f..8204bad0ac9 100644 --- a/client/settings/settings-manager/index.js +++ b/client/settings/settings-manager/index.js @@ -2,7 +2,7 @@ /** * External dependencies */ -import React, { useContext, useState, useLayoutEffect } from 'react'; +import React, { useState, useLayoutEffect } from 'react'; import { ExternalLink } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { getQuery } from '@woocommerce/navigation'; @@ -19,9 +19,7 @@ import SettingsLayout from '../settings-layout'; import SaveSettingsSection from '../save-settings-section'; import Transactions from '../transactions'; import Deposits from '../deposits'; -import WCPaySettingsContext from '../wcpay-settings-context'; import LoadableSettingsSection from '../loadable-settings-section'; -import WcPayUpeContextProvider from '../wcpay-upe-toggle/provider'; import ErrorBoundary from '../../components/error-boundary'; import { useDepositDelayDays, useSettings } from '../../data'; import FraudProtection from '../fraud-protection'; @@ -152,13 +150,6 @@ const AdvancedDescription = () => { }; const SettingsManager = () => { - const { - featureFlags: { - upeSettingsPreview: isUPESettingsPreviewEnabled, - upe: isUpeEnabled, - upeType, - }, - } = useContext( WCPaySettingsContext ); const [ isTransactionInputsValid, setTransactionInputsValid ] = useState( true ); @@ -205,23 +196,16 @@ const SettingsManager = () => { - { isUPESettingsPreviewEnabled && ( - - - - - - - - - - ) } + + + + + + + { > - - - + diff --git a/client/settings/settings-manager/test/index.test.js b/client/settings/settings-manager/test/index.test.js index 51e861bd192..c9709f1ec15 100644 --- a/client/settings/settings-manager/test/index.test.js +++ b/client/settings/settings-manager/test/index.test.js @@ -8,46 +8,21 @@ import { render, screen } from '@testing-library/react'; * Internal dependencies */ import SettingsManager from '..'; -import WCPaySettingsContext from '../../wcpay-settings-context'; describe( 'SettingsManager', () => { - it( 'renders the PaymentMethods section if the UPE feature flag is enabled', () => { - const context = { featureFlags: { upeSettingsPreview: true } }; + it( 'renders the PaymentMethods section', () => { global.wcpaySettings = {}; - render( - - - - ); + render( ); expect( screen.queryByText( 'Payments accepted on checkout' ) ).toBeInTheDocument(); } ); - it( 'does not render the PaymentMethods section if the UPE feature flag is disabled', () => { - const context = { featureFlags: {} }; - global.wcpaySettings = {}; - render( - - - - ); - - expect( - screen.queryByText( 'Payments accepted on checkout' ) - ).not.toBeInTheDocument(); - } ); - it( 'renders the Fraud Protection settings section', () => { - const context = { featureFlags: {} }; global.wcpaySettings = {}; - render( - - - - ); + render( ); expect( screen.queryByText( 'Fraud protection' ) ).toBeInTheDocument(); } ); diff --git a/client/settings/survey-modal/index.js b/client/settings/survey-modal/index.js index 45bfe42194a..58f46eb2c44 100644 --- a/client/settings/survey-modal/index.js +++ b/client/settings/survey-modal/index.js @@ -11,41 +11,23 @@ import { Button, RadioControl, TextareaControl } from '@wordpress/components'; */ import './style.scss'; import ConfirmationModal from 'components/confirmation-modal'; -import useIsUpeEnabled from 'settings/wcpay-upe-toggle/hook'; import { wcPaySurveys } from './questions'; import WcPaySurveyContext from './context'; -import InlineNotice from 'components/inline-notice'; import { LoadableBlock } from 'components/loadable'; const SurveyModalBody = ( { options, surveyQuestion } ) => { - const [ isUpeEnabled ] = useIsUpeEnabled(); const { surveyAnswers, setSurveyAnswers, isLoadingSsr } = useContext( WcPaySurveyContext ); return ( <> - { ! isUpeEnabled && ( - - { __( - "You've disabled the new payments experience in your store.", - 'woocommerce-payments' - ) } - - ) } { setSurveyAnswers( ( prev ) => ( { diff --git a/client/settings/transactions/manual-capture-control.tsx b/client/settings/transactions/manual-capture-control.tsx index 4ef796c2645..6482106c5ff 100644 --- a/client/settings/transactions/manual-capture-control.tsx +++ b/client/settings/transactions/manual-capture-control.tsx @@ -12,7 +12,6 @@ import { useState } from '@wordpress/element'; import { useManualCapture, useCardPresentEligible } from '../../data'; import './style.scss'; import ConfirmationModal from 'wcpay/components/confirmation-modal'; -import useIsUpeEnabled from 'wcpay/settings/wcpay-upe-toggle/hook'; import interpolateComponents from '@automattic/interpolate-components'; const ManualCaptureControl = (): JSX.Element => { @@ -27,11 +26,9 @@ const ManualCaptureControl = (): JSX.Element => { setIsManualDepositConfirmationModalOpen, ] = useState( false ); - const [ isUpeEnabled ] = useIsUpeEnabled() as [ boolean ]; - const handleCheckboxToggle = ( isChecked: boolean ) => { // toggling from "manual" capture to "automatic" capture - no need to show the modal. - if ( ! isChecked || ! isUpeEnabled ) { + if ( ! isChecked ) { setIsManualCaptureEnabled( isChecked ); return; } diff --git a/client/settings/wcpay-upe-toggle/context.js b/client/settings/wcpay-upe-toggle/context.js deleted file mode 100644 index 0ed4e203507..00000000000 --- a/client/settings/wcpay-upe-toggle/context.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * External dependencies - */ -import { createContext } from 'react'; - -const WcPayUpeContext = createContext( { - isUpeEnabled: false, - setIsUpeEnabled: () => null, - status: 'resolved', - upeType: '', -} ); - -export default WcPayUpeContext; diff --git a/client/settings/wcpay-upe-toggle/hook.js b/client/settings/wcpay-upe-toggle/hook.js deleted file mode 100644 index 8cea0f9d2c9..00000000000 --- a/client/settings/wcpay-upe-toggle/hook.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * External dependencies - */ -import { useContext } from 'react'; - -/** - * Internal dependencies - */ -import WcPayUpeContext from './context'; - -const useIsUpeEnabled = () => { - const { isUpeEnabled, setIsUpeEnabled } = useContext( WcPayUpeContext ); - - return [ isUpeEnabled, setIsUpeEnabled ]; -}; - -export default useIsUpeEnabled; diff --git a/client/settings/wcpay-upe-toggle/provider.js b/client/settings/wcpay-upe-toggle/provider.js deleted file mode 100644 index 044ecb2339d..00000000000 --- a/client/settings/wcpay-upe-toggle/provider.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * External dependencies - */ -import { useCallback, useMemo, useState } from 'react'; -import apiFetch from '@wordpress/api-fetch'; -import { useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import WcPayUpeContext from './context'; -import { NAMESPACE, STORE_NAME } from '../../data/constants'; -import { useEnabledPaymentMethodIds } from '../../data'; - -const WcPayUpeContextProvider = ( { - children, - defaultIsUpeEnabled, - defaultUpeType, -} ) => { - const [ isUpeEnabled, setIsUpeEnabled ] = useState( - Boolean( defaultIsUpeEnabled ) - ); - const [ upeType, setUpeType ] = useState( - defaultIsUpeEnabled ? defaultUpeType || 'legacy' : '' - ); - const [ status, setStatus ] = useState( 'resolved' ); - const [ , setEnabledPaymentMethods ] = useEnabledPaymentMethodIds(); - const { updateAvailablePaymentMethodIds } = useDispatch( STORE_NAME ); - - const updateFlag = useCallback( - ( value ) => { - setStatus( 'pending' ); - - return apiFetch( { - path: `${ NAMESPACE }/upe_flag_toggle`, - method: 'POST', - // eslint-disable-next-line camelcase - data: { is_upe_enabled: Boolean( value ) }, - } ) - .then( () => { - // new "toggles" will continue being "split" UPE - setUpeType( value ? 'split' : '' ); - setIsUpeEnabled( Boolean( value ) ); - - // the backend already takes care of this, - // we're just duplicating the effort - // to ensure that the non-UPE payment methods are removed when the flag is disabled - if ( ! value ) { - updateAvailablePaymentMethodIds( [ 'card' ] ); - setEnabledPaymentMethods( [ 'card' ] ); - } - setStatus( 'resolved' ); - } ) - .catch( () => { - setStatus( 'error' ); - } ); - }, - [ - setStatus, - setIsUpeEnabled, - setUpeType, - setEnabledPaymentMethods, - updateAvailablePaymentMethodIds, - ] - ); - - const contextValue = useMemo( - () => ( { - isUpeEnabled, - setIsUpeEnabled: updateFlag, - status, - upeType, - } ), - [ isUpeEnabled, updateFlag, status, upeType ] - ); - - return ( - - { children } - - ); -}; - -export default WcPayUpeContextProvider; diff --git a/client/settings/wcpay-upe-toggle/test/provider.test.js b/client/settings/wcpay-upe-toggle/test/provider.test.js deleted file mode 100644 index 03538b22da7..00000000000 --- a/client/settings/wcpay-upe-toggle/test/provider.test.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * External dependencies - */ -import React, { useEffect } from 'react'; -import { render, waitFor } from '@testing-library/react'; -import apiFetch from '@wordpress/api-fetch'; -import { useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ - -import WcPayUpeContextProvider from '../provider'; -import WcPayUpeContext from '../context'; -import useIsUpeEnabled from '../hook'; -import { useEnabledPaymentMethodIds } from '../../../data'; - -jest.mock( '@wordpress/api-fetch', () => jest.fn() ); -jest.mock( '../../../data', () => ( { - useEnabledPaymentMethodIds: jest.fn(), -} ) ); -jest.mock( '@wordpress/data', () => ( { - useDispatch: jest - .fn() - .mockReturnValue( { updateAvailablePaymentMethodIds: jest.fn() } ), -} ) ); - -describe( 'WcPayUpeContextProvider', () => { - beforeEach( () => { - useEnabledPaymentMethodIds.mockReturnValue( [ [], () => null ] ); - } ); - - afterEach( () => { - jest.clearAllMocks(); - - apiFetch.mockResolvedValue( true ); - } ); - - afterAll( () => { - jest.restoreAllMocks(); - } ); - - it( 'should render the initial state', () => { - const childrenMock = jest.fn().mockReturnValue( null ); - render( - - - { childrenMock } - - - ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: false, - setIsUpeEnabled: expect.any( Function ), - status: 'resolved', - upeType: '', - } ); - expect( apiFetch ).not.toHaveBeenCalled(); - } ); - - it( 'should render the initial state given a default value for isUpeEnabled', () => { - const childrenMock = jest.fn().mockReturnValue( null ); - render( - - - { childrenMock } - - - ); - - expect( childrenMock ).toHaveBeenCalledWith( - expect.objectContaining( { - isUpeEnabled: true, - } ) - ); - expect( apiFetch ).not.toHaveBeenCalled(); - } ); - - it( 'should call the API and resolve when setIsUpeEnabled has been called', async () => { - const childrenMock = jest.fn().mockReturnValue( null ); - const setEnabledPaymentMethodIds = jest.fn(); - useEnabledPaymentMethodIds.mockReturnValue( [ - [ 'card', 'giropay' ], - setEnabledPaymentMethodIds, - ] ); - - const UpdateUpeEnabledFlagMock = () => { - const [ , setIsUpeEnabled ] = useIsUpeEnabled(); - useEffect( () => { - setIsUpeEnabled( true ); - }, [ setIsUpeEnabled ] ); - - return null; - }; - - render( - - - - { childrenMock } - - - ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: false, - setIsUpeEnabled: expect.any( Function ), - status: 'resolved', - upeType: '', - } ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: false, - setIsUpeEnabled: expect.any( Function ), - status: 'pending', - upeType: '', - } ); - - await waitFor( () => - expect( apiFetch ).toHaveBeenCalledWith( { - path: '/wc/v3/payments/upe_flag_toggle', - method: 'POST', - // eslint-disable-next-line camelcase - data: { is_upe_enabled: true }, - } ) - ); - - await waitFor( () => expect( apiFetch ).toHaveReturned() ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: true, - setIsUpeEnabled: expect.any( Function ), - status: 'resolved', - upeType: 'split', - } ); - expect( setEnabledPaymentMethodIds ).not.toHaveBeenCalled(); - } ); - - it( 'should disable non-UPE payment methods when the flag is disabled', async () => { - const childrenMock = jest.fn().mockReturnValue( null ); - const setEnabledPaymentMethodIds = jest.fn(); - useEnabledPaymentMethodIds.mockReturnValue( [ - [ 'card', 'giropay' ], - setEnabledPaymentMethodIds, - ] ); - const updateAvailablePaymentMethodIds = jest.fn(); - useDispatch.mockReturnValue( { updateAvailablePaymentMethodIds } ); - - const UpdateUpeDisabledFlagMock = () => { - const [ , setIsUpeEnabled ] = useIsUpeEnabled(); - useEffect( () => { - setIsUpeEnabled( false ); - }, [ setIsUpeEnabled ] ); - - return null; - }; - - render( - - - - { childrenMock } - - - ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: true, - setIsUpeEnabled: expect.any( Function ), - status: 'resolved', - upeType: 'legacy', - } ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: true, - setIsUpeEnabled: expect.any( Function ), - status: 'pending', - upeType: 'legacy', - } ); - - await waitFor( () => - expect( apiFetch ).toHaveBeenCalledWith( { - path: '/wc/v3/payments/upe_flag_toggle', - method: 'POST', - // eslint-disable-next-line camelcase - data: { is_upe_enabled: false }, - } ) - ); - - await waitFor( () => expect( apiFetch ).toHaveReturned() ); - - expect( childrenMock ).toHaveBeenCalledWith( { - isUpeEnabled: false, - setIsUpeEnabled: expect.any( Function ), - status: 'resolved', - upeType: '', - } ); - expect( setEnabledPaymentMethodIds ).toHaveBeenCalledWith( [ 'card' ] ); - expect( updateAvailablePaymentMethodIds ).toHaveBeenCalledWith( [ - 'card', - ] ); - } ); -} ); diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index c44adb42ac8..153746bd832 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -847,10 +847,6 @@ private function get_js_settings(): array { 'accountEmail' => $this->account->get_account_email(), 'showUpdateDetailsTask' => $this->get_should_show_update_business_details_task( $account_status_data ), 'wpcomReconnectUrl' => $this->payments_api_client->is_server_connected() && ! $this->payments_api_client->has_server_connection_owner() ? WC_Payments_Account::get_wpcom_reconnect_url() : null, - 'additionalMethodsSetup' => [ - 'isUpeEnabled' => WC_Payments_Features::is_upe_enabled(), - 'upeType' => WC_Payments_Features::get_enabled_upe_type(), - ], 'multiCurrencySetup' => [ 'isSetupCompleted' => get_option( 'wcpay_multi_currency_setup_completed' ), ], @@ -921,7 +917,6 @@ private function get_frontend_feature_flags(): array { [ 'paymentTimeline' => self::version_compare( WC_ADMIN_VERSION_NUMBER, '1.4.0', '>=' ), 'customSearch' => self::version_compare( WC_ADMIN_VERSION_NUMBER, '1.3.0', '>=' ), - 'upeType' => WC_Payments_Features::get_enabled_upe_type(), ], WC_Payments_Features::to_array() ); diff --git a/includes/admin/class-wc-rest-upe-flag-toggle-controller.php b/includes/admin/class-wc-rest-upe-flag-toggle-controller.php deleted file mode 100644 index 9977a12d07f..00000000000 --- a/includes/admin/class-wc-rest-upe-flag-toggle-controller.php +++ /dev/null @@ -1,192 +0,0 @@ -wcpay_gateway = $wcpay_gateway; - } - - /** - * Verify access to request. - */ - public function check_permission() { - return current_user_can( 'manage_woocommerce' ); - } - - /** - * Configure REST API routes. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_flag' ], - 'permission_callback' => [ $this, 'check_permission' ], - ] - ); - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - [ - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => [ $this, 'set_flag' ], - 'permission_callback' => [ $this, 'check_permission' ], - 'args' => [ - 'is_upe_enabled' => [ - 'description' => __( 'Determines if the UPE feature flag is enabled.', 'woocommerce-payments' ), - 'type' => 'boolean', - 'validate_callback' => 'rest_validate_request_arg', - ], - ], - ] - ); - } - - /** - * Retrieve flag status. - * - * @return WP_REST_Response - */ - public function get_flag(): WP_REST_Response { - return new WP_REST_Response( - [ - 'is_upe_enabled' => WC_Payments_Features::is_upe_enabled(), - ] - ); - } - - /** - * Update the data. - * - * @param WP_REST_Request $request Full data about the request. - */ - public function set_flag( WP_REST_Request $request ) { - $this->update_is_upe_enabled( $request ); - - return new WP_REST_Response( [], 200 ); - } - - /** - * Update UPE feature flag enabled status. - * - * @param WP_REST_Request $request Request object. - */ - private function update_is_upe_enabled( WP_REST_Request $request ) { - if ( ! $request->has_param( 'is_upe_enabled' ) ) { - return; - } - - $is_upe_enabled = $request->get_param( 'is_upe_enabled' ); - - if ( $is_upe_enabled ) { - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '1' ); - - // WooCommerce core only includes Tracks in admin, not the REST API, so we need to use this wc_admin method - // that includes WC_Tracks in case it's not loaded. - if ( function_exists( 'wc_admin_record_tracks_event' ) ) { - wc_admin_record_tracks_event( - Track_Events::DEFERRED_INTENT_UPE_ENABLED, - [ - 'is_bnpl_affirm_afterpay_enabled' => WC_Payments_Features::is_bnpl_affirm_afterpay_enabled(), - ] - ); - } - - return; - } - - // If the legacy UPE flag has been enabled, we need to disable the legacy UPE flag. - if ( '1' === get_option( WC_Payments_Features::UPE_FLAG_NAME ) ) { - update_option( WC_Payments_Features::UPE_FLAG_NAME, 'disabled' ); - - if ( function_exists( 'wc_admin_record_tracks_event' ) ) { - wc_admin_record_tracks_event( - Track_Events::UPE_DISABLED, - [ - 'is_bnpl_affirm_afterpay_enabled' => WC_Payments_Features::is_bnpl_affirm_afterpay_enabled(), - ] - ); - } - } elseif ( '1' === get_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME ) ) { - // marking the flag as "disabled", so that we can keep track that the merchant explicitly disabled split UPE. - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, 'disabled' ); - - if ( function_exists( 'wc_admin_record_tracks_event' ) ) { - wc_admin_record_tracks_event( - Track_Events::SPLIT_UPE_DISABLED, - [ - 'is_bnpl_affirm_afterpay_enabled' => WC_Payments_Features::is_bnpl_affirm_afterpay_enabled(), - ] - ); - } - } elseif ( '1' === get_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME ) ) { - // marking the flag as "disabled", so that we can keep track that the merchant explicitly disabled deferred intent UPE. - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, 'disabled' ); - - if ( function_exists( 'wc_admin_record_tracks_event' ) ) { - wc_admin_record_tracks_event( - Track_Events::DEFERRED_INTENT_UPE_DISABLED, - [ - 'is_bnpl_affirm_afterpay_enabled' => WC_Payments_Features::is_bnpl_affirm_afterpay_enabled(), - ] - ); - } - } - - // resetting a few other things: - // removing the UPE payment methods and _only_ leaving "card", - // just in case the user added additional payment method types. - $this->wcpay_gateway->update_option( - 'upe_enabled_payment_method_ids', - [ - 'card', - ] - ); - - // removing the note from the database. - if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-additional-payment-methods.php'; - WC_Payments_Notes_Additional_Payment_Methods::possibly_delete_note(); - } - } -} diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 29ec2f85bbc..18198308323 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -157,9 +157,6 @@ public function get_payment_fields_js_config() { 'forceNetworkSavedCards' => WC_Payments::is_network_saved_cards_enabled() || $gateway->should_use_stripe_platform_on_checkout_page(), 'locale' => WC_Payments_Utils::convert_to_stripe_locale( get_locale() ), 'isPreview' => is_preview(), - 'isUPEEnabled' => WC_Payments_Features::is_upe_enabled(), - 'isUPESplitEnabled' => false, - 'isUPEDeferredEnabled' => true, 'isSavedCardsEnabled' => $this->gateway->is_saved_cards_enabled(), 'isWooPayEnabled' => $this->woopay_util->should_enable_woopay( $this->gateway ) && $this->woopay_util->should_enable_woopay_on_cart_or_checkout(), 'isWoopayExpressCheckoutEnabled' => $this->woopay_util->is_woopay_express_checkout_enabled(), diff --git a/includes/class-wc-payments-features.php b/includes/class-wc-payments-features.php index ef9ae19c263..416cac38831 100644 --- a/includes/class-wc-payments-features.php +++ b/includes/class-wc-payments-features.php @@ -13,9 +13,6 @@ * WC Payments Features class */ class WC_Payments_Features { - const UPE_FLAG_NAME = '_wcpay_feature_upe'; - const UPE_SPLIT_FLAG_NAME = '_wcpay_feature_upe_split'; - const UPE_DEFERRED_INTENT_FLAG_NAME = '_wcpay_feature_upe_deferred_intent'; const WCPAY_SUBSCRIPTIONS_FLAG_NAME = '_wcpay_feature_subscriptions'; const STRIPE_BILLING_FLAG_NAME = '_wcpay_feature_stripe_billing'; const WOOPAY_EXPRESS_CHECKOUT_FLAG_NAME = '_wcpay_feature_woopay_express_checkout'; @@ -25,42 +22,6 @@ class WC_Payments_Features { const DISPUTE_ISSUER_EVIDENCE = '_wcpay_feature_dispute_issuer_evidence'; const STREAMLINE_REFUNDS_FLAG_NAME = '_wcpay_feature_streamline_refunds'; - /** - * Checks whether any UPE gateway is enabled. - * - * @return bool - */ - public static function is_upe_enabled() { - return true; - } - - /** - * Returns the "type" of UPE that will be displayed at checkout. - * - * @return string - */ - public static function get_enabled_upe_type() { - return 'deferred_intent'; - } - - /** - * Checks whether the UPE gateway is enabled - * - * @return bool - */ - public static function did_merchant_disable_upe() { - return 'disabled' === get_option( self::UPE_FLAG_NAME, '0' ) || 'disabled' === get_option( self::UPE_SPLIT_FLAG_NAME, '0' ); - } - - /** - * Checks whether the UPE settings redesign is enabled - * - * @return bool - */ - public static function is_upe_settings_preview_enabled() { - return '1' === get_option( '_wcpay_feature_upe_settings_preview', '1' ); - } - /** * Indicates whether card payments are enabled for this (Stripe) account. * @@ -398,10 +359,6 @@ public static function is_dispute_issuer_evidence_enabled(): bool { public static function to_array() { return array_filter( [ - 'upe' => self::is_upe_enabled(), - 'upeSplit' => false, - 'upeDeferred' => true, - 'upeSettingsPreview' => self::is_upe_settings_preview_enabled(), 'multiCurrency' => self::is_customer_multi_currency_enabled(), 'woopay' => self::is_woopay_eligible(), 'documents' => self::is_documents_section_enabled(), diff --git a/includes/class-wc-payments-status.php b/includes/class-wc-payments-status.php index fa91edc01f0..6f3caf46c14 100644 --- a/includes/class-wc-payments-status.php +++ b/includes/class-wc-payments-status.php @@ -139,17 +139,10 @@ public function render_status_report_section() { ?> is_test() ? esc_html_e( 'Enabled', 'woocommerce-payments' ) : esc_html_e( 'Disabled', 'woocommerce-payments' ); ?> - : - - - - - - : - + : + gateway->get_upe_enabled_payment_method_ids() ) ); ?> - diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 0d9137ec4a2..69e684e4343 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -591,12 +591,12 @@ public static function init() { require_once __DIR__ . '/migrations/class-allowed-payment-request-button-types-update.php'; require_once __DIR__ . '/migrations/class-allowed-payment-request-button-sizes-update.php'; require_once __DIR__ . '/migrations/class-update-service-data-from-server.php'; - require_once __DIR__ . '/migrations/class-track-upe-status.php'; + require_once __DIR__ . '/migrations/class-additional-payment-methods-admin-notes-removal.php'; require_once __DIR__ . '/migrations/class-delete-active-woopay-webhook.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' ] ); - add_action( 'woocommerce_woocommerce_payments_updated', [ '\WCPay\Migrations\Track_Upe_Status', 'maybe_track' ] ); + add_action( 'woocommerce_woocommerce_payments_updated', [ new \WCPay\Migrations\Additional_Payment_Methods_Admin_Notes_Removal(), 'maybe_migrate' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ '\WCPay\Migrations\Delete_Active_WooPay_Webhook', 'maybe_delete' ] ); include_once WCPAY_ABSPATH . '/includes/class-wc-payments-explicit-price-formatter.php'; @@ -1041,15 +1041,9 @@ public static function init_rest_api() { $customer_controller = new WC_REST_Payments_Customer_Controller( self::$api_client, self::$customer_service ); $customer_controller->register_routes(); - if ( WC_Payments_Features::is_upe_settings_preview_enabled() ) { - include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-upe-flag-toggle-controller.php'; - $upe_flag_toggle_controller = new WC_REST_UPE_Flag_Toggle_Controller( self::get_gateway() ); - $upe_flag_toggle_controller->register_routes(); - - include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-survey-controller.php'; - $survey_controller = new WC_REST_Payments_Survey_Controller( self::get_wc_payments_http() ); - $survey_controller->register_routes(); - } + include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-survey-controller.php'; + $survey_controller = new WC_REST_Payments_Survey_Controller( self::get_wc_payments_http() ); + $survey_controller->register_routes(); if ( WC_Payments_Features::is_documents_section_enabled() ) { include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-documents-controller.php'; @@ -1368,11 +1362,6 @@ public static function add_woo_admin_notes() { require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-https-for-checkout.php'; WC_Payments_Notes_Set_Https_For_Checkout::possibly_add_note(); - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-additional-payment-methods.php'; - WC_Payments_Notes_Additional_Payment_Methods::set_account( self::get_account_service() ); - WC_Payments_Notes_Additional_Payment_Methods::possibly_add_note(); - WC_Payments_Notes_Additional_Payment_Methods::maybe_enable_upe_feature_flag(); - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-up-stripelink.php'; WC_Payments_Notes_Set_Up_StripeLink::set_gateway( self::get_gateway() ); WC_Payments_Notes_Set_Up_StripeLink::possibly_add_note(); @@ -1425,9 +1414,6 @@ public static function remove_woo_admin_notes() { require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-instant-deposits-eligible.php'; WC_Payments_Notes_Instant_Deposits_Eligible::possibly_delete_note(); - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-additional-payment-methods.php'; - WC_Payments_Notes_Additional_Payment_Methods::possibly_delete_note(); - require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-set-up-stripelink.php'; WC_Payments_Notes_Set_Up_StripeLink::possibly_delete_note(); } diff --git a/includes/migrations/class-additional-payment-methods-admin-notes-removal.php b/includes/migrations/class-additional-payment-methods-admin-notes-removal.php new file mode 100644 index 00000000000..9d85abee74b --- /dev/null +++ b/includes/migrations/class-additional-payment-methods-admin-notes-removal.php @@ -0,0 +1,48 @@ +' ) ) { + $this->migrate(); + } + } + + /** + * Does the actual migration as described in the class docblock. + */ + private function migrate() { + // Delete UPE admin notes as UPE is the default now. This is part of the migration and should be removed in the future. + require_once WCPAY_ABSPATH . 'includes/notes/class-wc-payments-notes-additional-payment-methods.php'; + WC_Payments_Notes_Additional_Payment_Methods::possibly_delete_note(); + } +} diff --git a/includes/migrations/class-track-upe-status.php b/includes/migrations/class-track-upe-status.php deleted file mode 100644 index b519cef583c..00000000000 --- a/includes/migrations/class-track-upe-status.php +++ /dev/null @@ -1,61 +0,0 @@ -gateway->get_upe_enabled_payment_method_ids(); $payment_methods_needing_currency = array_reduce( $enabled_payment_method_ids, diff --git a/includes/notes/class-wc-payments-notes-additional-payment-methods.php b/includes/notes/class-wc-payments-notes-additional-payment-methods.php index 95c0b6635b7..7b309f840bf 100644 --- a/includes/notes/class-wc-payments-notes-additional-payment-methods.php +++ b/includes/notes/class-wc-payments-notes-additional-payment-methods.php @@ -6,7 +6,6 @@ * @package WooCommerce\Payments\Admin */ -use Automattic\WooCommerce\Admin\Notes\Note; use Automattic\WooCommerce\Admin\Notes\NoteTraits; defined( 'ABSPATH' ) || exit; @@ -22,137 +21,11 @@ class WC_Payments_Notes_Additional_Payment_Methods { */ const NOTE_NAME = 'wc-payments-notes-additional-payment-methods'; - /** - * Nonce action name - */ - const NOTE_ACTION = 'enable-upe'; - - /** - * The account service instance. - * - * @var WC_Payments_Account - */ - private static $account; - /** * Get the note. */ public static function get_note() { - // Show this notice only if UPE settings preview is disabled, and UPE flag is not enabled. - if ( false === WC_Payments_Features::is_upe_settings_preview_enabled() ) { - return; - } - - if ( WC_Payments_Features::is_upe_enabled() ) { - return; - } - - if ( WC_Payments_Features::did_merchant_disable_upe() ) { - return; - } - - if ( self::$account instanceof WC_Payments_Account ) { - // if the user hasn't connected their account, do not add the note. - if ( ! self::$account->is_stripe_connected() ) { - return; - } - - // If the account hasn't completed intitial Stripe onboarding, do not add the note. - if ( self::$account->is_account_partially_onboarded() ) { - return; - } - - // If this is a PO account which has not yet completed full onboarding, do not add the note. - if ( self::$account->is_progressive_onboarding_in_progress() ) { - return; - } - } - - $note = new Note(); - - $note->set_title( __( 'Boost your sales by accepting new payment methods', 'woocommerce-payments' ) ); - $note->set_content( - WC_Payments_Utils::esc_interpolated_html( - sprintf( - /* translators: %s: WooPayments */ - __( 'Get early access to additional payment methods and an improved checkout experience, coming soon to %s. Learn more', 'woocommerce-payments' ), - 'WooPayments' - ), - [ - 'a' => '', - ] - ) - ); - $note->set_content_data( (object) [] ); - $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); - $note->set_name( self::NOTE_NAME ); - $note->set_source( 'woocommerce-payments' ); - $note->add_action( - self::NOTE_NAME, - __( 'Enable on your store', 'woocommerce-payments' ), - admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=woocommerce_payments&action=' . self::NOTE_ACTION ), - Note::E_WC_ADMIN_NOTE_UNACTIONED, - true - ); - - return $note; - } - - /** - * Enable UPE feature flag. CTA from Admin Notes. See WC_Payments_Notes_Additional_Payment_Methods. - */ - public static function maybe_enable_upe_feature_flag() { - /** - * TODO: Add nonce to verify https://github.com/woocommerce/woocommerce-admin/pull/6726/ - * requires WC requirement is > 5.4. - */ - if ( - ! current_user_can( 'manage_woocommerce' ) || - empty( $_GET['page'] ) || // phpcs:disable WordPress.Security.NonceVerification.Recommended - 'wc-settings' !== $_GET['page'] || // phpcs:disable WordPress.Security.NonceVerification.Recommended - empty( $_GET['action'] ) || // phpcs:disable WordPress.Security.NonceVerification.Recommended - 'enable-upe' !== $_GET['action'] // phpcs:disable WordPress.Security.NonceVerification.Recommended - ) { - return; - } - - // once we get to this point, it means we should enable UPE, - // but we also need to check whether the account is connected or not. - - // if we get to this point and the account is not connected, - // it means that the note has been displayed at some point, but the account got disconnected afterwards. - // in that case, let's redirect the user to the account connection page. - if ( self::$account instanceof WC_Payments_Account ) { - if ( false === self::$account->is_stripe_connected() ) { - // account is not connected, redirecting to connection page. - self::$account->redirect_to_onboarding_welcome_page( __( 'We detected a temporary issue with your account. Please try and connect your Stripe account.', 'woocommerce-payments' ) ); - - return; - } - } - - // Track enabling UPE if it wasn't enabled before. - if ( ! WC_Payments_Features::is_upe_enabled() && class_exists( 'WC_Tracks' ) ) { - // We're not using Tracker::track_admin() here because - // WC_Pay\record_tracker_events() is never triggered due to the redirect below. - WC_Tracks::record_event( 'wcpay_split_upe_enabled' ); - } - - // Enable UPE, deletes the note and redirect to onboarding task. - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '1' ); - self::possibly_delete_note(); - - $wcpay_settings_url = admin_url( 'admin.php?page=wc-admin&path=/payments/additional-payment-methods' ); - wp_safe_redirect( $wcpay_settings_url ); - exit; - } - - /** - * Sets the account service instance reference on the class. - * - * @param WC_Payments_Account $account account service instance. - */ - public static function set_account( WC_Payments_Account $account ) { - self::$account = $account; + // The notice should not be shown anymore because UPE is the default now. + return false; } } diff --git a/includes/notes/class-wc-payments-notes-loan-approved.php b/includes/notes/class-wc-payments-notes-loan-approved.php index 5af554d9c21..6f16a43fd16 100644 --- a/includes/notes/class-wc-payments-notes-loan-approved.php +++ b/includes/notes/class-wc-payments-notes-loan-approved.php @@ -13,7 +13,7 @@ defined( 'ABSPATH' ) || exit; /** - * Class WC_Payments_Notes_Additional_Payment_Methods + * Class WC_Payments_Notes_Loan_Approved */ class WC_Payments_Notes_Loan_Approved { use NoteTraits; diff --git a/includes/notes/class-wc-payments-notes-set-up-stripelink.php b/includes/notes/class-wc-payments-notes-set-up-stripelink.php index 2a4c933e0e7..54956d85cec 100644 --- a/includes/notes/class-wc-payments-notes-set-up-stripelink.php +++ b/includes/notes/class-wc-payments-notes-set-up-stripelink.php @@ -42,11 +42,6 @@ class WC_Payments_Notes_Set_Up_StripeLink { * @return bool */ public static function should_display_note():bool { - // If UPE is not enabled, skip. - if ( ! \WC_Payments_Features::is_upe_enabled() ) { - return false; - } - // Check if Link payment is available. $available_upe_payment_methods = self::$gateway->get_upe_available_payment_methods(); if ( ! in_array( Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID, $available_upe_payment_methods, true ) ) { diff --git a/tests/e2e/specs/performance/payment-methods.spec.js b/tests/e2e/specs/performance/payment-methods.spec.js index 63425346c5a..470fff34f5c 100644 --- a/tests/e2e/specs/performance/payment-methods.spec.js +++ b/tests/e2e/specs/performance/payment-methods.spec.js @@ -47,11 +47,6 @@ describe( 'Checkout page performance', () => { describe( 'UPE', () => { beforeEach( async () => { - // Activate UPE - await merchant.login(); - await merchantWCP.activateUpe(); - await merchant.logout(); - // Setup cart await setupProductCheckout( config.get( 'addresses.customer.billing' ) @@ -61,11 +56,6 @@ describe( 'Checkout page performance', () => { afterEach( async () => { // Clear the cart at the end so it's ready for another test await shopperWCP.emptyCart(); - - // Deactivate UPE - await merchant.login(); - await merchantWCP.deactivateUpe(); - await merchant.logout(); } ); it( 'measures averaged page load metrics', async () => { diff --git a/tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js b/tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js index 3cb6a37c02a..67d1a98a142 100644 --- a/tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js +++ b/tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js @@ -20,7 +20,6 @@ const UPE_METHOD_CHECKBOXES = [ describe( 'BNPL checkout', () => { beforeAll( async () => { await merchant.login(); - await merchantWCP.activateUPEWithDefferedIntentCreation(); await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); await merchant.logout(); await shopper.login(); @@ -31,7 +30,6 @@ describe( 'BNPL checkout', () => { await shopperWCP.logout(); await merchant.login(); await merchantWCP.disablePaymentMethod( UPE_METHOD_CHECKBOXES ); - await merchantWCP.deactivateUPEWithDefferedIntentCreation(); await merchant.logout(); } ); diff --git a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js b/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js index 21c1c471815..1e9a6a27956 100644 --- a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js +++ b/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js @@ -30,7 +30,6 @@ const MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS = 20000; describe( 'Enabled UPE with deferred intent creation', () => { beforeAll( async () => { await merchant.login(); - await merchantWCP.activateUPEWithDefferedIntentCreation(); await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); await merchant.logout(); await shopper.login(); @@ -42,7 +41,6 @@ describe( 'Enabled UPE with deferred intent creation', () => { await shopperWCP.logout(); await merchant.login(); await merchantWCP.disablePaymentMethod( UPE_METHOD_CHECKBOXES ); - await merchantWCP.deactivateUPEWithDefferedIntentCreation(); await merchant.logout(); } ); diff --git a/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js b/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js index 75a228753fa..78642474eb1 100644 --- a/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js +++ b/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js @@ -23,7 +23,6 @@ const card = config.get( 'cards.basic' ); describe( 'Enabled Split UPE', () => { beforeAll( async () => { await merchant.login(); - await merchantWCP.activateSplitUpe(); await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); await merchant.logout(); await shopper.login(); @@ -35,7 +34,6 @@ describe( 'Enabled Split UPE', () => { await shopperWCP.logout(); await merchant.login(); await merchantWCP.disablePaymentMethod( UPE_METHOD_CHECKBOXES ); - await merchantWCP.deactivateSplitUpe(); await merchant.logout(); } ); diff --git a/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js b/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js index 6c5a175d8a2..c5ba0caa81e 100644 --- a/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js +++ b/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js @@ -18,7 +18,6 @@ const card = config.get( 'cards.basic' ); describe( 'Enabled UPE', () => { beforeAll( async () => { await merchant.login(); - await merchantWCP.activateUpe(); // enable SEPA await merchantWCP.enablePaymentMethod( [ sepaPaymentMethod ] ); await merchant.logout(); @@ -32,7 +31,6 @@ describe( 'Enabled UPE', () => { await merchant.login(); //disable SEPA await merchantWCP.disablePaymentMethod( [ sepaPaymentMethod ] ); - await merchantWCP.deactivateUpe(); await merchant.logout(); } ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-payment-settings-manual-capture.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-payment-settings-manual-capture.spec.js index 3349419f89e..54b622569cd 100644 --- a/tests/e2e/specs/wcpay/merchant/merchant-payment-settings-manual-capture.spec.js +++ b/tests/e2e/specs/wcpay/merchant/merchant-payment-settings-manual-capture.spec.js @@ -12,6 +12,8 @@ const confirmationModalClass = '.wcpay-confirmation-modal'; describe( 'As a merchant, I should be prompted a confirmation modal when I try to activate the manual capture', () => { beforeAll( async () => { await merchant.login(); + await merchantWCP.openWCPSettings(); + await merchantWCP.skipFraudProtectionTour(); } ); afterAll( async () => { @@ -19,13 +21,10 @@ describe( 'As a merchant, I should be prompted a confirmation modal when I try t } ); afterEach( async () => { - await merchantWCP.deactivateUpe(); + await merchantWCP.unsetCheckboxByTestId( checkboxCaptureLaterOption ); } ); - it( 'should show the confirmation dialog when enabling the manual capture while UPE is activated', async () => { - await merchantWCP.activateUpe(); - await merchantWCP.openWCPSettings(); - await merchantWCP.skipFraudProtectionTour(); + it( 'should show the confirmation dialog when enabling the manual capture', async () => { await merchantWCP.setCheckboxByTestId( checkboxCaptureLaterOption ); const confirmationModal = await expect( page ).toMatchElement( confirmationModalClass @@ -35,10 +34,7 @@ describe( 'As a merchant, I should be prompted a confirmation modal when I try t ); } ); - it( 'should not show the confirmation dialog when disabling the manual capture while UPE is activated', async () => { - await merchantWCP.activateUpe(); - await merchantWCP.openWCPSettings(); - await merchantWCP.skipFraudProtectionTour(); + it( 'should not show the confirmation dialog when disabling the manual capture', async () => { await merchantWCP.setCheckboxByTestId( checkboxCaptureLaterOption ); const confirmationModal = await expect( page ).toMatchElement( confirmationModalClass @@ -51,10 +47,7 @@ describe( 'As a merchant, I should be prompted a confirmation modal when I try t await expect( page ).not.toMatchElement( '.wcpay-confirmation-modal' ); } ); - it( 'should show the UPE methods disabled when manual capture is enabled', async () => { - await merchantWCP.activateUpe(); - await merchantWCP.openWCPSettings(); - await merchantWCP.skipFraudProtectionTour(); + it( 'should show the non-card methods disabled when manual capture is enabled', async () => { await merchantWCP.setCheckboxByTestId( checkboxCaptureLaterOption ); const confirmationModal = await expect( page ).toMatchElement( confirmationModalClass diff --git a/tests/e2e/specs/wcpay/shopper/shopper-checkout-free-cart.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-checkout-free-cart.spec.js index c6a186ba696..9d8497888ac 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-checkout-free-cart.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-checkout-free-cart.spec.js @@ -6,11 +6,10 @@ import config from 'config'; * Internal dependencies */ import { fillCardDetails, setupCheckout } from '../../../utils/payments'; -import { merchantWCP, shopperWCP } from '../../../utils'; +import { shopperWCP } from '../../../utils'; const { shopper, - merchant, clearAndFillInput, uiUnblocked, } = require( '@woocommerce/e2e-utils' ); @@ -59,12 +58,6 @@ describe( 'Checkout with free coupon & after modifying cart on Checkout page', ( } ); describe( 'UPE', () => { - beforeAll( async () => { - await merchant.login(); - await merchantWCP.activateSplitUpe(); - await merchant.logout(); - } ); - beforeEach( async () => { await shopper.goToShop(); await shopper.addToCartFromShopPage( productName ); @@ -83,11 +76,5 @@ describe( 'Checkout with free coupon & after modifying cart on Checkout page', ( await shopper.placeOrder(); await expect( page ).toMatch( 'Order received' ); } ); - - afterAll( async () => { - await merchant.login(); - await merchantWCP.deactivateSplitUpe(); - await merchant.logout(); - } ); } ); } ); diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js index 50a0b294a75..0a2d8a8e7f8 100644 --- a/tests/e2e/utils/flows.js +++ b/tests/e2e/utils/flows.js @@ -290,192 +290,6 @@ export const shopperWCP = { // The generic flows will be moved to their own package soon (more details in p7bje6-2gV-p2), so we're // keeping our customizations grouped here so it's easier to extend the flows once the move happens. export const merchantWCP = { - activateUpe: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - if ( ! ( await page.$( '#_wcpay_feature_upe:checked' ) ) ) { - await expect( page ).toClick( 'label[for="_wcpay_feature_upe"]' ); - } - - const isSplitUPEEnabled = await page.$( - '#_wcpay_feature_upe_split:checked' - ); - - if ( isSplitUPEEnabled ) { - // Deactivate the Split UPE checkout. - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_split"]' - ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( ! isAdditionalPaymentsActive ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_additional_payment_methods"]' - ); - } - - await expect( page ).toClick( 'input#submit' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - - activateSplitUpe: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - if ( ! ( await page.$( '#_wcpay_feature_upe_split:checked' ) ) ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_split"]' - ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( ! isAdditionalPaymentsActive ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_additional_payment_methods"]' - ); - } - - await expect( page ).toClick( 'input#submit' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - - activateUPEWithDefferedIntentCreation: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - // uncheck UPE - if ( await page.$( '#_wcpay_feature_upe:checked' ) ) { - await expect( page ).toClick( 'label', { - text: 'Enable UPE checkout (legacy)', - } ); - } - - // uncheck split UPE - if ( await page.$( '#_wcpay_feature_upe_split:checked' ) ) { - await expect( page ).toClick( 'label', { - text: 'Enable Split UPE checkout', - } ); - } - - // check enhanced UPE - if ( - ! ( await page.$( '#_wcpay_feature_upe_deferred_intent:checked' ) ) - ) { - await expect( page ).toClick( 'label', { - text: 'Enable Split UPE checkout with deferred intent creation', - } ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( ! isAdditionalPaymentsActive ) { - await expect( page ).toClick( 'label', { - text: 'Add UPE additional payment methods', - } ); - } - - await expect( page ).toClick( 'input[type="submit"]' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - - deactivateUPEWithDefferedIntentCreation: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - if ( await page.$( '#_wcpay_feature_upe_deferred_intent:checked' ) ) { - await expect( page ).toClick( 'label', { - text: 'Enable Split UPE checkout with deferred intent creation', - } ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( isAdditionalPaymentsActive ) { - await expect( page ).toClick( 'label', { - text: 'Add UPE additional payment methods', - } ); - } - - await expect( page ).toClick( 'input[type="submit"]' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - - deactivateUpe: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - if ( await page.$( '#_wcpay_feature_upe:checked' ) ) { - await expect( page ).toClick( 'label[for="_wcpay_feature_upe"]' ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( isAdditionalPaymentsActive ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_additional_payment_methods"]' - ); - } - - await expect( page ).toClick( 'input#submit' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - - deactivateSplitUpe: async () => { - await page.goto( WCPAY_DEV_TOOLS, { - waitUntil: 'networkidle0', - } ); - - if ( await page.$( '#_wcpay_feature_upe_split:checked' ) ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_split"]' - ); - } - - const isAdditionalPaymentsActive = await page.$( - '#_wcpay_feature_upe_additional_payment_methods:checked' - ); - - if ( isAdditionalPaymentsActive ) { - await expect( page ).toClick( - 'label[for="_wcpay_feature_upe_additional_payment_methods"]' - ); - } - - await expect( page ).toClick( 'input#submit' ); - await page.waitForNavigation( { - waitUntil: 'networkidle0', - } ); - }, - enableActAsDisconnectedFromWCPay: async () => { await page.goto( WCPAY_DEV_TOOLS, { waitUntil: 'networkidle0', diff --git a/tests/unit/admin/test-class-wc-payments-admin.php b/tests/unit/admin/test-class-wc-payments-admin.php index ea7aaaed164..65b75d854fe 100644 --- a/tests/unit/admin/test-class-wc-payments-admin.php +++ b/tests/unit/admin/test-class-wc-payments-admin.php @@ -145,9 +145,6 @@ public function test_it_does_not_render_settings_badge( $is_upe_settings_preview $this->mock_current_user_is_admin(); - update_option( '_wcpay_feature_upe_settings_preview', $is_upe_settings_preview_enabled ? '1' : '0' ); - update_option( '_wcpay_feature_upe', $is_upe_enabled ? '1' : '0' ); - // Make sure we render the menu with submenu items. $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); 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 4d42756e1ef..3f109547442 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 @@ -240,9 +240,6 @@ public function set_up() { public function tear_down() { parent::tear_down(); - update_option( WC_Payments_Features::UPE_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); WC_Blocks_REST_API_Registration_Preventer::stop_preventing(); } diff --git a/tests/unit/admin/test-class-wc-rest-upe-flag-toggle-controller.php b/tests/unit/admin/test-class-wc-rest-upe-flag-toggle-controller.php deleted file mode 100644 index cc5b40ce560..00000000000 --- a/tests/unit/admin/test-class-wc-rest-upe-flag-toggle-controller.php +++ /dev/null @@ -1,193 +0,0 @@ -getMockBuilder( WC_Payments_API_Client::class ) - ->disableOriginalConstructor() - ->getMock(); - - $mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); - $this->mock_db_cache = $this->createMock( Database_Cache::class ); - $mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); - $customer_service = new WC_Payments_Customer_Service( $mock_api_client, $mock_wcpay_account, $this->mock_db_cache, $mock_session_service ); - $token_service = new WC_Payments_Token_Service( $mock_api_client, $customer_service ); - $order_service = new WC_Payments_Order_Service( $mock_api_client ); - $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $mock_api_client, $order_service ); - $rate_limiter = new Session_Rate_Limiter( 'wcpay_card_declined_registry', 5, 60 ); - $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); - - $this->gateway = new WC_Payment_Gateway_WCPay( - $mock_api_client, - $mock_wcpay_account, - $customer_service, - $token_service, - $action_scheduler_service, - $rate_limiter, - $order_service, - $mock_dpps, - $this->createMock( WC_Payments_Localization_Service::class ), - $mock_fraud_service - ); - $this->controller = new WC_REST_UPE_Flag_Toggle_Controller( $this->gateway ); - update_option( WC_Payments_Features::UPE_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); - } - - public function tear_down() { - parent::tear_down(); - update_option( WC_Payments_Features::UPE_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '0' ); - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); - } - - public function test_get_flag_fails_if_user_cannot_manage_woocommerce() { - // Set the user so that we can pass the authentication. - wp_set_current_user( 1 ); - - $cb = $this->create_can_manage_woocommerce_cap_override( false ); - add_filter( 'user_has_cap', $cb ); - $response = rest_do_request( new WP_REST_Request( 'GET', self::ROUTE ) ); - $this->assertEquals( 403, $response->get_status() ); - remove_filter( 'user_has_cap', $cb ); - - $cb = $this->create_can_manage_woocommerce_cap_override( true ); - add_filter( 'user_has_cap', $cb ); - $response = rest_do_request( new WP_REST_Request( 'GET', self::ROUTE ) ); - $this->assertEquals( 200, $response->get_status() ); - remove_filter( 'user_has_cap', $cb ); - } - - public function test_get_flag_request_returns_status_code_200() { - $response = $this->controller->get_flag(); - - $this->assertEquals( 200, $response->get_status() ); - } - - public function test_set_flag_without_param_returns_status_code_200() { - $request = new WP_REST_Request( 'POST', self::ROUTE ); - $response = $this->controller->set_flag( $request ); - - $this->assertEquals( 200, $response->get_status() ); - // no change from the initial flag value. - $this->assertEquals( '0', get_option( WC_Payments_Features::UPE_FLAG_NAME ) ); - $this->assertEquals( '0', get_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME ) ); - $this->assertEquals( '0', get_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME ) ); - } - - public function test_set_flag_disabled_with_split_returns_status_code_200() { - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '1' ); - $request = new WP_REST_Request( 'POST', self::ROUTE ); - $request->set_param( 'is_upe_enabled', false ); - - $response = $this->controller->set_flag( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( '0', get_option( WC_Payments_Features::UPE_FLAG_NAME ) ); - $this->assertEquals( 'disabled', get_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME ) ); - } - - public function test_set_flag_disabled_with_deferred_intent_returns_status_code_200() { - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '1' ); - $request = new WP_REST_Request( 'POST', self::ROUTE ); - $request->set_param( 'is_upe_enabled', false ); - - $response = $this->controller->set_flag( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( '0', get_option( WC_Payments_Features::UPE_FLAG_NAME ) ); - $this->assertEquals( 'disabled', get_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME ) ); - } - - public function test_set_flag_enabled_request_returns_status_code_200() { - $request = new WP_REST_Request( 'POST', self::ROUTE ); - $request->set_param( 'is_upe_enabled', true ); - - $response = $this->controller->set_flag( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( '1', get_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME ) ); - } - - public function test_set_flag_disabled_request_returns_status_code_200() { - $this->gateway->update_option( - 'upe_enabled_payment_method_ids', - [ - 'card', - 'giropay', - ] - ); - update_option( WC_Payments_Features::UPE_FLAG_NAME, '1' ); - $this->assertEquals( '1', get_option( WC_Payments_Features::UPE_FLAG_NAME ) ); - - $request = new WP_REST_Request( 'POST', self::ROUTE ); - $request->set_param( 'is_upe_enabled', false ); - - $response = $this->controller->set_flag( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 'disabled', get_option( WC_Payments_Features::UPE_FLAG_NAME, null ) ); - $this->assertEquals( - [ - 'card', - ], - $this->gateway->get_option( - 'upe_enabled_payment_method_ids' - ) - ); - } - - /** - * @param bool $can_manage_woocommerce - * - * @return Closure - */ - private function create_can_manage_woocommerce_cap_override( bool $can_manage_woocommerce ) { - return function ( $allcaps ) use ( $can_manage_woocommerce ) { - $allcaps['manage_woocommerce'] = $can_manage_woocommerce; - - return $allcaps; - }; - } -} diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 6feb10417ce..0188cfb4f93 100755 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -87,10 +87,8 @@ function() { require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-terminal-locations-controller.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-tos-controller.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-settings-controller.php'; - require_once $_plugin_dir . 'includes/admin/class-wc-rest-upe-flag-toggle-controller.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-survey-controller.php'; require_once $_plugin_dir . 'includes/admin/tracks/class-tracker.php'; - require_once $_plugin_dir . 'includes/notes/class-wc-payments-notes-additional-payment-methods.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-reader-controller.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-files-controller.php'; require_once $_plugin_dir . 'includes/reports/class-wc-rest-payments-reports-transactions-controller.php'; diff --git a/tests/unit/migrations/test-class-track-upe-status.php b/tests/unit/migrations/test-class-track-upe-status.php deleted file mode 100644 index 6fbf4817162..00000000000 --- a/tests/unit/migrations/test-class-track-upe-status.php +++ /dev/null @@ -1,77 +0,0 @@ -assertEquals( - [ - 'wcpay_upe_enabled' => [], - ], - Tracker::get_admin_events() - ); - - $this->assertSame( '1', get_option( Track_Upe_Status::IS_TRACKED_OPTION ) ); - } - - public function test_do_nothing_default_on_upgrade() { - Track_Upe_Status::maybe_track(); - - $this->assertEquals( [], Tracker::get_admin_events() ); - - $this->assertSame( '1', get_option( Track_Upe_Status::IS_TRACKED_OPTION ) ); - } - - public function test_do_nothing_when_tracked() { - update_option( Track_Upe_Status::IS_TRACKED_OPTION, '1' ); - - Track_Upe_Status::maybe_track(); - - $this->assertEquals( [], Tracker::get_admin_events() ); - } -} diff --git a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php index b71c305ef2d..6a0f24eeba8 100644 --- a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php +++ b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php @@ -60,16 +60,6 @@ public function set_up() { $this->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); $this->payment_methods_compatibility->init_hooks(); - add_filter( 'pre_option__wcpay_feature_upe', [ $this, 'mock_upe_flag' ], 50, 3 ); - } - - public function tear_down() { - parent::tear_down(); - remove_filter( 'pre_option__wcpay_feature_upe', [ $this, 'mock_upe_flag' ], 50 ); - } - - public function mock_upe_flag( $pre_option, $option, $default ) { - return '1'; } public function test_it_should_not_update_available_currencies_when_enabled_payment_methods_do_not_need_it() { diff --git a/tests/unit/notes/test-class-wc-payments-notes-additional-payment-methods.php b/tests/unit/notes/test-class-wc-payments-notes-additional-payment-methods.php deleted file mode 100644 index 53553340b2b..00000000000 --- a/tests/unit/notes/test-class-wc-payments-notes-additional-payment-methods.php +++ /dev/null @@ -1,39 +0,0 @@ -getMockBuilder( \WC_Payments_Account::class )->disableOriginalConstructor()->setMethods( [ 'is_stripe_connected', 'redirect_to_onboarding_welcome_page' ] )->getMock(); - $account_mock->expects( $this->atLeastOnce() )->method( 'is_stripe_connected' )->will( $this->returnValue( false ) ); - $account_mock->expects( $this->once() )->method( 'redirect_to_onboarding_welcome_page' ); - WC_Payments_Notes_Additional_Payment_Methods::set_account( $account_mock ); - $_GET['page'] = 'wc-settings'; - $_GET['action'] = 'enable-upe'; - - WC_Payments_Notes_Additional_Payment_Methods::maybe_enable_upe_feature_flag(); - - $this->assertSame( '0', get_option( '_wcpay_feature_upe' ) ); - } -} diff --git a/tests/unit/notes/test-class-wc-payments-notes-set-up-stripelink.php b/tests/unit/notes/test-class-wc-payments-notes-set-up-stripelink.php index 883bd5cd96e..530ea9b9726 100644 --- a/tests/unit/notes/test-class-wc-payments-notes-set-up-stripelink.php +++ b/tests/unit/notes/test-class-wc-payments-notes-set-up-stripelink.php @@ -32,12 +32,6 @@ public function set_up() { ->getMock(); } - public function tear_down() { - delete_option( '_wcpay_feature_upe' ); - - parent::tear_down(); - } - public function test_stripelink_setup_get_note() { $this->mock_gateway_data( '1', [ 'card', 'link' ], [ 'card' ] ); @@ -72,8 +66,6 @@ public function test_stripelink_setup_note_null_when_link_enabled() { } public function mock_gateway_data( $upe_enabled = '0', $available_methods, $enabled_methods ) { - update_option( '_wcpay_feature_upe', $upe_enabled ); - $this->mock_wcpay_gateway ->expects( $this->any() ) ->method( 'get_upe_available_payment_methods' ) diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php index 955fc151476..7ca31052a47 100644 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -312,9 +312,6 @@ public function set_up() { $this->returnValue( $this->mock_payment_result ) ); - update_option( '_wcpay_feature_upe', '1' ); - update_option( '_wcpay_feature_upe_split', '0' ); - // Arrange: Define a $_POST array which includes the payment method, // so that get_payment_method_from_request() does not throw error. $_POST = [ @@ -345,8 +342,6 @@ public function set_up() { public function tear_down() { parent::tear_down(); WC_Payments::set_database_cache( $this->_cache ); - update_option( '_wcpay_feature_upe', '0' ); - update_option( '_wcpay_feature_upe_split', '0' ); wcpay_get_test_container()->reset_all_replacements(); } diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php index eb86595cd96..2d6e888229e 100644 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -332,8 +332,6 @@ public function set_up() { $get_payment_gateway_by_id_return_value_map[] = [ $payment_method_id, $mock_gateway ]; WC_Helper_Site_Currency::$mock_site_currency = ''; - - update_option( '_wcpay_feature_upe_split', '1' ); } foreach ( $this->mock_payment_gateways as $id => $mock_gateway ) { @@ -367,8 +365,6 @@ public function set_up() { public function tear_down() { parent::tear_down(); WC_Payments::set_database_cache( $this->_cache ); - update_option( '_wcpay_feature_upe_split', '0' ); - update_option( '_wcpay_feature_upe_deferred_intent', '0' ); wcpay_get_test_container()->reset_all_replacements(); } diff --git a/tests/unit/test-class-wc-payments-features.php b/tests/unit/test-class-wc-payments-features.php index 5176215dc59..ea5c7a82df4 100644 --- a/tests/unit/test-class-wc-payments-features.php +++ b/tests/unit/test-class-wc-payments-features.php @@ -19,12 +19,9 @@ class WC_Payments_Features_Test extends WCPAY_UnitTestCase { protected $mock_cache; const FLAG_OPTION_NAME_TO_FRONTEND_KEY_MAPPING = [ - '_wcpay_feature_upe' => 'upe', - '_wcpay_feature_upe_settings_preview' => 'upeSettingsPreview', - '_wcpay_feature_customer_multi_currency' => 'multiCurrency', - '_wcpay_feature_documents' => 'documents', - '_wcpay_feature_auth_and_capture' => 'isAuthAndCaptureEnabled', - 'is_deferred_intent_creation_upe_enabled' => 'upeDeferred', + '_wcpay_feature_customer_multi_currency' => 'multiCurrency', + '_wcpay_feature_documents' => 'documents', + '_wcpay_feature_auth_and_capture' => 'isAuthAndCaptureEnabled', ]; public function set_up() { @@ -62,7 +59,7 @@ public function test_it_returns_expected_to_array_result( array $enabled_flags ) public function enabled_flags_provider() { return [ - 'no flags' => [ [ '_wcpay_feature_upe', 'is_deferred_intent_creation_upe_enabled' ] ], + 'no flags' => [ [] ], 'all flags' => [ array_keys( self::FLAG_OPTION_NAME_TO_FRONTEND_KEY_MAPPING ) ], ]; } diff --git a/tests/unit/test-class-wc-payments-token-service.php b/tests/unit/test-class-wc-payments-token-service.php index a5c24e5fb03..7d9f40dfa6e 100644 --- a/tests/unit/test-class-wc-payments-token-service.php +++ b/tests/unit/test-class-wc-payments-token-service.php @@ -141,7 +141,6 @@ public function test_add_token_to_user_for_sepa_deferred_intent_creation_upe() { $this->assertEquals( 'pm_mock', $token->get_token() ); $this->assertEquals( '3000', $token->get_last4() ); $this->assertInstanceOf( WC_Payment_Token_WCPay_SEPA::class, $token ); - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '0' ); } @@ -165,7 +164,6 @@ public function test_add_token_to_user_for_sepa_deferred_upe() { $this->assertEquals( 'pm_mock', $token->get_token() ); $this->assertEquals( '3000', $token->get_last4() ); $this->assertInstanceOf( WC_Payment_Token_WCPay_SEPA::class, $token ); - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); } @@ -195,7 +193,6 @@ public function test_add_token_to_user_for_link() { * Test add Link token to user with split UPE. */ public function test_add_token_to_user_for_link_split_upe() { - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '1' ); $mock_payment_method = [ 'id' => 'pm_mock', 'link' => [ @@ -212,14 +209,12 @@ public function test_add_token_to_user_for_link_split_upe() { $this->assertSame( 'test@test.com', $token->get_email() ); $this->assertSame( '***test@test.com', $token->get_redacted_email() ); $this->assertInstanceOf( WC_Payment_Token_WCPay_Link::class, $token ); - update_option( WC_Payments_Features::UPE_SPLIT_FLAG_NAME, '0' ); } /** * Test add Link token to user with deferred intent UPE. */ public function test_add_token_to_user_for_link_deferred_upe() { - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '1' ); $mock_payment_method = [ 'id' => 'pm_mock', 'link' => [ @@ -236,7 +231,6 @@ public function test_add_token_to_user_for_link_deferred_upe() { $this->assertSame( 'test@test.com', $token->get_email() ); $this->assertSame( '***test@test.com', $token->get_redacted_email() ); $this->assertInstanceOf( WC_Payment_Token_WCPay_Link::class, $token ); - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); } public function test_add_payment_method_to_user() { @@ -595,8 +589,6 @@ public function test_woocommerce_get_customer_payment_tokens_not_added_from_diff $this->assertEquals( $gateway_id, $result_tokens[1]->get_gateway_id() ); $this->assertEquals( 'pm_mock0', $result_tokens[0]->get_token() ); $this->assertEquals( 'pm_222', $result_tokens[1]->get_token() ); - - update_option( '_wcpay_feature_upe_split', '0' ); } public function test_woocommerce_get_customer_payment_tokens_payment_methods_only_for_retrievable_types() { diff --git a/tests/unit/test-class-wc-payments.php b/tests/unit/test-class-wc-payments.php index 00bd7e0a852..692a45b96df 100644 --- a/tests/unit/test-class-wc-payments.php +++ b/tests/unit/test-class-wc-payments.php @@ -102,8 +102,6 @@ public function test_it_skips_stripe_link_gateway_registration() { $this->assertCount( 1, $registered_gateways ); $this->assertInstanceOf( UPE_Split_Payment_Gateway::class, $registered_gateways[0] ); $this->assertEquals( $registered_gateways[0]->get_stripe_id(), 'card' ); - - update_option( WC_Payments_Features::UPE_DEFERRED_INTENT_FLAG_NAME, '0' ); } public function test_rest_endpoints_validate_nonce() { From 88711f283486643ac4fc5a09091f3bd37124ca9c Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 12 Dec 2023 00:16:07 -0800 Subject: [PATCH 17/56] fix: account currency hook return value (#7872) --- changelog/fix-account-currency-hook | 4 ++++ client/payment-methods/index.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-account-currency-hook diff --git a/changelog/fix-account-currency-hook b/changelog/fix-account-currency-hook new file mode 100644 index 00000000000..23d9628b19f --- /dev/null +++ b/changelog/fix-account-currency-hook @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +fix: account currency hook return value diff --git a/client/payment-methods/index.js b/client/payment-methods/index.js index 950610e6fba..7efb306b0da 100644 --- a/client/payment-methods/index.js +++ b/client/payment-methods/index.js @@ -92,7 +92,7 @@ const PaymentMethods = () => { const [ , updateSelectedPaymentMethod ] = useSelectedPaymentMethod(); - const [ stripeAccountDomesticCurrency ] = useAccountDomesticCurrency(); + const stripeAccountDomesticCurrency = useAccountDomesticCurrency(); const completeActivation = ( itemId ) => { updateSelectedPaymentMethod( itemId ); From d04bda8dfbfd2a05ac7e73e9f588c0deab8bc82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Janisels?= Date: Tue, 12 Dec 2023 11:12:01 +0200 Subject: [PATCH 18/56] Include discount in fees brakedown tooltip (#7787) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kārlis Janisels Co-authored-by: Vladimir Reznichenko --- .../fix-7750-include-discount-in-tooltip | 4 ++ client/utils/account-fees.tsx | 59 +++++++++++------ .../test/__snapshots__/account-fees.tsx.snap | 65 ++----------------- client/utils/test/account-fees.tsx | 29 --------- 4 files changed, 48 insertions(+), 109 deletions(-) create mode 100644 changelog/fix-7750-include-discount-in-tooltip diff --git a/changelog/fix-7750-include-discount-in-tooltip b/changelog/fix-7750-include-discount-in-tooltip new file mode 100644 index 00000000000..b600a611e92 --- /dev/null +++ b/changelog/fix-7750-include-discount-in-tooltip @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Include discount fee in fees tooltip diff --git a/client/utils/account-fees.tsx b/client/utils/account-fees.tsx index 6ba2674de6d..c2b3214c1b5 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -74,24 +74,31 @@ const getStripeFeeSectionUrl = ( country: string ): string => { ); }; -const getFeeDescriptionString = ( fee: BaseFee ): string => { +const getFeeDescriptionString = ( + fee: BaseFee, + discountBasedMultiplier: number +): string => { if ( fee.fixed_rate && fee.percentage_rate ) { return sprintf( '%1$f%% + %2$s', - formatFee( fee.percentage_rate ), - formatCurrency( fee.fixed_rate, fee.currency ) + formatFee( fee.percentage_rate * discountBasedMultiplier ), + formatCurrency( + fee.fixed_rate * discountBasedMultiplier, + fee.currency + ) ); } else if ( fee.fixed_rate ) { return sprintf( - '%2$s', - formatFee( fee.percentage_rate ), - formatCurrency( fee.fixed_rate, fee.currency ) + '%1$s', + formatCurrency( + fee.fixed_rate * discountBasedMultiplier, + fee.currency + ) ); } else if ( fee.percentage_rate ) { return sprintf( '%1$f%%', - formatFee( fee.percentage_rate ), - formatCurrency( fee.fixed_rate, fee.currency ) + formatFee( fee.percentage_rate * discountBasedMultiplier ) ); } return ''; @@ -109,19 +116,19 @@ export const formatMethodFeesTooltip = ( accountFees: FeeStructure ): JSX.Element => { if ( ! accountFees ) return <>; - const currentBaseFee = getCurrentBaseFee( accountFees ); - // If the current fee doesn't have a fixed or percentage rate, use the base fee's rate. Eg. when there is a promotional discount fee applied. Use this to calculate the total fee too. - const currentFeeWithBaseFallBack = currentBaseFee.percentage_rate - ? currentBaseFee - : accountFees.base; + + const discountAdjustedFeeRate: number = + accountFees.discount.length && accountFees.discount[ 0 ].discount + ? 1 - accountFees.discount[ 0 ].discount + : 1; const total = { percentage_rate: - currentFeeWithBaseFallBack.percentage_rate + + accountFees.base.percentage_rate + accountFees.additional.percentage_rate + accountFees.fx.percentage_rate, fixed_rate: - currentFeeWithBaseFallBack.fixed_rate + + accountFees.base.fixed_rate + accountFees.additional.fixed_rate + accountFees.fx.fixed_rate, currency: accountFees.base.currency, @@ -136,14 +143,20 @@ export const formatMethodFeesTooltip = (
Base fee
- { getFeeDescriptionString( currentFeeWithBaseFallBack ) } + { getFeeDescriptionString( + accountFees.base, + discountAdjustedFeeRate + ) }
{ hasFees( accountFees.additional ) ? (
International payment method fee
- { getFeeDescriptionString( accountFees.additional ) } + { getFeeDescriptionString( + accountFees.additional, + discountAdjustedFeeRate + ) }
) : ( @@ -152,7 +165,12 @@ export const formatMethodFeesTooltip = ( { hasFees( accountFees.fx ) ? (
Foreign exchange fee
-
{ getFeeDescriptionString( accountFees.fx ) }
+
+ { getFeeDescriptionString( + accountFees.fx, + discountAdjustedFeeRate + ) } +
) : ( '' @@ -160,7 +178,10 @@ export const formatMethodFeesTooltip = (
Total per transaction
- { getFeeDescriptionString( total ) } + { getFeeDescriptionString( + total, + discountAdjustedFeeRate + ) }
{ wcpaySettings && diff --git a/client/utils/test/__snapshots__/account-fees.tsx.snap b/client/utils/test/__snapshots__/account-fees.tsx.snap index 5ec2897e12b..5388a2b344d 100644 --- a/client/utils/test/__snapshots__/account-fees.tsx.snap +++ b/client/utils/test/__snapshots__/account-fees.tsx.snap @@ -10,7 +10,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base Base fee
- 12.3% + $4.57 + 9.84% + $3.65
@@ -18,7 +18,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base International payment method fee
- 1% + 0.8%
@@ -26,7 +26,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base Foreign exchange fee
- 1% + 0.8%
@@ -36,7 +36,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base
- 14.3% + $4.57 + 11.44% + $3.65
`; - -exports[`Account fees utility functions formatMethodFeesTooltip() displays custom fee details, when applicable 1`] = ` -
-`; diff --git a/client/utils/test/account-fees.tsx b/client/utils/test/account-fees.tsx index 72cf891067b..c1b181ac160 100644 --- a/client/utils/test/account-fees.tsx +++ b/client/utils/test/account-fees.tsx @@ -310,35 +310,6 @@ describe( 'Account fees utility functions', () => { expect( container ).toMatchSnapshot(); } ); - it( 'displays custom fee details, when applicable', () => { - const methodFees = mockAccountFees( - { - percentage_rate: 0.123, - fixed_rate: 456.78, - currency: 'USD', - }, - [ { percentage_rate: 0.101, fixed_rate: 400.78 } ] - ); - - methodFees.additional = { - percentage_rate: 0.01, - fixed_rate: 0, - currency: 'USD', - }; - - methodFees.fx = { - percentage_rate: 0.01, - fixed_rate: 0, - currency: 'USD', - }; - - const { container } = render( - formatMethodFeesTooltip( methodFees ) - ); - - expect( container ).toMatchSnapshot(); - } ); - it( 'displays base fee, when only promo discount without percentage or fixed', () => { const methodFees = mockAccountFees( { From 3d373d68fe0f8cee425bac3aa9b0b37f9fa4f092 Mon Sep 17 00:00:00 2001 From: Jesse Pearson Date: Tue, 12 Dec 2023 07:41:22 -0400 Subject: [PATCH 19/56] Update Qualitative Feedback note to have more efficient sql query (#7825) --- changelog/fix-3693-qualitative-feedback-note | 4 ++++ ...wc-payments-notes-qualitative-feedback.php | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-3693-qualitative-feedback-note diff --git a/changelog/fix-3693-qualitative-feedback-note b/changelog/fix-3693-qualitative-feedback-note new file mode 100644 index 00000000000..8862cc0c961 --- /dev/null +++ b/changelog/fix-3693-qualitative-feedback-note @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Update Qualitative Feedback note to have more efficient sql query. diff --git a/includes/notes/class-wc-payments-notes-qualitative-feedback.php b/includes/notes/class-wc-payments-notes-qualitative-feedback.php index 266e1dd70f1..031fbd6b473 100644 --- a/includes/notes/class-wc-payments-notes-qualitative-feedback.php +++ b/includes/notes/class-wc-payments-notes-qualitative-feedback.php @@ -38,8 +38,27 @@ public static function get_note() { } // We should have at least one transaction. - $token_count = $wpdb->get_var( "select count(*) from {$wpdb->prefix}woocommerce_payment_tokens" ); - if ( 0 === (int) $token_count ) { + if ( WC_Payments_Utils::is_hpos_tables_usage_enabled() ) { + $result = $wpdb->get_var( + "SELECT EXISTS( + SELECT 1 + FROM {$wpdb->prefix}wc_orders_meta + WHERE meta_key = '_wcpay_transaction_fee' + LIMIT 1) + AS count;" + ); + } else { + $result = $wpdb->get_var( + "SELECT EXISTS( + SELECT 1 + FROM {$wpdb->postmeta} + WHERE meta_key = '_wcpay_transaction_fee' + LIMIT 1) + AS count;" + ); + } + + if ( 1 !== intval( $result ) ) { return; } From a30b28c1c6493291e391287b77a42034751c2753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Costa?= <10233985+cesarcosta99@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:52:51 -0500 Subject: [PATCH 20/56] Fix multi-currency e2e tests (#7870) --- changelog/dev-fix-multi-currency-e2e-tests | 5 +++++ tests/e2e/config/jest.setup.js | 3 +++ .../wcpay/shopper/shopper-multi-currency-widget.spec.js | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 changelog/dev-fix-multi-currency-e2e-tests diff --git a/changelog/dev-fix-multi-currency-e2e-tests b/changelog/dev-fix-multi-currency-e2e-tests new file mode 100644 index 00000000000..45ac7deccc1 --- /dev/null +++ b/changelog/dev-fix-multi-currency-e2e-tests @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Fix multi-currency e2e tests. + + diff --git a/tests/e2e/config/jest.setup.js b/tests/e2e/config/jest.setup.js index 00f41b376ad..a29f5138fcb 100644 --- a/tests/e2e/config/jest.setup.js +++ b/tests/e2e/config/jest.setup.js @@ -24,6 +24,9 @@ const ERROR_MESSAGES_TO_IGNORE = [ 'Scripts that have a dependency on', 'was preloaded using link preload but not used within a few seconds', 'No UI will be shown. CanMakePayment and hasEnrolledInstrument', + 'Failed to load resource: the server responded with a status of 404 (Not Found)', + 'Store "wc/payments" is already registered.', + 'Preflight request for request with keepalive specified is currently not supported', ]; ERROR_MESSAGES_TO_IGNORE.forEach( ( errorMessage ) => { diff --git a/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js index 182802a62ba..23aec018d59 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-multi-currency-widget.spec.js @@ -64,6 +64,7 @@ describe( 'Shopper Multi-Currency widget', () => { it( 'should display currency switcher widget if multi-currency is enabled', async () => { await merchantWCP.addMulticurrencyWidget(); + await merchant.logout(); await shopper.goToShop(); await page.waitForSelector( '.widget select[name=currency]', { visible: true, @@ -72,6 +73,7 @@ describe( 'Shopper Multi-Currency widget', () => { } ); it( 'should not display currency switcher widget if multi-currency is disabled', async () => { + await merchant.login(); await merchantWCP.openWCPSettings(); await merchantWCP.deactivateMulticurrency(); await shopper.goToShop(); From ef7a13cea29e43d9333a99a2eb054ba817b43a5c Mon Sep 17 00:00:00 2001 From: Malith Senaweera <6216000+malithsen@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:50:17 -0600 Subject: [PATCH 21/56] Record a Tracks event when viewing the order success page (#7859) --- changelog/add-thank-you-page-tracks | 4 ++++ includes/class-woopay-tracker.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 changelog/add-thank-you-page-tracks diff --git a/changelog/add-thank-you-page-tracks b/changelog/add-thank-you-page-tracks new file mode 100644 index 00000000000..31888d5d917 --- /dev/null +++ b/changelog/add-thank-you-page-tracks @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Thank you page Tracks event diff --git a/includes/class-woopay-tracker.php b/includes/class-woopay-tracker.php index 123ec606a7b..672e8251b11 100644 --- a/includes/class-woopay-tracker.php +++ b/includes/class-woopay-tracker.php @@ -71,6 +71,7 @@ public function __construct( $http ) { add_action( 'woocommerce_blocks_checkout_order_processed', [ $this, 'checkout_order_processed' ] ); add_action( 'woocommerce_payments_save_user_in_woopay', [ $this, 'must_save_payment_method_to_platform' ] ); add_action( 'before_woocommerce_pay_form', [ $this, 'pay_for_order_page_view' ] ); + add_action( 'woocommerce_thankyou', [ $this, 'thank_you_page_view' ] ); } /** @@ -460,6 +461,22 @@ public function must_save_payment_method_to_platform() { ); } + /** + * Record a Tracks event that Thank you page was viewed for a WCPay order. + * + * @param int $order_id The ID of the order. + * @return void + */ + public function thank_you_page_view($order_id) { + $order = wc_get_order( $order_id ); + + if ( ! $order || 'woocommerce_payments' !== $order->get_payment_method() ) { + return; + } + + $this->maybe_record_wcpay_shopper_event( 'order_success_page_view' ); + } + /** * Record a Tracks event that the WooPay express button locations has been updated. * From 56863b221dbdfd9121189fb22303855f16d866d2 Mon Sep 17 00:00:00 2001 From: Ricardo Metring Date: Tue, 12 Dec 2023 15:40:49 -0300 Subject: [PATCH 22/56] Do not expect 3D Secure authentication for declined 3DS card (#7844) --- changelog/dev-test-ci-without-3ds1 | 4 ++++ .../e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog/dev-test-ci-without-3ds1 diff --git a/changelog/dev-test-ci-without-3ds1 b/changelog/dev-test-ci-without-3ds1 new file mode 100644 index 00000000000..f2d4b778788 --- /dev/null +++ b/changelog/dev-test-ci-without-3ds1 @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Comment: Fix declined 3DS card E2E test. diff --git a/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js index 9c14db6bdbf..715732b40f9 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js @@ -9,7 +9,6 @@ import { shopperWCP } from '../../../utils'; import { clearCardDetails, - confirmCardAuthentication, fillCardDetails, setupProductCheckout, } from '../../../utils/payments'; @@ -145,7 +144,6 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { const declinedCard = config.get( 'cards.declined-3ds' ); await fillCardDetails( page, declinedCard ); await expect( page ).toClick( '#place_order' ); - await confirmCardAuthentication( page, '3DS' ); await page.waitForSelector( 'ul.woocommerce-error' ); const declined3dsCardError = await page.$eval( 'div.woocommerce-NoticeGroup > ul.woocommerce-error', From e5bca8df8ccbb61b1707cbd947f413b1bacb9f27 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Wed, 13 Dec 2023 14:43:10 -0500 Subject: [PATCH 23/56] Improve E2E checkout tests (#7664) Co-authored-by: Timur Karimov Co-authored-by: Timur Karimov --- changelog/deferred-intent | 4 ++ ...ferred-intent-creation-upe-enabled.spec.js | 54 ++++++++++++++++--- tests/e2e/utils/flows.js | 8 +++ 3 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 changelog/deferred-intent diff --git a/changelog/deferred-intent b/changelog/deferred-intent new file mode 100644 index 00000000000..ef5442b5f17 --- /dev/null +++ b/changelog/deferred-intent @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Improve E2E checkout tests diff --git a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js b/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js index 1e9a6a27956..e1062f6d1ca 100644 --- a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js +++ b/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js @@ -18,13 +18,10 @@ import { uiUnblocked } from '@woocommerce/e2e-utils/build/page-utils'; const { shopper, merchant } = require( '@woocommerce/e2e-utils' ); const UPE_METHOD_CHECKBOXES = [ - '#inspector-checkbox-control-5', // bancontact - '#inspector-checkbox-control-6', // eps '#inspector-checkbox-control-7', // giropay - '#inspector-checkbox-control-8', // ideal - '#inspector-checkbox-control-9', // sofort ]; const card = config.get( 'cards.basic' ); +const card2 = config.get( 'cards.basic2' ); const MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS = 20000; describe( 'Enabled UPE with deferred intent creation', () => { @@ -33,11 +30,9 @@ describe( 'Enabled UPE with deferred intent creation', () => { await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); await merchant.logout(); await shopper.login(); - await shopperWCP.changeAccountCurrencyTo( 'EUR' ); } ); afterAll( async () => { - await shopperWCP.changeAccountCurrencyTo( 'USD' ); await shopperWCP.logout(); await merchant.login(); await merchantWCP.disablePaymentMethod( UPE_METHOD_CHECKBOXES ); @@ -46,6 +41,7 @@ describe( 'Enabled UPE with deferred intent creation', () => { describe( 'Enabled UPE with deferred intent creation', () => { it( 'should successfully place order with Giropay', async () => { + await shopperWCP.goToShopWithCurrency( 'EUR' ); await setupProductCheckout( config.get( 'addresses.customer.billing' ) ); @@ -115,10 +111,23 @@ describe( 'Enabled UPE with deferred intent creation', () => { await shopperWCP.deleteSavedPaymentMethod( card.label ); await expect( page ).toMatch( 'Payment method deleted' ); } ); + + it( 'should not allow guest user to save the card', async () => { + await shopperWCP.logout(); + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + + await expect( page ).not.toMatchElement( + 'input#wc-woocommerce_payments-new-payment-method' + ); + await shopper.login(); + } ); } ); describe( 'My Account', () => { let timeAdded; + it( 'should add the card as a new payment method', async () => { await shopperWCP.goToPaymentMethods(); await shopperWCP.addNewPaymentMethod( 'basic', card ); @@ -134,14 +143,43 @@ describe( 'Enabled UPE with deferred intent creation', () => { await expect( page ).toMatch( `${ card.expires.month }/${ card.expires.year }` ); + await waitTwentySecondsSinceLastCardAdded(); + } ); + + it( 'should be able to set payment method as default', async () => { + await shopperWCP.goToPaymentMethods(); + await shopperWCP.addNewPaymentMethod( 'basic2', card2 ); + // Take note of the time when we added this card + timeAdded = Date.now(); + + // Verify that the card was added + await expect( page ).not.toMatch( + 'You cannot add a new payment method so soon after the previous one. Please wait for 20 seconds.' + ); + await expect( page ).toMatch( 'Payment method successfully added' ); + await expect( page ).toMatch( + `${ card2.expires.month }/${ card2.expires.year }` + ); + await shopperWCP.setDefaultPaymentMethod( card2.label ); + // Verify that the card was set as default + await expect( page ).toMatch( + 'This payment method was successfully set as your default.' + ); } ); - it( 'should be able to delete the card', async () => { + it( 'should be able to delete cards', async () => { await shopperWCP.deleteSavedPaymentMethod( card.label ); await expect( page ).toMatch( 'Payment method deleted.' ); + + await shopperWCP.deleteSavedPaymentMethod( card2.label ); + await expect( page ).toMatch( 'Payment method deleted.' ); } ); afterAll( async () => { + await waitTwentySecondsSinceLastCardAdded(); + } ); + + async function waitTwentySecondsSinceLastCardAdded() { // Make sure that at least 20s had already elapsed since the last card was added. // Otherwise, you will get the error message, // "You cannot add a new payment method so soon after the previous one." @@ -153,6 +191,6 @@ describe( 'Enabled UPE with deferred intent creation', () => { : 0; await new Promise( ( r ) => setTimeout( r, remainingWaitTime ) ); - } ); + } } ); } ); diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js index 0a2d8a8e7f8..aa18cd328a7 100644 --- a/tests/e2e/utils/flows.js +++ b/tests/e2e/utils/flows.js @@ -129,6 +129,14 @@ export const shopperWCP = { await expect( page ).toClick( 'label', { text: label } ); }, + setDefaultPaymentMethod: async ( label ) => { + const [ paymentMethodRow ] = await page.$x( + `//tr[contains(., '${ label }')]` + ); + await expect( paymentMethodRow ).toClick( '.button.default' ); + await page.waitForNavigation( { waitUntil: 'networkidle0' } ); + }, + toggleCreateAccount: async () => { await expect( page ).toClick( '#createaccount' ); }, From 85e191afad8cadfe87f4854b8e39cfebf08af838 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 14 Dec 2023 09:57:31 +0100 Subject: [PATCH 24/56] Cleanup the removed scripts rollout (#7911) Co-authored-by: Timur Karimov --- changelog/cleanup-redundant-script-enqueueing | 4 +++ ...yments-upe-split-blocks-payment-method.php | 26 ------------------- includes/class-wc-payments.php | 3 +-- 3 files changed, 5 insertions(+), 28 deletions(-) create mode 100644 changelog/cleanup-redundant-script-enqueueing delete mode 100644 includes/class-wc-payments-upe-split-blocks-payment-method.php diff --git a/changelog/cleanup-redundant-script-enqueueing b/changelog/cleanup-redundant-script-enqueueing new file mode 100644 index 00000000000..20c952d459e --- /dev/null +++ b/changelog/cleanup-redundant-script-enqueueing @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup enqueueing of the scripts which were removed diff --git a/includes/class-wc-payments-upe-split-blocks-payment-method.php b/includes/class-wc-payments-upe-split-blocks-payment-method.php deleted file mode 100644 index 2f1188611ea..00000000000 --- a/includes/class-wc-payments-upe-split-blocks-payment-method.php +++ /dev/null @@ -1,26 +0,0 @@ -register( new WC_Payments_UPE_Split_Blocks_Payment_Method() ); + $payment_method_registry->register( new WC_Payments_Blocks_Payment_Method() ); } /** From 7a7a05e87888422d12b6f40b64e7bb616f27048a Mon Sep 17 00:00:00 2001 From: Cvetan Cvetanov Date: Thu, 14 Dec 2023 11:32:46 +0200 Subject: [PATCH 25/56] Introduce additional columns to the Transaction list UI (#7813) --- changelog/add-7591-missing-columns-export-csv | 4 + client/transactions/list/index.tsx | 76 +++++++++++++++-- .../list/test/__snapshots__/index.tsx.snap | 84 +++++++++---------- client/transactions/list/test/index.tsx | 26 +++--- 4 files changed, 132 insertions(+), 58 deletions(-) create mode 100644 changelog/add-7591-missing-columns-export-csv diff --git a/changelog/add-7591-missing-columns-export-csv b/changelog/add-7591-missing-columns-export-csv new file mode 100644 index 00000000000..668644d2cfc --- /dev/null +++ b/changelog/add-7591-missing-columns-export-csv @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Introduce Customer currency, Deposit currency, Amount in Customer Currency and Deposit ID columns to the Transaction list UI and CSV export diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index 8b66096a60c..931712821a9 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -144,8 +144,11 @@ const getColumns = ( }, { key: 'date', - label: __( 'Date / Time', 'woocommerce-payments' ), - screenReaderLabel: __( 'Date and time', 'woocommerce-payments' ), + label: __( 'Date / Time (UTC)', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Date and time in UTC', + 'woocommerce-payments' + ), required: true, isLeftAligned: true, defaultOrder: 'desc', @@ -167,10 +170,41 @@ const getColumns = ( required: true, isLeftAligned: true, }, + { + key: 'customer_currency', + label: __( 'Paid Currency', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Customer Currency', + 'woocommerce-payments' + ), + isSortable: true, + visible: false, + }, + { + key: 'customer_amount', + label: __( 'Amount Paid', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Amount in Customer Currency', + 'woocommerce-payments' + ), + isNumeric: true, + isSortable: true, + visible: false, + }, + { + key: 'deposit_currency', + label: __( 'Deposit Currency', 'woocommerce-payments' ), + screenReaderLabel: __( 'Deposit Currency', 'woocommerce-payments' ), + isSortable: true, + visible: false, + }, { key: 'amount', label: __( 'Amount', 'woocommerce-payments' ), - screenReaderLabel: __( 'Amount', 'woocommerce-payments' ), + screenReaderLabel: __( + 'Amount in Deposit Curency', + 'woocommerce-payments' + ), isNumeric: true, isSortable: true, }, @@ -205,8 +239,8 @@ const getColumns = ( }, { key: 'source', - label: __( 'Source', 'woocommerce-payments' ), - screenReaderLabel: __( 'Source', 'woocommerce-payments' ), + label: __( 'Payment Method', 'woocommerce-payments' ), + screenReaderLabel: __( 'Payment Method', 'woocommerce-payments' ), cellClassName: 'is-center-aligned', }, { @@ -236,6 +270,14 @@ const getColumns = ( visible: false, isLeftAligned: true, }, + includeDeposit && { + key: 'deposit_id', + label: __( 'Deposit ID', 'woocommerce-payments' ), + screenReaderLabel: __( 'Deposit ID', 'woocommerce-payments' ), + cellClassName: 'deposit', + isLeftAligned: true, + visible: false, + }, includeDeposit && { key: 'deposit', label: __( 'Deposit date', 'woocommerce-payments' ), @@ -371,6 +413,17 @@ export const TransactionsList = ( ), }; }; + const formatCustomerAmount = () => { + return { + value: formatExportAmount( + txn.customer_amount, + txn.customer_currency + ), + display: clickable( + formatCurrency( txn.customer_amount, txn.customer_currency ) + ), + }; + }; const isFinancingType = -1 !== @@ -464,6 +517,15 @@ export const TransactionsList = ( value: txn.customer_country, display: clickable( txn.customer_country ), }, + customer_currency: { + value: txn.customer_currency.toUpperCase(), + display: clickable( txn.customer_currency.toUpperCase() ), + }, + customer_amount: formatCustomerAmount(), + deposit_currency: { + value: txn.currency.toUpperCase(), + display: clickable( txn.currency.toUpperCase() ), + }, amount: formatAmount(), // fees should display as negative. The format $-9.99 is determined by WC-Admin fees: formatFees(), @@ -477,6 +539,10 @@ export const TransactionsList = ( value: calculateRiskMapping( txn.risk_level ), display: clickable( riskLevel ), }, + deposit_id: { + value: txn.deposit_id, + display: txn.deposit_id, + }, deposit: { value: txn.available_on, display: deposit }, deposit_status: { value: depositStatus, diff --git a/client/transactions/list/test/__snapshots__/index.tsx.snap b/client/transactions/list/test/__snapshots__/index.tsx.snap index 35b86a155f7..c7bfca77b45 100644 --- a/client/transactions/list/test/__snapshots__/index.tsx.snap +++ b/client/transactions/list/test/__snapshots__/index.tsx.snap @@ -248,19 +248,19 @@ exports[`Transactions list renders correctly when can filter by several currenci - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method - Date / Time + Date / Time (UTC) - Date and time + Date and time in UTC - Sort by Date and time in ascending order + Sort by Date and time in UTC in ascending order - Amount + Amount in Deposit Curency - Sort by Amount in descending order + Sort by Amount in Deposit Curency in descending order - Source + Payment Method - Source + Payment Method { } ); test( 'sorts by default field date', () => { - sortBy( 'Date and time' ); + sortBy( 'Date and time in UTC' ); expectSortingToBe( 'date', 'asc' ); - sortBy( 'Date and time' ); + sortBy( 'Date and time in UTC' ); expectSortingToBe( 'date', 'desc' ); } ); test( 'sorts by amount', () => { - sortBy( 'Amount' ); + sortBy( 'Amount in Deposit Curency' ); expectSortingToBe( 'amount', 'desc' ); - sortBy( 'Amount' ); + sortBy( 'Amount in Deposit Curency' ); expectSortingToBe( 'amount', 'asc' ); } ); @@ -609,18 +609,22 @@ describe( 'Transactions list', () => { const expected = [ '"Transaction Id"', - '"Date / Time"', + '"Date / Time (UTC)"', 'Type', 'Channel', + '"Paid Currency"', + '"Amount Paid"', + '"Deposit Currency"', 'Amount', 'Fees', 'Net', '"Order #"', - 'Source', + '"Payment Method"', 'Customer', 'Email', 'Country', '"Risk level"', + '"Deposit ID"', '"Deposit date"', '"Deposit status"', ]; @@ -675,26 +679,26 @@ describe( 'Transactions list', () => { ); // channel expect( getUnformattedAmount( displayFirstTransaction[ 3 ] ).indexOf( - csvFirstTransaction[ 4 ] + csvFirstTransaction[ 7 ] ) ).not.toBe( -1 ); // amount expect( -Number( getUnformattedAmount( displayFirstTransaction[ 4 ] ) ) ).toEqual( Number( - csvFirstTransaction[ 5 ].replace( /['"]+/g, '' ) // strip extra quotes + csvFirstTransaction[ 8 ].replace( /['"]+/g, '' ) // strip extra quotes ) ); // fees expect( getUnformattedAmount( displayFirstTransaction[ 5 ] ).indexOf( - csvFirstTransaction[ 6 ] + csvFirstTransaction[ 9 ] ) ).not.toBe( -1 ); // net expect( displayFirstTransaction[ 6 ] ).toBe( - csvFirstTransaction[ 7 ] + csvFirstTransaction[ 10 ] ); // order number expect( displayFirstTransaction[ 8 ] ).toBe( - csvFirstTransaction[ 9 ].replace( /['"]+/g, '' ) // strip extra quotes + csvFirstTransaction[ 12 ].replace( /['"]+/g, '' ) // strip extra quotes ); // customer } ); } ); From b0fc45f6ca1feb91caba5fc798d7ab81482b3a5d Mon Sep 17 00:00:00 2001 From: Jesse Pearson Date: Thu, 14 Dec 2023 10:02:02 -0400 Subject: [PATCH 26/56] Add Compatibility Service (#7778) --- changelog/update-4163-compatibility-service | 4 ++ includes/class-compatibility-service.php | 61 +++++++++++++++++++ includes/class-wc-payments.php | 11 ++++ includes/core/server/class-request.php | 1 + .../class-wc-payments-api-client.php | 22 +++++++ .../unit/test-class-compatibility-service.php | 61 +++++++++++++++++++ .../test-class-wc-payments-api-client.php | 29 ++++++++- 7 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 changelog/update-4163-compatibility-service create mode 100644 includes/class-compatibility-service.php create mode 100644 tests/unit/test-class-compatibility-service.php diff --git a/changelog/update-4163-compatibility-service b/changelog/update-4163-compatibility-service new file mode 100644 index 00000000000..3a524da05ae --- /dev/null +++ b/changelog/update-4163-compatibility-service @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adding Compatibility Service to assist with flagging possible compatibility issues in the future. diff --git a/includes/class-compatibility-service.php b/includes/class-compatibility-service.php new file mode 100644 index 00000000000..444b3383cb1 --- /dev/null +++ b/includes/class-compatibility-service.php @@ -0,0 +1,61 @@ +payments_api_client = $payments_api_client; + } + + /** + * Initializes this class's WP hooks. + * + * @return void + */ + public function init_hooks() { + add_action( 'woocommerce_payments_account_refreshed', [ $this, 'update_compatibility_data' ] ); + } + + /** + * Gets the data we need to confirm compatibility and sends it to the server. + * + * @return void + */ + public function update_compatibility_data() { + try { + $this->payments_api_client->update_compatibility_data( + [ + 'woopayments_version' => WCPAY_VERSION_NUMBER, + 'woocommerce_version' => WC_VERSION, + ] + ); + } catch ( API_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // The exception is already logged if logging is on, nothing else needed. + } + } +} diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index f2e038afe59..26e51088d9f 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -44,6 +44,7 @@ use WCPay\Internal\Service\OrderService; use WCPay\WooPay\WooPay_Scheduler; use WCPay\WooPay\WooPay_Session; +use WCPay\Compatibility_Service; /** * Main class for the WooPayments extension. Its responsibility is to initialize the extension. @@ -295,6 +296,13 @@ class WC_Payments { */ private static $incentives_service; + /** + * Instance of Compatibility_Service, created in init function + * + * @var Compatibility_Service + */ + private static $compatibility_service; + /** * Entry point to the initialization logic. */ @@ -463,6 +471,7 @@ public static function init() { include_once __DIR__ . '/core/service/class-wc-payments-customer-service-api.php'; include_once __DIR__ . '/class-duplicate-payment-prevention-service.php'; include_once __DIR__ . '/class-wc-payments-incentives-service.php'; + include_once __DIR__ . '/class-compatibility-service.php'; include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php'; self::$woopay_checkout_service = new Checkout_Service(); @@ -497,6 +506,7 @@ public static function init() { self::$woopay_tracker = new WooPay_Tracker( self::get_wc_payments_http() ); self::$incentives_service = new WC_Payments_Incentives_Service( self::$database_cache ); self::$duplicate_payment_prevention_service = new Duplicate_Payment_Prevention_Service(); + self::$compatibility_service = new Compatibility_Service( self::$api_client ); ( new WooPay_Scheduler( self::$api_client ) )->init(); @@ -505,6 +515,7 @@ public static function init() { self::$fraud_service->init_hooks(); self::$onboarding_service->init_hooks(); self::$incentives_service->init_hooks(); + self::$compatibility_service->init_hooks(); self::$legacy_card_gateway = new CC_Payment_Gateway( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service ); diff --git a/includes/core/server/class-request.php b/includes/core/server/class-request.php index 80e35dba0de..6264660bb29 100644 --- a/includes/core/server/class-request.php +++ b/includes/core/server/class-request.php @@ -161,6 +161,7 @@ abstract class Request { WC_Payments_API_Client::AUTHORIZATIONS_API => 'authorizations', WC_Payments_API_Client::FRAUD_OUTCOMES_API => 'fraud_outcomes', WC_Payments_API_Client::FRAUD_RULESET_API => 'fraud_ruleset', + WC_Payments_API_Client::COMPATIBILITY_API => 'compatibility', ]; /** 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 f6cd60bfb87..637126b3fa4 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -76,6 +76,7 @@ class WC_Payments_API_Client { const FRAUD_SERVICES_API = 'accounts/fraud_services'; const FRAUD_OUTCOMES_API = 'fraud_outcomes'; const FRAUD_RULESET_API = 'fraud_ruleset'; + const COMPATIBILITY_API = 'compatibility'; /** * Common keys in API requests/responses that we might want to redact. @@ -1703,6 +1704,27 @@ public function get_onboarding_po_eligible( array $business_info, array $store_i ); } + /** + * Sends the compatibility data to the server to be saved to the account. + * + * @param array $compatibility_data The array containing the data. + * + * @return array HTTP response on success. + * + * @throws API_Exception - If not connected or request failed. + */ + public function update_compatibility_data( $compatibility_data ) { + $response = $this->request( + [ + 'compatibility_data' => $compatibility_data, + ], + self::COMPATIBILITY_API, + self::POST + ); + + return $response; + } + /** * Sends a request object. * diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php new file mode 100644 index 00000000000..70e86b5d2dc --- /dev/null +++ b/tests/unit/test-class-compatibility-service.php @@ -0,0 +1,61 @@ +mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + $this->compatibility_service = new Compatibility_Service( $this->mock_api_client ); + $this->compatibility_service->init_hooks(); + } + + public function test_registers_woocommerce_filters_properly() { + $priority = has_filter( 'woocommerce_payments_account_refreshed', [ $this->compatibility_service, 'update_compatibility_data' ] ); + $this->assertEquals( 10, $priority ); + } + + public function test_update_compatibility_data() { + // Arrange: Create the expected value to be passed to update_compatibility_data. + $expected = [ + 'woopayments_version' => WCPAY_VERSION_NUMBER, + 'woocommerce_version' => WC_VERSION, + ]; + + // Arrange/Assert: Set the expectations for update_compatibility_data. + $this->mock_api_client + ->expects( $this->once() ) + ->method( 'update_compatibility_data' ) + ->with( $expected ); + + // Act: Call the method we're testing. + $this->compatibility_service->update_compatibility_data(); + } +} diff --git a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php index 4476cce139a..d6320c14bd0 100644 --- a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php +++ b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php @@ -907,7 +907,7 @@ public function test_get_disputes_summary_success() { $this->assertSame( 12, $disputes_summary['data']['count'] ); } - public function get_onboarding_po_eligible() { + public function test_get_onboarding_po_eligible() { $this->set_http_mock_response( 200, [ @@ -1203,6 +1203,33 @@ public function test_request_doesnt_retry_get_without_idempotency_header_on_netw [ [], 'intentions', 'GET' ] ); } + + public function test_update_compatibility_data() { + // Arrange: Set expectation and return for remote_request. + $this->mock_http_client + ->expects( $this->once() ) + ->method( 'remote_request' ) + ->willReturn( + [ + 'body' => wp_json_encode( [ 'result' => 'success' ] ), + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + ] + ); + + // Act: Get the result of updating the data. + $result = $this->payments_api_client->update_compatibility_data( + [ + 'woocommerce_core_version' => WC_VERSION, + ] + ); + + // Assert: Confirm we get the expected response. + $this->assertSame( 'success', $result['result'] ); + } + /** * Set up http mock response. * From 6da89587890b77f813e5987362287e84c7102413 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 14 Dec 2023 17:13:28 +0100 Subject: [PATCH 27/56] Descriptive error message when payment method is invalid (#7842) Co-authored-by: Dat Hoang --- changelog/fix-7301-descriptive-error-message-on-invalid-pm | 4 ++++ includes/class-payment-information.php | 2 +- tests/unit/test-class-payment-information.php | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-7301-descriptive-error-message-on-invalid-pm diff --git a/changelog/fix-7301-descriptive-error-message-on-invalid-pm b/changelog/fix-7301-descriptive-error-message-on-invalid-pm new file mode 100644 index 00000000000..798235980af --- /dev/null +++ b/changelog/fix-7301-descriptive-error-message-on-invalid-pm @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Improved error message for invalid payment method diff --git a/includes/class-payment-information.php b/includes/class-payment-information.php index 8a27682dd7a..1f95a05887d 100644 --- a/includes/class-payment-information.php +++ b/includes/class-payment-information.php @@ -134,7 +134,7 @@ public function __construct( if ( empty( $payment_method ) && empty( $token ) && ! \WC_Payments::is_network_saved_cards_enabled() ) { // If network-wide cards are enabled, a payment method or token may not be specified and the platform default one will be used. throw new Invalid_Payment_Method_Exception( - __( 'Invalid payment method. Please input a new card number.', 'woocommerce-payments' ), + __( 'Invalid or missing payment details. Please ensure the provided payment method is correctly entered.', 'woocommerce-payments' ), 'payment_method_not_provided' ); } diff --git a/tests/unit/test-class-payment-information.php b/tests/unit/test-class-payment-information.php index 3da65eb1501..b6cfd8c3960 100644 --- a/tests/unit/test-class-payment-information.php +++ b/tests/unit/test-class-payment-information.php @@ -34,7 +34,7 @@ public function set_up() { public function test_requires_payment_method_or_token() { $this->expectException( Exception::class ); - $this->expectExceptionMessage( 'Invalid payment method. Please input a new card number.' ); + $this->expectExceptionMessage( 'Invalid or missing payment details. Please ensure the provided payment method is correctly entered.' ); $payment_information = new Payment_Information( '' ); } From 066e8763c9a2fb86285da0cd2fdd1983102275f8 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Thu, 14 Dec 2023 17:19:15 +0100 Subject: [PATCH 28/56] Use CardNotice component for authorization notification in transaction details page (#7891) Co-authored-by: Vladimir Reznichenko --- ...748-capture-notification-styles-are-broken | 4 + client/payment-details/summary/index.tsx | 133 ++++++++---------- .../test/__snapshots__/index.test.tsx.snap | 17 ++- 3 files changed, 76 insertions(+), 78 deletions(-) create mode 100644 changelog/fix-7748-capture-notification-styles-are-broken diff --git a/changelog/fix-7748-capture-notification-styles-are-broken b/changelog/fix-7748-capture-notification-styles-are-broken new file mode 100644 index 00000000000..348106f98f8 --- /dev/null +++ b/changelog/fix-7748-capture-notification-styles-are-broken @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed broken styles in authorization capture notifications diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index 6112a27e86f..34887da95db 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -5,13 +5,7 @@ */ import { __ } from '@wordpress/i18n'; import { dateI18n } from '@wordpress/date'; -import { - Card, - CardBody, - CardFooter, - CardDivider, - Flex, -} from '@wordpress/components'; +import { Card, CardBody, CardDivider, Flex } from '@wordpress/components'; import moment from 'moment'; import React, { useContext } from 'react'; import { createInterpolateElement } from '@wordpress/element'; @@ -55,6 +49,7 @@ import MissingOrderNotice from 'wcpay/payment-details/summary/missing-order-noti import DisputeAwaitingResponseDetails from '../dispute-details/dispute-awaiting-response-details'; import DisputeResolutionFooter from '../dispute-details/dispute-resolution-footer'; import ErrorBoundary from 'components/error-boundary'; +import CardNotice from 'wcpay/components/card-notice'; declare const window: any; @@ -508,73 +503,69 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { authorization && ! authorization.captured && ( - -
-
- { createInterpolateElement( - __( - 'You must capture this charge within the next', - 'woocommerce-payments' - ), - { - a: ( - // eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-no-target-blank - - ), + - - { moment - .utc( authorization.created ) - .add( 7, 'days' ) - .fromNow( true ) } - - - { isFraudOutcomeReview && - `. ${ __( - 'Approving this transaction will capture the charge.', - 'woocommerce-payments' - ) }` } -
- - { ! isFraudOutcomeReview && ( - + ), + } + ) }{ ' ' } + - + > + + { moment + .utc( authorization.created ) + .add( 7, 'days' ) + .fromNow( true ) } + + + { isFraudOutcomeReview && + `. ${ __( + 'Approving this transaction will capture the charge.', + 'woocommerce-payments' + ) }` } + ) } diff --git a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap index 02f35f6c1db..30c5c237e49 100644 --- a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap @@ -244,15 +244,15 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders ca
@@ -2238,11 +2238,11 @@ exports[`PaymentDetailsSummary renders partially refunded information for a char >

Refunded: - $-12.00 + -$12.00

Fees: - $-0.70 + -$0.70

@@ -2502,7 +2502,7 @@ exports[`PaymentDetailsSummary renders the Tap to Pay channel from metadata 1`]

Fees: - $-0.70 + -$0.70

@@ -2762,7 +2762,7 @@ exports[`PaymentDetailsSummary renders the information of a dispute-reversal cha

Fees: - $-0.70 + -$0.70

diff --git a/client/payment-details/summary/test/index.test.tsx b/client/payment-details/summary/test/index.test.tsx index 58de0645616..522c613d5ff 100755 --- a/client/payment-details/summary/test/index.test.tsx +++ b/client/payment-details/summary/test/index.test.tsx @@ -251,7 +251,7 @@ describe( 'PaymentDetailsSummary', () => { } ); const container = renderCharge( charge ); - screen.getByText( /Refunded: \$-20.00/i ); + screen.getByText( /Refunded: -\$20.00/i ); expect( container ).toMatchSnapshot(); } ); diff --git a/client/payment-details/timeline/test/__snapshots__/index.js.snap b/client/payment-details/timeline/test/__snapshots__/index.js.snap index df78052123c..e4753b57f06 100644 --- a/client/payment-details/timeline/test/__snapshots__/index.js.snap +++ b/client/payment-details/timeline/test/__snapshots__/index.js.snap @@ -1494,7 +1494,7 @@ exports[`PaymentDetailsTimeline renders subscription fee correctly 1`] = ` > - Fee (3.9% + $0.30): $-0.34 + Fee (3.9% + $0.30): -$0.34

    @@ -412,7 +412,7 @@ Array [ Variable fee: -4.9%
  • - Fixed fee: £-0.20 + Fixed fee: -£0.20
@@ -455,7 +455,7 @@ Array [ Object { "body": Array [ undefined, - "Base fee (1.95% + $0.15): $-3.50", + "Base fee (1.95% + $0.15): -$3.50", undefined, "Net deposit: $59.50 USD", ], @@ -695,7 +695,7 @@ Array [ Object { "body": Array [ undefined, - "Fee (2.6% + $0.20): $-0.61", + "Fee (2.6% + $0.20): -$0.61",
    @@ -745,7 +745,7 @@ Array [ Object { "body": Array [ undefined, - "Fee (1.95% + $0.15): $-3.50", + "Fee (1.95% + $0.15): -$3.50", undefined, "Net deposit: $59.50 USD", ], diff --git a/client/transactions/list/test/__snapshots__/index.tsx.snap b/client/transactions/list/test/__snapshots__/index.tsx.snap index c7bfca77b45..3e5c77a9a7a 100644 --- a/client/transactions/list/test/__snapshots__/index.tsx.snap +++ b/client/transactions/list/test/__snapshots__/index.tsx.snap @@ -542,7 +542,7 @@ exports[`Transactions list renders correctly when can filter by several currenci href="admin.php?page=wc-admin&path=%2Fpayments%2Ftransactions%2Fdetails&id=pi_mock&transaction_id=txn_j23jda9JJa&transaction_type=refund" tabindex="-1" > - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.75 + -$0.75 - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.75 + -$0.75 - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.75 + -$0.75 - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.75 + -$0.75 - $-0.50 + -$0.50 - $-0.50 + -$0.50 - $-0.75 + -$0.75 = 0 ) { - return $formatted; - } - - // Handle the subtle display difference for the negative amount between PHP wc_price `-$0.74` vs JavaScript formatCurrency `$-0.74` for the same input. - // Remove the minus sign, and then move it right before the number. - $formatted = str_replace( '-', '', $formatted ); - - return preg_replace( '/([0-9,\.]+)/', '-$1', $formatted ); + return $formatted; } /** diff --git a/tests/fixtures/captured-payments/discount.json b/tests/fixtures/captured-payments/discount.json index 939a6b7bb8d..af1b0f8cf77 100644 --- a/tests/fixtures/captured-payments/discount.json +++ b/tests/fixtures/captured-payments/discount.json @@ -61,7 +61,7 @@ "discount": { "label": "Discount", "variable": "Variable fee: -4.9%", - "fixed": "Fixed fee: $-0.30" + "fixed": "Fixed fee: -$0.30" } }, "netString": "Net deposit: $105.48 USD" diff --git a/tests/fixtures/captured-payments/foreign-card.json b/tests/fixtures/captured-payments/foreign-card.json index 8f5f7f214c6..9a121e3a7ed 100644 --- a/tests/fixtures/captured-payments/foreign-card.json +++ b/tests/fixtures/captured-payments/foreign-card.json @@ -49,7 +49,7 @@ }, "expectation": { "fxString": "1.00 CAD → 0.774692 USD: $100.71 USD", - "feeString": "Fee (4.9% + $0.30): $-5.24", + "feeString": "Fee (4.9% + $0.30): -$5.24", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-international": "International card fee: 1%", diff --git a/tests/fixtures/captured-payments/fx-decimal.json b/tests/fixtures/captured-payments/fx-decimal.json index 0088ab63680..77a136924f8 100644 --- a/tests/fixtures/captured-payments/fx-decimal.json +++ b/tests/fixtures/captured-payments/fx-decimal.json @@ -42,7 +42,7 @@ }, "expectation": { "fxString": "1.00 EUR → 1.0504 USD: $105.04 USD", - "feeString": "Fee (3.9% + $0.30): $-4.39", + "feeString": "Fee (3.9% + $0.30): -$4.39", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-fx": "Foreign exchange fee: 1%" diff --git a/tests/fixtures/captured-payments/fx-foreign-card.json b/tests/fixtures/captured-payments/fx-foreign-card.json index 7d9ea59e557..9b26ad48fc1 100644 --- a/tests/fixtures/captured-payments/fx-foreign-card.json +++ b/tests/fixtures/captured-payments/fx-foreign-card.json @@ -42,7 +42,7 @@ } }, "expectation": { - "feeString": "Fee (3.9% + $0.30): $-3.20", + "feeString": "Fee (3.9% + $0.30): -$3.20", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-international": "International card fee: 1%" diff --git a/tests/fixtures/captured-payments/fx-with-capped-fee.json b/tests/fixtures/captured-payments/fx-with-capped-fee.json index 1eaecc3e9d7..23c3cbf1387 100644 --- a/tests/fixtures/captured-payments/fx-with-capped-fee.json +++ b/tests/fixtures/captured-payments/fx-with-capped-fee.json @@ -51,7 +51,7 @@ }, "expectation": { "fxString": "1.00 EUR → 1.05483 USD: $1,002.09 USD", - "feeString": "Fee (2.5% + $6.00): $-31.05", + "feeString": "Fee (2.5% + $6.00): -$31.05", "feeBreakdown": { "base": "Base fee: capped at $6.00", "additional-international": "International card fee: 1.5%", diff --git a/tests/fixtures/captured-payments/fx.json b/tests/fixtures/captured-payments/fx.json index 9dfe492153b..166deab4a86 100644 --- a/tests/fixtures/captured-payments/fx.json +++ b/tests/fixtures/captured-payments/fx.json @@ -43,7 +43,7 @@ }, "expectation": { "fxString": "1 VND → 0.000044 USD: $100.04 USD", - "feeString": "Fee (3.9% + $0.30): $-4.20", + "feeString": "Fee (3.9% + $0.30): -$4.20", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-fx": "Foreign exchange fee: 1%" diff --git a/tests/fixtures/captured-payments/jpy-payment.json b/tests/fixtures/captured-payments/jpy-payment.json index 50af38dd483..c5bc6eb7dd6 100644 --- a/tests/fixtures/captured-payments/jpy-payment.json +++ b/tests/fixtures/captured-payments/jpy-payment.json @@ -50,7 +50,7 @@ }, "expectation": { "fxString": "1.00 EUR → 159.13333 JPY: ¥4,774 JPY", - "feeString": "Fee (7.6% + ¥0): ¥-267", + "feeString": "Fee (7.6% + ¥0): -¥267", "feeBreakdown": { "base": "Base fee: 3.6%", "additional-international": "International card fee: 2%", diff --git a/tests/fixtures/captured-payments/only-base-fee.json b/tests/fixtures/captured-payments/only-base-fee.json index 39a3dfccdd4..0385bd61c6f 100644 --- a/tests/fixtures/captured-payments/only-base-fee.json +++ b/tests/fixtures/captured-payments/only-base-fee.json @@ -34,7 +34,7 @@ } }, "expectation": { - "feeString": "Base fee (2.9% + $0.30): $-0.74", + "feeString": "Base fee (2.9% + $0.30): -$0.74", "netString": "Net deposit: $14.26 USD" } } diff --git a/tests/fixtures/captured-payments/subscription.json b/tests/fixtures/captured-payments/subscription.json index 1ee34195c2a..a9a4d01eae4 100644 --- a/tests/fixtures/captured-payments/subscription.json +++ b/tests/fixtures/captured-payments/subscription.json @@ -50,7 +50,7 @@ }, "expectation": { "fxString": "1.00 EUR → 1.05491 USD: $55.91 USD", - "feeString": "Fee (4.9% + $0.30): $-3.04", + "feeString": "Fee (4.9% + $0.30): -$3.04", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-fx": "Foreign exchange fee: 1%", diff --git a/tests/unit/test-class-wc-payments-utils.php b/tests/unit/test-class-wc-payments-utils.php index f9ed09117f6..0248b757f3b 100644 --- a/tests/unit/test-class-wc-payments-utils.php +++ b/tests/unit/test-class-wc-payments-utils.php @@ -518,7 +518,7 @@ public function test_format_currency( float $amount, string $currency, string $e public function provider_format_currency(): array { return [ 'US dollar' => [ 123.456, 'USD', '$123.46' ], - 'US dollar with negative amount' => [ -123.456, 'USD', '$-123.46' ], + 'US dollar with negative amount' => [ -123.456, 'USD', '-$123.46' ], 'Euro' => [ 12000, 'EUR', '12.000,00 €' ], 'CHF - no currency symbol' => [ 123, 'CHF', 'CHF 123.00' ], 'VND - decimal currency' => [ 123456, 'VND', '123.456 ₫' ], From f1cabba6355e295727cffc74ba0d7f138ecc26e7 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Wed, 20 Dec 2023 20:47:57 +0100 Subject: [PATCH 40/56] Add empty file to avoid fatals (#7936) Co-authored-by: Timur Karimov Co-authored-by: Brett Shumaker --- changelog/revert-file-needed-for-plugin-update | 5 +++++ .../class-wc-rest-upe-flag-toggle-controller.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 changelog/revert-file-needed-for-plugin-update create mode 100644 includes/admin/class-wc-rest-upe-flag-toggle-controller.php diff --git a/changelog/revert-file-needed-for-plugin-update b/changelog/revert-file-needed-for-plugin-update new file mode 100644 index 00000000000..f4e5d1e3224 --- /dev/null +++ b/changelog/revert-file-needed-for-plugin-update @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: This is the file revert to avoid failures on plugin update. This is a temporary solution. Both removal & revert happen on develop meaning there is no change to the outside world. + + diff --git a/includes/admin/class-wc-rest-upe-flag-toggle-controller.php b/includes/admin/class-wc-rest-upe-flag-toggle-controller.php new file mode 100644 index 00000000000..bc476de7731 --- /dev/null +++ b/includes/admin/class-wc-rest-upe-flag-toggle-controller.php @@ -0,0 +1,16 @@ + Date: Thu, 21 Dec 2023 11:12:39 +1000 Subject: [PATCH 41/56] Updates WooPayments with the latest from Subscriptions Core library (6.6.0) (#7934) Co-authored-by: Matt Allan --- changelog/subscriptions-6.6.0-1 | 4 ++++ changelog/subscriptions-6.6.0-2 | 4 ++++ changelog/subscriptions-core-6.6.0 | 4 ++++ changelog/subscriptions-core-6.6.0-3 | 4 ++++ changelog/subscriptions-core-6.6.0-4 | 4 ++++ changelog/subscriptions-core-6.6.0-5 | 4 ++++ changelog/subscriptions-core-6.6.0-6 | 4 ++++ changelog/subscriptions-core-6.6.0-7 | 4 ++++ changelog/subscriptions-core-6.6.0-8 | 4 ++++ composer.json | 2 +- composer.lock | 16 ++++++++-------- 11 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 changelog/subscriptions-6.6.0-1 create mode 100644 changelog/subscriptions-6.6.0-2 create mode 100644 changelog/subscriptions-core-6.6.0 create mode 100644 changelog/subscriptions-core-6.6.0-3 create mode 100644 changelog/subscriptions-core-6.6.0-4 create mode 100644 changelog/subscriptions-core-6.6.0-5 create mode 100644 changelog/subscriptions-core-6.6.0-6 create mode 100644 changelog/subscriptions-core-6.6.0-7 create mode 100644 changelog/subscriptions-core-6.6.0-8 diff --git a/changelog/subscriptions-6.6.0-1 b/changelog/subscriptions-6.6.0-1 new file mode 100644 index 00000000000..9c70ea3d4ce --- /dev/null +++ b/changelog/subscriptions-6.6.0-1 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Deprecate the WC_Subscriptions_Synchroniser::add_to_recurring_cart_key(). Use WC_Subscriptions_Synchroniser::add_to_recurring_product_grouping_key() instead. diff --git a/changelog/subscriptions-6.6.0-2 b/changelog/subscriptions-6.6.0-2 new file mode 100644 index 00000000000..98a24e2a8d8 --- /dev/null +++ b/changelog/subscriptions-6.6.0-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Introduce a new wcs_get_subscription_grouping_key() function to generate a unique key for a subscription based on its billing schedule. This function uses the existing recurring cart key concept. diff --git a/changelog/subscriptions-core-6.6.0 b/changelog/subscriptions-core-6.6.0 new file mode 100644 index 00000000000..192de7697f3 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Updated subscriptions-core to version 6.6.0 diff --git a/changelog/subscriptions-core-6.6.0-3 b/changelog/subscriptions-core-6.6.0-3 new file mode 100644 index 00000000000..39e3728713e --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-3 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fetch and update the `_cancelled_email_sent` meta in a HPOS compatibile way. diff --git a/changelog/subscriptions-core-6.6.0-4 b/changelog/subscriptions-core-6.6.0-4 new file mode 100644 index 00000000000..adf5488ac54 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-4 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Ensure proper backfilling of subscription metadata (i.e. dates and cache) to the postmeta table when HPOS is enabled and compatibility mode (data syncing) is turned on. diff --git a/changelog/subscriptions-core-6.6.0-5 b/changelog/subscriptions-core-6.6.0-5 new file mode 100644 index 00000000000..fc15acdb576 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-5 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Resolved an issue that would cause undefined $current_page, $max_num_pages, and $paginate variable errors when viewing a page with the subscriptions-shortcode. diff --git a/changelog/subscriptions-core-6.6.0-6 b/changelog/subscriptions-core-6.6.0-6 new file mode 100644 index 00000000000..df965094736 --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-6 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +When HPOS is enabled and data compatibility mode is turned on, make sure subscription date changes made to postmeta are synced to orders_meta table. diff --git a/changelog/subscriptions-core-6.6.0-7 b/changelog/subscriptions-core-6.6.0-7 new file mode 100644 index 00000000000..96c2cae1f2c --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-7 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Prevents a PHP fatal error that occurs when the cart contains a renewal order item that no longer exists. diff --git a/changelog/subscriptions-core-6.6.0-8 b/changelog/subscriptions-core-6.6.0-8 new file mode 100644 index 00000000000..a2ffa0feb0c --- /dev/null +++ b/changelog/subscriptions-core-6.6.0-8 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +When using the checkout block to pay for renewal orders, ensure the order's cart hash is updated to make sure the existing order can be used. diff --git a/composer.json b/composer.json index 01444821e33..0b2890b6228 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "automattic/jetpack-autoloader": "2.11.18", "automattic/jetpack-identity-crisis": "0.8.43", "automattic/jetpack-sync": "1.47.7", - "woocommerce/subscriptions-core": "6.4.0" + "woocommerce/subscriptions-core": "6.6.0" }, "require-dev": { "composer/installers": "1.10.0", diff --git a/composer.lock b/composer.lock index 54c7ccceb4e..686f8f9aa92 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9dfc107cbaa90afb69bed66432c8cf70", + "content-hash": "2f207d4579d3db832302089bb6f3a11f", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -940,16 +940,16 @@ }, { "name": "woocommerce/subscriptions-core", - "version": "6.4.0", + "version": "6.6.0", "source": { "type": "git", "url": "https://github.com/Automattic/woocommerce-subscriptions-core.git", - "reference": "a94c9aab6d47f32461974ed09a4d3cad590f25b0" + "reference": "5abcf9aac4e53ad9bdcf3752a34a04ae42261bac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/a94c9aab6d47f32461974ed09a4d3cad590f25b0", - "reference": "a94c9aab6d47f32461974ed09a4d3cad590f25b0", + "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/5abcf9aac4e53ad9bdcf3752a34a04ae42261bac", + "reference": "5abcf9aac4e53ad9bdcf3752a34a04ae42261bac", "shasum": "" }, "require": { @@ -960,7 +960,7 @@ "dave-liddament/sarb": "^1.1", "phpunit/phpunit": "9.5.14", "woocommerce/woocommerce-sniffs": "0.1.0", - "yoast/phpunit-polyfills": "1.0.3" + "yoast/phpunit-polyfills": "1.1.0" }, "type": "wordpress-plugin", "extra": { @@ -990,10 +990,10 @@ "description": "Sell products and services with recurring payments in your WooCommerce Store.", "homepage": "https://github.com/Automattic/woocommerce-subscriptions-core", "support": { - "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.4.0", + "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.6.0", "issues": "https://github.com/Automattic/woocommerce-subscriptions-core/issues" }, - "time": "2023-10-18T03:32:50+00:00" + "time": "2023-12-20T07:19:09+00:00" } ], "packages-dev": [ From f73f0df93f336101ffe995ec47849c0a8a60b589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Janisels?= Date: Thu, 21 Dec 2023 09:09:35 +0200 Subject: [PATCH 42/56] Refund transaction from details page (#7742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kārlis Janisels Co-authored-by: Vladimir Reznichenko Co-authored-by: Miguel Gasca --- ...-7248-refund-transaction-from-details-page | 4 + client/data/payment-intents/actions.ts | 63 +++++ client/data/payment-intents/hooks.ts | 20 +- client/data/payment-intents/test/hooks.ts | 13 ++ .../test/__snapshots__/index.test.tsx.snap | 3 + client/payment-details/summary/index.tsx | 116 +++++++++- .../summary/missing-order-notice/index.tsx | 136 +++-------- .../test/__snapshots__/index.test.tsx.snap | 34 +++ .../missing-order-notice/test/index.test.tsx | 39 ++++ .../summary/refund-modal/index.tsx | 127 ++++++++++ .../test/__snapshots__/index.test.tsx.snap | 7 + .../summary/refund-modal/test/index.test.tsx | 92 ++++++++ client/payment-details/summary/style.scss | 10 + .../test/__snapshots__/index.test.tsx.snap | 218 +++++++++++++++++- .../test/__snapshots__/index.test.tsx.snap | 74 ++++++ client/payment-details/types.ts | 1 + client/settings/wcpay-settings-context.js | 1 - ...ss-wc-rest-payments-refunds-controller.php | 80 +++++++ includes/class-wc-payment-gateway-wcpay.php | 4 + includes/class-wc-payments.php | 4 + .../server/request/class-refund-charge.php | 27 +++ ...ss-wc-rest-payments-refunds-controller.php | 73 ++++++ tests/unit/bootstrap.php | 1 + 23 files changed, 1023 insertions(+), 124 deletions(-) create mode 100644 changelog/add-7248-refund-transaction-from-details-page create mode 100644 client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap create mode 100644 client/payment-details/summary/missing-order-notice/test/index.test.tsx create mode 100644 client/payment-details/summary/refund-modal/index.tsx create mode 100644 client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap create mode 100644 client/payment-details/summary/refund-modal/test/index.test.tsx create mode 100644 includes/admin/class-wc-rest-payments-refunds-controller.php create mode 100644 tests/unit/admin/test-class-wc-rest-payments-refunds-controller.php diff --git a/changelog/add-7248-refund-transaction-from-details-page b/changelog/add-7248-refund-transaction-from-details-page new file mode 100644 index 00000000000..bfd753860e8 --- /dev/null +++ b/changelog/add-7248-refund-transaction-from-details-page @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add refund controls to transaction details view diff --git a/client/data/payment-intents/actions.ts b/client/data/payment-intents/actions.ts index ea74c5036bb..ceae8ef6efa 100644 --- a/client/data/payment-intents/actions.ts +++ b/client/data/payment-intents/actions.ts @@ -1,5 +1,12 @@ /** @format */ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { __, sprintf } from '@wordpress/i18n'; + /** * Internal Dependencies */ @@ -10,6 +17,8 @@ import { UpdateErrorForPaymentIntentAction, UpdatePaymentIntentAction, } from './types'; +import { Charge } from 'wcpay/types/charges'; +import { STORE_NAME } from 'wcpay/data/constants'; export function updatePaymentIntent( id: string, @@ -32,3 +41,57 @@ export function updateErrorForPaymentIntent( error, }; } + +export function* refundCharge( + charge: Charge, + reason: string | null +): Generator { + const paymentIntentId = charge.payment_intent; + try { + yield apiFetch( { + path: `/wc/v3/payments/refund/`, + method: 'post', + data: { + charge_id: charge.id, + amount: charge.amount, + reason: reason, + order_id: charge?.order?.number, + }, + } ); + + yield controls.dispatch( + STORE_NAME, + 'invalidateResolutionForStoreSelector', + 'getTimeline' + ); + + yield controls.dispatch( + STORE_NAME, + 'invalidateResolutionForStoreSelector', + 'getPaymentIntent' + ); + + yield controls.dispatch( + 'core/notices', + 'createSuccessNotice', + sprintf( + // translators: %s payment intent id + __( 'Refunded payment #%s.', 'woocommerce-payments' ), + paymentIntentId + ) + ); + } catch ( error ) { + yield controls.dispatch( + 'core/notices', + 'createErrorNotice', + sprintf( + // translators: %s payment intent id + __( + 'There has been an error refunding the payment #%s. Please try again later.', + 'woocommerce-payments' + ), + paymentIntentId + ) + ); + } +} diff --git a/client/data/payment-intents/hooks.ts b/client/data/payment-intents/hooks.ts index 61f51442fd1..330365219ad 100644 --- a/client/data/payment-intents/hooks.ts +++ b/client/data/payment-intents/hooks.ts @@ -2,19 +2,20 @@ /** * External dependencies */ -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { PaymentIntent } from '../../types/payment-intents'; import { getChargeData } from '../charges'; import { PaymentChargeDetailsResponse } from '../../payment-details/types'; import { STORE_NAME } from '../constants'; +import { Charge } from 'wcpay/types/charges'; export const getIsChargeId = ( id: string ): boolean => -1 !== id.indexOf( 'ch_' ) || -1 !== id.indexOf( 'py_' ); export const usePaymentIntentWithChargeFallback = ( id: string -): PaymentChargeDetailsResponse => - useSelect( +): PaymentChargeDetailsResponse => { + const { data, error, isLoading } = useSelect( ( select ) => { const selectors = select( STORE_NAME ); const isChargeId = getIsChargeId( id ); @@ -52,3 +53,16 @@ export const usePaymentIntentWithChargeFallback = ( }, [ id ] ); + + const { refundCharge } = useDispatch( STORE_NAME ); + + const doRefund = ( charge: Charge, reason: string | null ) => + refundCharge( charge, reason ); + + return { + data, + error, + isLoading, + doRefund, + }; +}; diff --git a/client/data/payment-intents/test/hooks.ts b/client/data/payment-intents/test/hooks.ts index 370816ac23a..e2631e291f2 100644 --- a/client/data/payment-intents/test/hooks.ts +++ b/client/data/payment-intents/test/hooks.ts @@ -110,6 +110,16 @@ describe( 'Payment Intent hooks', () => { ( useSelect as jest.Mock ).mockImplementation( ( cb: ( callback: any ) => jest.Mock ) => cb( selectMock ) ); + + jest.spyOn( + // eslint-disable-next-line @typescript-eslint/no-var-requires + require( '@wordpress/data' ), + 'useDispatch' + ).mockReturnValue( () => { + return { + refundCharge: jest.fn(), // Mock the refundCharge function + }; + } ); } ); describe( 'usePaymentIntentWithChargeFallback', () => { @@ -133,6 +143,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: paymentIntentMock.charge, + doRefund: expect.any( Function ), error: {}, isLoading: false, } ); @@ -158,6 +169,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: paymentIntentMock, + doRefund: expect.any( Function ), error: {}, isLoading: false, } ); @@ -181,6 +193,7 @@ describe( 'Payment Intent hooks', () => { expect( result ).toEqual( { data: {}, + doRefund: expect.any( Function ), error: {}, isLoading: true, } ); diff --git a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap index d02d14d3476..f28176053a7 100644 --- a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap @@ -66,6 +66,9 @@ exports[`Order details page should match the snapshot - Charge without payment i
+

= ( { charge.currency && balance.currency !== charge.currency; const { - featureFlags: { isAuthAndCaptureEnabled, isRefundControlsEnabled }, + featureFlags: { isAuthAndCaptureEnabled }, } = useContext( WCPaySettingsContext ); // We should only fetch the authorization data if the payment is marked for manual capture and it is not already captured. @@ -225,6 +239,7 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { balance.currency ); + const [ isRefundModalOpen, setIsRefundModalOpen ] = useState( false ); return ( @@ -464,6 +479,71 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
+
+ { ! charge?.refunded && charge?.captured && ( + + + { ( { onClose } ) => ( + + { + setIsRefundModalOpen( true ); + wcpayTracks.recordEvent( + 'payments_transactions_details_refund_modal_open', + { + payment_intent_id: + charge.payment_intent, + } + ); + onClose(); + } } + > + { __( + 'Refund in full', + 'woocommerce-payments' + ) } + + { charge.order && ( + { + wcpayTracks.recordEvent( + 'payments_transactions_details_partial_refund', + { + payment_intent_id: + charge.payment_intent, + order_id: + charge.order + ?.number, + } + ); + window.location = + charge.order?.url; + } } + > + { __( + 'Partial refund', + 'woocommerce-payments' + ) } + + ) } + + ) } + + + ) } +
@@ -491,14 +571,28 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { ) } ) } - { isRefundControlsEnabled && - ! _.isEmpty( charge ) && - ! charge.order && ( - - ) } + { isRefundModalOpen && ( + { + setIsRefundModalOpen( false ); + wcpayTracks.recordEvent( + 'payments_transactions_details_refund_modal_close', + { + payment_intent_id: charge.payment_intent, + } + ); + } } + /> + ) } + { ! _.isEmpty( charge ) && ! charge.order && ! isLoading && ( + setIsRefundModalOpen( true ) } + /> + ) } { isAuthAndCaptureEnabled && authorization && ! authorization.captured && ( diff --git a/client/payment-details/summary/missing-order-notice/index.tsx b/client/payment-details/summary/missing-order-notice/index.tsx index 69d7f008760..7b05331ee4c 100644 --- a/client/payment-details/summary/missing-order-notice/index.tsx +++ b/client/payment-details/summary/missing-order-notice/index.tsx @@ -5,10 +5,8 @@ */ import React from 'react'; -import { Button, RadioControl } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; -import interpolateComponents from '@automattic/interpolate-components'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies. @@ -16,131 +14,53 @@ import interpolateComponents from '@automattic/interpolate-components'; import './style.scss'; import CardNotice from 'wcpay/components/card-notice'; -import ConfirmationModal from 'wcpay/components/confirmation-modal'; import Loadable from 'wcpay/components/loadable'; +import { Charge } from 'wcpay/types/charges'; interface MissingOrderNoticeProps { + charge: Charge; isLoading: boolean; - formattedAmount: string; + onButtonClick: () => void; } const MissingOrderNotice: React.FC< MissingOrderNoticeProps > = ( { + charge, isLoading, - formattedAmount, + onButtonClick, } ) => { - const [ isModalOpen, setIsModalOpen ] = useState( false ); - - const [ reason, setReason ] = useState< string | null >( null ); - - const handleOnButtonClick = () => { - setIsModalOpen( true ); - }; - - const handleModalCancel = () => { - setIsModalOpen( false ); - }; - - const handleModalConfirmation = () => { - // TODO: Handle the refund. - }; - return ( <> - { __( 'Refund', 'woocommerce-payments' ) } - + ! charge.refunded ? ( + + ) : ( + <> + ) } > { __( - 'This transaction is not connected to order. Investigate this purchase and refund the transaction as needed.', + 'This transaction is not connected to order. ', 'woocommerce-payments' ) } + { charge.refunded + ? __( + 'It has been refunded and is not a subject for disputes.', + 'woocommerce-payments' + ) + : __( + 'Investigate this purchase and refund the transaction as needed.', + 'woocommerce-payments' + ) } - { isModalOpen && ( - - - - - } - onRequestClose={ handleModalCancel } - > -

- { interpolateComponents( { - mixedString: sprintf( - __( - 'This will issue a full refund of {{strong}}%s{{/strong}} to the customer.', - 'woocommerce-payments' - ), - formattedAmount - ), - components: { - strong: , - }, - } ) } -

- setReason( value ) } - /> -
- ) } ); }; diff --git a/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..64cd813ebc2 --- /dev/null +++ b/client/payment-details/summary/missing-order-notice/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MissingOrderNotice it renders correctly 1`] = ` +
+ +`; + +exports[`MissingOrderNotice renders loading state 1`] = ` +
+ +
+`; diff --git a/client/payment-details/summary/missing-order-notice/test/index.test.tsx b/client/payment-details/summary/missing-order-notice/test/index.test.tsx new file mode 100644 index 00000000000..97758906fdc --- /dev/null +++ b/client/payment-details/summary/missing-order-notice/test/index.test.tsx @@ -0,0 +1,39 @@ +/** @format */ + +/** + * External dependencies + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { chargeMock } from 'wcpay/data/payment-intents/test/hooks'; + +/** + * Internal dependencies + */ +import MissingOrderNotice from '..'; +import { Charge } from 'wcpay/types/charges'; + +describe( 'MissingOrderNotice', () => { + test( 'it renders correctly', () => { + const { container: notice } = render( + + ); + + expect( notice ).toMatchSnapshot(); + } ); + + test( 'renders loading state', () => { + const { container: notice } = render( + + ); + expect( notice ).toMatchSnapshot(); + } ); +} ); diff --git a/client/payment-details/summary/refund-modal/index.tsx b/client/payment-details/summary/refund-modal/index.tsx new file mode 100644 index 00000000000..0127e379210 --- /dev/null +++ b/client/payment-details/summary/refund-modal/index.tsx @@ -0,0 +1,127 @@ +/** @format **/ + +/** + * External dependencies + */ + +import React from 'react'; +import { Button, RadioControl } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import interpolateComponents from '@automattic/interpolate-components'; + +/** + * Internal dependencies. + */ + +import ConfirmationModal from 'wcpay/components/confirmation-modal'; +import { Charge } from 'wcpay/types/charges'; +import { usePaymentIntentWithChargeFallback } from 'wcpay/data'; +import { PaymentChargeDetailsResponse } from 'wcpay/payment-details/types'; +import wcpayTracks from 'tracks'; + +interface RefundModalProps { + charge: Charge; + formattedAmount: string; + onModalClose: () => void; +} + +const RefundModal: React.FC< RefundModalProps > = ( { + charge, + formattedAmount, + onModalClose, +} ) => { + const [ reason, setReason ] = useState< string | null >( null ); + + const [ isRefundInProgress, setIsRefundInProgress ] = useState< boolean >( + false + ); + + const { doRefund } = usePaymentIntentWithChargeFallback( + charge.payment_intent as string + ) as PaymentChargeDetailsResponse; + + const handleModalCancel = () => { + onModalClose(); + }; + + const handleRefund = async () => { + wcpayTracks.recordEvent( 'payments_transactions_details_refund_full', { + payment_intent_id: charge.payment_intent, + } ); + setIsRefundInProgress( true ); + await doRefund( charge, reason === 'other' ? null : reason ); + setIsRefundInProgress( false ); + handleModalCancel(); + }; + + return ( + + + + + } + onRequestClose={ handleModalCancel } + > +

+ { interpolateComponents( { + mixedString: sprintf( + __( + 'This will issue a full refund of {{strong}}%s{{/strong}} to the customer.', + 'woocommerce-payments' + ), + formattedAmount + ), + components: { + strong: , + }, + } ) } +

+ setReason( value ) } + /> +
+ ); +}; + +export default RefundModal; diff --git a/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..251b5f8438f --- /dev/null +++ b/client/payment-details/summary/refund-modal/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RefundModal it renders correctly 1`] = ` +
+

+

+
+
+ +
+

+
+
+ +
+

+
+
+ +
+

- This transaction is not connected to order. Investigate this purchase and refund the transaction as needed. + This transaction is not connected to order. + Investigate this purchase and refund the transaction as needed.
+
+
+ +
+

+

+

+
+
+ +
+

+
+
+ +
+

+
+
+ +
+

+
+ + + +

+
+
+ +
+

+