From cffafeb6687dc4afee128bc402810fd1c4d414f5 Mon Sep 17 00:00:00 2001 From: Alex Florisca Date: Tue, 14 Feb 2023 12:08:19 +0000 Subject: [PATCH] Refactor payment status (#8110) * WIP * Change payment status from pristine to idle * Deprecate isPaymentStarted and isPaymentFinished * Correct comments * Deprecate isPaymentPristine and undeprecate isPaymentStarted * Set payment status to FAILED or SUCCESS when the storeAPI fetch returns * Remove FINISHED as a status * Remove ready status * Revert "Remove FINISHED as a status" This reverts commit 38d66ed1d9565756d2373533c7a7c5b107a68ddd. * Add payment status READY * Update use-payment-interface * Removed payment statuses pristine, failed and success * Remove deprecated selectors and update docs * Deprecate isPaymentStarted in favour of isExpressPaymentStarted * Fix tests * Update assets/js/base/context/providers/cart-checkout/payment-events/index.tsx Co-authored-by: Mike Jolley * Mikes suggestions * Change since version * Fix tests --------- Co-authored-by: Mike Jolley --- .../use-payment-method-interface.ts | 43 ++++++- .../cart-checkout/checkout-processor.ts | 6 +- .../cart-checkout/payment-events/index.tsx | 73 ++++++------ .../express-payment-methods.js | 12 +- assets/js/data/checkout/constants.ts | 2 - assets/js/data/checkout/reducers.ts | 8 -- assets/js/data/checkout/test/reducer.ts | 17 +-- assets/js/data/payment/action-types.ts | 7 +- assets/js/data/payment/actions.ts | 16 +-- assets/js/data/payment/constants.ts | 7 +- assets/js/data/payment/default-state.ts | 2 +- assets/js/data/payment/reducers.ts | 19 +--- assets/js/data/payment/selectors.ts | 107 ++++++++++++++---- .../test/set-default-payment-method.ts | 7 +- assets/js/data/payment/test/thunks.tsx | 6 +- assets/js/data/payment/thunks.ts | 48 ++++---- .../utils/set-default-payment-method.ts | 2 +- .../type-defs/payment-method-interface.ts | 1 + .../checkout/checkout-flow-and-events.md | 16 ++- .../payment-method-integration.md | 2 +- .../extensibility/data-store/payment.md | 77 +++++++++---- 21 files changed, 284 insertions(+), 194 deletions(-) diff --git a/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts b/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts index e8e2b720eb2..0a8ae83c3a8 100644 --- a/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts +++ b/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts @@ -60,13 +60,46 @@ export const usePaymentMethodInterface = (): PaymentMethodInterface => { return { // The paymentStatus is exposed to third parties via the payment method interface so the API must not be changed paymentStatus: { - isPristine: store.isPaymentPristine(), - isStarted: store.isPaymentStarted(), + get isPristine() { + deprecated( 'isPristine', { + since: '9.6.0', + alternative: 'isIdle', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return store.isPaymentIdle(); + }, // isPristine is the same as isIdle + isIdle: store.isPaymentIdle(), + isStarted: store.isExpressPaymentStarted(), isProcessing: store.isPaymentProcessing(), - isFinished: store.isPaymentFinished(), + get isFinished() { + deprecated( 'isFinished', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return ( + store.hasPaymentError() || store.isPaymentReady() + ); + }, hasError: store.hasPaymentError(), - hasFailed: store.isPaymentFailed(), - isSuccessful: store.isPaymentSuccess(), + get hasFailed() { + deprecated( 'hasFailed', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return store.hasPaymentError(); + }, + get isSuccessful() { + deprecated( 'isSuccessful', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return store.isPaymentReady(); + }, + isReady: store.isPaymentReady(), isDoingExpressPayment: store.isExpressPaymentMethodActive(), }, activePaymentMethod: store.getActivePaymentMethod(), diff --git a/assets/js/base/context/providers/cart-checkout/checkout-processor.ts b/assets/js/base/context/providers/cart-checkout/checkout-processor.ts index 7024107f250..0428c215b5b 100644 --- a/assets/js/base/context/providers/cart-checkout/checkout-processor.ts +++ b/assets/js/base/context/providers/cart-checkout/checkout-processor.ts @@ -92,7 +92,7 @@ const CheckoutProcessor = () => { paymentMethodData, isExpressPaymentMethodActive, hasPaymentError, - isPaymentSuccess, + isPaymentReady, shouldSavePayment, } = useSelect( ( select ) => { const store = select( PAYMENT_STORE_KEY ); @@ -102,7 +102,7 @@ const CheckoutProcessor = () => { paymentMethodData: store.getPaymentMethodData(), isExpressPaymentMethodActive: store.isExpressPaymentMethodActive(), hasPaymentError: store.hasPaymentError(), - isPaymentSuccess: store.isPaymentSuccess(), + isPaymentReady: store.isPaymentReady(), shouldSavePayment: store.getShouldSavePaymentMethod(), }; }, [] ); @@ -130,7 +130,7 @@ const CheckoutProcessor = () => { const paidAndWithoutErrors = ! checkoutHasError && ! checkoutWillHaveError && - ( isPaymentSuccess || ! cartNeedsPayment ) && + ( isPaymentReady || ! cartNeedsPayment ) && checkoutIsProcessing; // Determine if checkout has an error. diff --git a/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx b/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx index 10a38b2cea2..d9b61713597 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx +++ b/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx @@ -61,16 +61,18 @@ export const PaymentEventsProvider = ( { isCalculating: store.isCalculating(), }; } ); - const { isPaymentSuccess, isPaymentFinished, isPaymentProcessing } = - useSelect( ( select ) => { - const store = select( PAYMENT_STORE_KEY ); + const { isPaymentReady } = useSelect( ( select ) => { + const store = select( PAYMENT_STORE_KEY ); - return { - isPaymentSuccess: store.isPaymentSuccess(), - isPaymentFinished: store.isPaymentFinished(), - isPaymentProcessing: store.isPaymentProcessing(), - }; - } ); + return { + // The PROCESSING status represents befor the checkout runs the observers + // registered for the payment_setup event. + isPaymentProcessing: store.isPaymentProcessing(), + // the READY status represents when the observers have finished processing and payment data + // synced with the payment store, ready to be sent to the StoreApi + isPaymentReady: store.isPaymentReady(), + }; + } ); const { setValidationErrors } = useDispatch( VALIDATION_STORE_KEY ); const [ observers, observerDispatch ] = useReducer( emitReducer, {} ); @@ -84,59 +86,50 @@ export const PaymentEventsProvider = ( { const { __internalSetPaymentProcessing, - __internalSetPaymentPristine, + __internalSetPaymentIdle, __internalEmitPaymentProcessingEvent, } = useDispatch( PAYMENT_STORE_KEY ); - // flip payment to processing if checkout processing is complete, there are no errors, and payment status is started. + // flip payment to processing if checkout processing is complete and there are no errors useEffect( () => { if ( checkoutIsProcessing && ! checkoutHasError && - ! checkoutIsCalculating && - ! isPaymentFinished + ! checkoutIsCalculating ) { __internalSetPaymentProcessing(); + + // Note: the nature of this event emitter is that it will bail on any + // observer that returns a response that !== true. However, this still + // allows for other observers that return true for continuing through + // to the next observer (or bailing if there's a problem). + __internalEmitPaymentProcessingEvent( + currentObservers.current, + setValidationErrors + ); } }, [ checkoutIsProcessing, checkoutHasError, checkoutIsCalculating, - isPaymentFinished, __internalSetPaymentProcessing, + __internalEmitPaymentProcessingEvent, + setValidationErrors, ] ); - // When checkout is returned to idle, set payment status to pristine but only if payment status is already not finished. + // When checkout is returned to idle, and the payment setup has not completed, set payment status to idle useEffect( () => { - if ( checkoutIsIdle && ! isPaymentSuccess ) { - __internalSetPaymentPristine(); + if ( checkoutIsIdle && ! isPaymentReady ) { + __internalSetPaymentIdle(); } - }, [ checkoutIsIdle, isPaymentSuccess, __internalSetPaymentPristine ] ); + }, [ checkoutIsIdle, isPaymentReady, __internalSetPaymentIdle ] ); - // if checkout has an error sync payment status back to pristine. + // if checkout has an error sync payment status back to idle. useEffect( () => { - if ( checkoutHasError && isPaymentSuccess ) { - __internalSetPaymentPristine(); + if ( checkoutHasError && isPaymentReady ) { + __internalSetPaymentIdle(); } - }, [ checkoutHasError, isPaymentSuccess, __internalSetPaymentPristine ] ); - - // Emit the payment processing event - useEffect( () => { - // Note: the nature of this event emitter is that it will bail on any - // observer that returns a response that !== true. However, this still - // allows for other observers that return true for continuing through - // to the next observer (or bailing if there's a problem). - if ( isPaymentProcessing ) { - __internalEmitPaymentProcessingEvent( - currentObservers.current, - setValidationErrors - ); - } - }, [ - isPaymentProcessing, - setValidationErrors, - __internalEmitPaymentProcessingEvent, - ] ); + }, [ checkoutHasError, isPaymentReady, __internalSetPaymentIdle ] ); const paymentContextData = { onPaymentProcessing, diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js index c8250967220..c30fa050be1 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js @@ -36,8 +36,8 @@ const ExpressPaymentMethods = () => { ); const { __internalSetActivePaymentMethod, - __internalSetPaymentStarted, - __internalSetPaymentPristine, + __internalSetExpressPaymentStarted, + __internalSetPaymentIdle, __internalSetPaymentError, __internalSetPaymentMethodData, __internalSetExpressPaymentError, @@ -58,14 +58,14 @@ const ExpressPaymentMethods = () => { ( paymentMethodId ) => () => { previousActivePaymentMethod.current = activePaymentMethod; previousPaymentMethodData.current = paymentMethodData; - __internalSetPaymentStarted(); + __internalSetExpressPaymentStarted(); __internalSetActivePaymentMethod( paymentMethodId ); }, [ activePaymentMethod, paymentMethodData, __internalSetActivePaymentMethod, - __internalSetPaymentStarted, + __internalSetExpressPaymentStarted, ] ); @@ -75,12 +75,12 @@ const ExpressPaymentMethods = () => { * This restores the active method and returns the state to pristine. */ const onExpressPaymentClose = useCallback( () => { - __internalSetPaymentPristine(); + __internalSetPaymentIdle(); __internalSetActivePaymentMethod( previousActivePaymentMethod.current, previousPaymentMethodData.current ); - }, [ __internalSetActivePaymentMethod, __internalSetPaymentPristine ] ); + }, [ __internalSetActivePaymentMethod, __internalSetPaymentIdle ] ); /** * onExpressPaymentError should be triggered when the express payment process errors. diff --git a/assets/js/data/checkout/constants.ts b/assets/js/data/checkout/constants.ts index 31bd2b71981..a5b16a1ca82 100644 --- a/assets/js/data/checkout/constants.ts +++ b/assets/js/data/checkout/constants.ts @@ -12,8 +12,6 @@ import { CheckoutResponseSuccess } from '@woocommerce/types'; export const STORE_KEY = 'wc/store/checkout'; export enum STATUS { - // Checkout is in its initialized state. - PRISTINE = 'pristine', // When checkout state has changed but there is no activity happening. IDLE = 'idle', // After the AFTER_PROCESSING event emitters have completed. This status triggers the checkout redirect. diff --git a/assets/js/data/checkout/reducers.ts b/assets/js/data/checkout/reducers.ts index 93cd3c02d6e..fa7845715a3 100644 --- a/assets/js/data/checkout/reducers.ts +++ b/assets/js/data/checkout/reducers.ts @@ -166,14 +166,6 @@ const reducer = ( state = defaultState, action: CheckoutAction ) => { } break; } - - if ( - newState !== state && - action.type !== types.SET_PRISTINE && - newState?.status === STATUS.PRISTINE - ) { - newState.status = STATUS.IDLE; - } return newState; }; diff --git a/assets/js/data/checkout/test/reducer.ts b/assets/js/data/checkout/test/reducer.ts index 4df3e787ef7..f83b5985a99 100644 --- a/assets/js/data/checkout/test/reducer.ts +++ b/assets/js/data/checkout/test/reducer.ts @@ -26,7 +26,6 @@ describe.only( 'Checkout Store Reducer', () => { const expectedState = { ...defaultState, redirectUrl: 'https://example.com', - status: STATUS.IDLE, }; expect( @@ -97,12 +96,15 @@ describe.only( 'Checkout Store Reducer', () => { } ); it( 'should handle SET_HAS_ERROR when status is anything else', () => { - const initialState = { ...defaultState, status: STATUS.PRISTINE }; + const initialState = { + ...defaultState, + status: STATUS.AFTER_PROCESSING, + }; const expectedState = { ...defaultState, hasError: false, - status: STATUS.IDLE, + status: STATUS.AFTER_PROCESSING, }; expect( @@ -135,7 +137,6 @@ describe.only( 'Checkout Store Reducer', () => { it( 'should handle INCREMENT_CALCULATING', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, calculatingCount: 1, }; @@ -152,7 +153,6 @@ describe.only( 'Checkout Store Reducer', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, calculatingCount: 0, }; @@ -164,7 +164,6 @@ describe.only( 'Checkout Store Reducer', () => { it( 'should handle SET_CUSTOMER_ID', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, customerId: 1, }; @@ -176,7 +175,6 @@ describe.only( 'Checkout Store Reducer', () => { it( 'should handle SET_USE_SHIPPING_AS_BILLING', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, useShippingAsBilling: false, }; @@ -191,7 +189,6 @@ describe.only( 'Checkout Store Reducer', () => { it( 'should handle SET_SHOULD_CREATE_ACCOUNT', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, shouldCreateAccount: true, }; @@ -206,7 +203,6 @@ describe.only( 'Checkout Store Reducer', () => { it( 'should handle SET_ORDER_NOTES', () => { const expectedState = { ...defaultState, - status: STATUS.IDLE, orderNotes: 'test', }; @@ -225,7 +221,6 @@ describe.only( 'Checkout Store Reducer', () => { }; const expectedState = { ...defaultState, - status: STATUS.IDLE, extensionData: mockExtensionData, }; expect( @@ -247,7 +242,6 @@ describe.only( 'Checkout Store Reducer', () => { }; const expectedState = { ...defaultState, - status: STATUS.IDLE, extensionData: mockExtensionData, }; const firstState = reducer( @@ -272,7 +266,6 @@ describe.only( 'Checkout Store Reducer', () => { }; const expectedState = { ...defaultState, - status: STATUS.IDLE, extensionData: mockExtensionData, }; const firstState = reducer( diff --git a/assets/js/data/payment/action-types.ts b/assets/js/data/payment/action-types.ts index f73208e3e83..5e4a57613a0 100644 --- a/assets/js/data/payment/action-types.ts +++ b/assets/js/data/payment/action-types.ts @@ -1,10 +1,9 @@ export enum ACTION_TYPES { - SET_PAYMENT_PRISTINE = 'SET_PAYMENT_PRISTINE', - SET_PAYMENT_STARTED = 'SET_PAYMENT_STARTED', + SET_PAYMENT_IDLE = 'SET_PAYMENT_IDLE', + SET_EXPRESS_PAYMENT_STARTED = 'SET_EXPRESS_PAYMENT_STARTED', + SET_PAYMENT_READY = 'SET_PAYMENT_READY', SET_PAYMENT_PROCESSING = 'SET_PAYMENT_PROCESSING', - SET_PAYMENT_FAILED = 'SET_PAYMENT_FAILED', SET_PAYMENT_ERROR = 'SET_PAYMENT_ERROR', - SET_PAYMENT_SUCCESS = 'SET_PAYMENT_SUCCESS', SET_PAYMENT_METHODS_INITIALIZED = 'SET_PAYMENT_METHODS_INITIALIZED', SET_EXPRESS_PAYMENT_METHODS_INITIALIZED = 'SET_EXPRESS_PAYMENT_METHODS_INITIALIZED', SET_ACTIVE_PAYMENT_METHOD = 'SET_ACTIVE_PAYMENT_METHOD', diff --git a/assets/js/data/payment/actions.ts b/assets/js/data/payment/actions.ts index 8ee3d412fee..f1c735111a5 100644 --- a/assets/js/data/payment/actions.ts +++ b/assets/js/data/payment/actions.ts @@ -17,28 +17,24 @@ import { setDefaultPaymentMethod } from './utils/set-default-payment-method'; // `Thunks are functions that can be dispatched, similar to actions creators export * from './thunks'; -export const __internalSetPaymentPristine = () => ( { - type: ACTION_TYPES.SET_PAYMENT_PRISTINE, +export const __internalSetPaymentIdle = () => ( { + type: ACTION_TYPES.SET_PAYMENT_IDLE, } ); -export const __internalSetPaymentStarted = () => ( { - type: ACTION_TYPES.SET_PAYMENT_STARTED, +export const __internalSetExpressPaymentStarted = () => ( { + type: ACTION_TYPES.SET_EXPRESS_PAYMENT_STARTED, } ); export const __internalSetPaymentProcessing = () => ( { type: ACTION_TYPES.SET_PAYMENT_PROCESSING, } ); -export const __internalSetPaymentFailed = () => ( { - type: ACTION_TYPES.SET_PAYMENT_FAILED, -} ); - export const __internalSetPaymentError = () => ( { type: ACTION_TYPES.SET_PAYMENT_ERROR, } ); -export const __internalSetPaymentSuccess = () => ( { - type: ACTION_TYPES.SET_PAYMENT_SUCCESS, +export const __internalSetPaymentReady = () => ( { + type: ACTION_TYPES.SET_PAYMENT_READY, } ); /** diff --git a/assets/js/data/payment/constants.ts b/assets/js/data/payment/constants.ts index f77e56d5aee..9f4ccd0f72b 100644 --- a/assets/js/data/payment/constants.ts +++ b/assets/js/data/payment/constants.ts @@ -1,10 +1,9 @@ export const STORE_KEY = 'wc/store/payment'; export enum STATUS { - PRISTINE = 'pristine', - STARTED = 'started', + IDLE = 'idle', + EXPRESS_STARTED = 'express_started', PROCESSING = 'processing', + READY = 'ready', ERROR = 'has_error', - FAILED = 'failed', - SUCCESS = 'success', } diff --git a/assets/js/data/payment/default-state.ts b/assets/js/data/payment/default-state.ts index 660951c0b04..21e121cb61a 100644 --- a/assets/js/data/payment/default-state.ts +++ b/assets/js/data/payment/default-state.ts @@ -32,7 +32,7 @@ export interface PaymentState { } export const defaultPaymentState: PaymentState = { - status: PAYMENT_STATUS.PRISTINE, + status: PAYMENT_STATUS.IDLE, activePaymentMethod: '', activeSavedToken: '', availablePaymentMethods: {}, diff --git a/assets/js/data/payment/reducers.ts b/assets/js/data/payment/reducers.ts index 18723fb3785..800db3f45fb 100644 --- a/assets/js/data/payment/reducers.ts +++ b/assets/js/data/payment/reducers.ts @@ -17,17 +17,17 @@ const reducer: Reducer< PaymentState > = ( ) => { let newState = state; switch ( action.type ) { - case ACTION_TYPES.SET_PAYMENT_PRISTINE: + case ACTION_TYPES.SET_PAYMENT_IDLE: newState = { ...state, - status: STATUS.PRISTINE, + status: STATUS.IDLE, }; break; - case ACTION_TYPES.SET_PAYMENT_STARTED: + case ACTION_TYPES.SET_EXPRESS_PAYMENT_STARTED: newState = { ...state, - status: STATUS.STARTED, + status: STATUS.EXPRESS_STARTED, }; break; @@ -38,10 +38,10 @@ const reducer: Reducer< PaymentState > = ( }; break; - case ACTION_TYPES.SET_PAYMENT_FAILED: + case ACTION_TYPES.SET_PAYMENT_READY: newState = { ...state, - status: STATUS.FAILED, + status: STATUS.READY, }; break; @@ -52,13 +52,6 @@ const reducer: Reducer< PaymentState > = ( }; break; - case ACTION_TYPES.SET_PAYMENT_SUCCESS: - newState = { - ...state, - status: STATUS.SUCCESS, - }; - break; - case ACTION_TYPES.SET_SHOULD_SAVE_PAYMENT_METHOD: newState = { ...state, diff --git a/assets/js/data/payment/selectors.ts b/assets/js/data/payment/selectors.ts index dd87cd4cdaf..afc815d85ce 100644 --- a/assets/js/data/payment/selectors.ts +++ b/assets/js/data/payment/selectors.ts @@ -14,6 +14,7 @@ import { filterActiveSavedPaymentMethods } from './utils/filter-active-saved-pay import { STATUS as PAYMENT_STATUS } from './constants'; const globalPaymentMethods: Record< string, string > = {}; + if ( getSetting( 'globalPaymentMethods' ) ) { getSetting< GlobalPaymentMethod[] >( 'globalPaymentMethods' ).forEach( ( method ) => { @@ -22,30 +23,62 @@ if ( getSetting( 'globalPaymentMethods' ) ) { ); } -export const isPaymentPristine = ( state: PaymentState ) => - state.status === PAYMENT_STATUS.PRISTINE; +export const isPaymentPristine = ( state: PaymentState ) => { + deprecated( 'isPaymentPristine', { + since: '9.6.0', + alternative: 'isPaymentIdle', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + + return state.status === PAYMENT_STATUS.IDLE; +}; + +export const isPaymentIdle = ( state: PaymentState ) => + state.status === PAYMENT_STATUS.IDLE; -export const isPaymentStarted = ( state: PaymentState ) => - state.status === PAYMENT_STATUS.STARTED; +export const isPaymentStarted = ( state: PaymentState ) => { + deprecated( 'isPaymentStarted', { + since: '9.6.0', + alternative: 'isExpressPaymentStarted', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return state.status === PAYMENT_STATUS.EXPRESS_STARTED; +}; + +export const isExpressPaymentStarted = ( state: PaymentState ) => { + return state.status === PAYMENT_STATUS.EXPRESS_STARTED; +}; export const isPaymentProcessing = ( state: PaymentState ) => state.status === PAYMENT_STATUS.PROCESSING; -export const isPaymentSuccess = ( state: PaymentState ) => - state.status === PAYMENT_STATUS.SUCCESS; +export const isPaymentReady = ( state: PaymentState ) => + state.status === PAYMENT_STATUS.READY; + +export const isPaymentSuccess = ( state: PaymentState ) => { + deprecated( 'isPaymentSuccess', { + since: '9.6.0', + alternative: 'isPaymentReady', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + + return state.status === PAYMENT_STATUS.READY; +}; export const hasPaymentError = ( state: PaymentState ) => state.status === PAYMENT_STATUS.ERROR; -export const isPaymentFailed = ( state: PaymentState ) => - state.status === PAYMENT_STATUS.FAILED; +export const isPaymentFailed = ( state: PaymentState ) => { + deprecated( 'isPaymentFailed', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); -export const isPaymentFinished = ( state: PaymentState ) => { - return ( - state.status === PAYMENT_STATUS.SUCCESS || - state.status === PAYMENT_STATUS.ERROR || - state.status === PAYMENT_STATUS.FAILED - ); + return state.status === PAYMENT_STATUS.ERROR; }; export const isExpressPaymentMethodActive = ( state: PaymentState ) => { @@ -119,26 +152,54 @@ export const expressPaymentMethodsInitialized = ( state: PaymentState ) => { }; /** - * @deprecated - use these selectors instead: isPaymentPristine, isPaymentStarted, isPaymentProcessing, - * isPaymentFinished, hasPaymentError, isPaymentSuccess, isPaymentFailed + * @deprecated - Use these selectors instead: isPaymentIdle, isPaymentProcessing, + * hasPaymentError */ export const getCurrentStatus = ( state: PaymentState ) => { deprecated( 'getCurrentStatus', { since: '8.9.0', - alternative: - 'isPaymentPristine, isPaymentStarted, isPaymentProcessing, isPaymentFinished, hasPaymentError, isPaymentSuccess, isPaymentFailed', + alternative: 'isPaymentIdle, isPaymentProcessing, hasPaymentError', plugin: 'WooCommerce Blocks', link: 'https://github.com/woocommerce/woocommerce-blocks/pull/7666', } ); return { - isPristine: isPaymentPristine( state ), - isStarted: isPaymentStarted( state ), + get isPristine() { + deprecated( 'isPristine', { + since: '9.6.0', + alternative: 'isIdle', + plugin: 'WooCommerce Blocks', + } ); + return isPaymentIdle( state ); + }, // isPristine is the same as isIdle. + isIdle: isPaymentIdle( state ), + isStarted: isExpressPaymentStarted( state ), isProcessing: isPaymentProcessing( state ), - isFinished: isPaymentFinished( state ), + get isFinished() { + deprecated( 'isFinished', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return hasPaymentError( state ) || isPaymentReady( state ); + }, hasError: hasPaymentError( state ), - hasFailed: isPaymentFailed( state ), - isSuccessful: isPaymentSuccess( state ), + get hasFailed() { + deprecated( 'hasFailed', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return hasPaymentError( state ); + }, + get isSuccessful() { + deprecated( 'isSuccessful', { + since: '9.6.0', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110', + } ); + return isPaymentReady( state ); + }, isDoingExpressPayment: isExpressPaymentMethodActive( state ), }; }; diff --git a/assets/js/data/payment/test/set-default-payment-method.ts b/assets/js/data/payment/test/set-default-payment-method.ts index c0ca7ae989c..aa1a21e82c2 100644 --- a/assets/js/data/payment/test/set-default-payment-method.ts +++ b/assets/js/data/payment/test/set-default-payment-method.ts @@ -126,11 +126,10 @@ describe( 'setDefaultPaymentMethod', () => { __internalSetActivePaymentMethod: setActivePaymentMethodMock, __internalSetPaymentError: () => void 0, - __internalSetPaymentFailed: () => void 0, - __internalSetPaymentSuccess: () => void 0, - __internalSetPaymentPristine: () => void 0, - __internalSetPaymentStarted: () => void 0, + __internalSetPaymentIdle: () => void 0, + __internalSetExpressPaymentStarted: () => void 0, __internalSetPaymentProcessing: () => void 0, + __internalSetPaymentReady: () => void 0, }; } return originalStore; diff --git a/assets/js/data/payment/test/thunks.tsx b/assets/js/data/payment/test/thunks.tsx index dfe1a755ec8..d3377df4303 100644 --- a/assets/js/data/payment/test/thunks.tsx +++ b/assets/js/data/payment/test/thunks.tsx @@ -191,7 +191,7 @@ describe( 'wc/store/payment thunks', () => { } ); const setPaymentErrorMock = jest.fn(); - const setPaymentSuccessMock = jest.fn(); + const setPaymentReadyMock = jest.fn(); const registryMock = { dispatch: jest .fn() @@ -211,14 +211,14 @@ describe( 'wc/store/payment thunks', () => { dispatch: { ...wpDataFunctions.dispatch( PAYMENT_STORE_KEY ), __internalSetPaymentError: setPaymentErrorMock, - __internalSetPaymentSuccess: setPaymentSuccessMock, + __internalSetPaymentReady: setPaymentReadyMock, }, } ); // The observer throwing will cause this. //expect( console ).toHaveErroredWith( new Error( 'test error' ) ); expect( setPaymentErrorMock ).toHaveBeenCalled(); - expect( setPaymentSuccessMock ).not.toHaveBeenCalled(); + expect( setPaymentReadyMock ).not.toHaveBeenCalled(); } ); } ); } ); diff --git a/assets/js/data/payment/thunks.ts b/assets/js/data/payment/thunks.ts index ab4f9467b76..d3fb85ab359 100644 --- a/assets/js/data/payment/thunks.ts +++ b/assets/js/data/payment/thunks.ts @@ -67,9 +67,11 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( shippingAddress: ShippingAddress | undefined; observerResponses.forEach( ( response ) => { if ( isSuccessResponse( response ) ) { - // the last observer response always "wins" for success. + // The last observer response always "wins" for success. successResponse = response; } + + // We consider both failed and error responses as an error. if ( isErrorResponse( response ) || isFailResponse( response ) @@ -125,27 +127,24 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( const { setBillingAddress, setShippingAddress } = registry.dispatch( CART_STORE_KEY ); - if ( - isObserverResponse( successResponse ) && - successResponse && - ! errorResponse - ) { + // Observer returned success, we sync the payment method data and billing address. + if ( isObserverResponse( successResponse ) && ! errorResponse ) { const { paymentMethodData } = successResponse?.meta || {}; - if ( billingAddress && isBillingAddress( billingAddress ) ) { + + if ( isBillingAddress( billingAddress ) ) { setBillingAddress( billingAddress ); } - if ( - typeof shippingAddress !== 'undefined' && - isShippingAddress( shippingAddress ) - ) { + if ( isShippingAddress( shippingAddress ) ) { setShippingAddress( shippingAddress ); } - const paymentDataToSet = isObject( paymentMethodData ) - ? paymentMethodData - : {}; - dispatch.__internalSetPaymentMethodData( paymentDataToSet ); - dispatch.__internalSetPaymentSuccess(); + + dispatch.__internalSetPaymentMethodData( + isObject( paymentMethodData ) ? paymentMethodData : {} + ); + dispatch.__internalSetPaymentReady(); } else if ( isFailResponse( errorResponse ) ) { + const { paymentMethodData } = errorResponse?.meta || {}; + if ( objectHasProp( errorResponse, 'message' ) && isString( errorResponse.message ) && @@ -166,16 +165,14 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( } ); } - const { paymentMethodData } = errorResponse?.meta || {}; - if ( billingAddress && isBillingAddress( billingAddress ) ) { + if ( isBillingAddress( billingAddress ) ) { setBillingAddress( billingAddress ); } - dispatch.__internalSetPaymentFailed(); - const paymentDataToSet = isObject( paymentMethodData ) - ? paymentMethodData - : {}; - dispatch.__internalSetPaymentMethodData( paymentDataToSet ); + dispatch.__internalSetPaymentMethodData( + isObject( paymentMethodData ) ? paymentMethodData : {} + ); + dispatch.__internalSetPaymentError(); } else if ( isErrorResponse( errorResponse ) ) { if ( objectHasProp( errorResponse, 'message' ) && @@ -207,9 +204,8 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( setValidationErrors( errorResponse.validationErrors ); } } else { - // otherwise there are no payment methods doing anything so - // just consider success - dispatch.__internalSetPaymentSuccess(); + // Otherwise there are no payment methods doing anything so just assume payment method is ready. + dispatch.__internalSetPaymentReady(); } } ); }; diff --git a/assets/js/data/payment/utils/set-default-payment-method.ts b/assets/js/data/payment/utils/set-default-payment-method.ts index 612284c6748..71fe94e7e76 100644 --- a/assets/js/data/payment/utils/set-default-payment-method.ts +++ b/assets/js/data/payment/utils/set-default-payment-method.ts @@ -60,7 +60,7 @@ export const setDefaultPaymentMethod = async ( return; } - dispatch( PAYMENT_STORE_KEY ).__internalSetPaymentPristine(); + dispatch( PAYMENT_STORE_KEY ).__internalSetPaymentIdle(); dispatch( PAYMENT_STORE_KEY ).__internalSetActivePaymentMethod( paymentMethodKeys[ 0 ] diff --git a/assets/js/types/type-defs/payment-method-interface.ts b/assets/js/types/type-defs/payment-method-interface.ts index b711c1552b0..4b05bb6e924 100644 --- a/assets/js/types/type-defs/payment-method-interface.ts +++ b/assets/js/types/type-defs/payment-method-interface.ts @@ -166,6 +166,7 @@ export type PaymentMethodInterface = { // Various payment status helpers. paymentStatus: { isPristine: boolean; + isIdle: boolean; isStarted: boolean; isProcessing: boolean; isFinished: boolean; diff --git a/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md b/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md index ad0556e6176..2f85077e151 100644 --- a/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md +++ b/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md @@ -109,14 +109,13 @@ import { select } from '@wordpress/data'; import { PAYMENT_STORE_KEY } from '@woocommerce/blocks-data'; const MyComponent = ( props ) => { - const isPaymentPristine = select( PAYMENT_STORE_KEY ).isPaymentPristine(); - const isPaymentStarted = select( PAYMENT_STORE_KEY ).isPaymentStarted(); + const isPaymentIdle = select( PAYMENT_STORE_KEY ).isPaymentIdle(); + const isExpressPaymentStarted = + select( PAYMENT_STORE_KEY ).isExpressPaymentStarted(); const isPaymentProcessing = select( PAYMENT_STORE_KEY ).isPaymentProcessing(); - const isPaymentSuccess = select( PAYMENT_STORE_KEY ).isPaymentSuccess(); - const isPaymentFailed = select( PAYMENT_STORE_KEY ).isPaymentFailed(); + const isPaymentReady = select( PAYMENT_STORE_KEY ).isPaymentReady(); const hasPaymentError = select( PAYMENT_STORE_KEY ).hasPaymentError(); - const hasPaymentFinished = select( PAYMENT_STORE_KEY ).hasPaymentFinished(); // do something with the boolean values }; @@ -126,11 +125,10 @@ The status here will help inform the current state of _client side_ processing f The possible _internal_ statuses that may be set are: -- `PRISTINE`: This is the status when checkout is initialized and there are payment methods that are not doing anything. This status is also set whenever the checkout status is changed to `IDLE`. -- `STARTED`: **Express Payment Methods Only** - This status is used when an express payment method has been triggered by the user clicking it's button. This flow happens before processing, usually in a modal window. +- `IDLE`: This is the status when checkout is initialized and there are payment methods that are not doing anything. This status is also set whenever the checkout status is changed to `IDLE`. +- `EXPRESS_STARTED`: **Express Payment Methods Only** - This status is used when an express payment method has been triggered by the user clicking it's button. This flow happens before processing, usually in a modal window. - `PROCESSING`: This status is set when the checkout status is `PROCESSING`, checkout `hasError` is false, checkout is not calculating, and the current payment status is not `FINISHED`. When this status is set, it will trigger the payment processing event emitter. -- `SUCCESS`: This status is set after all the observers hooked into the payment processing event have completed successfully. The `CheckoutProcessor` component uses this along with the checkout `PROCESSING` status to signal things are ready to send the order to the server with data for processing. -- `FAILED`: This status is set after an observer hooked into the payment processing event returns a fail response. This in turn will end up causing the checkout `hasError` flag to be set to true. +- `READY`: This status is set after all the observers hooked into the payment processing event have completed successfully. The `CheckoutProcessor` component uses this along with the checkout `PROCESSING` status to signal things are ready to send the order to the server with data for processing and to take payment - `ERROR`: This status is set after an observer hooked into the payment processing event returns an error response. This in turn will end up causing the checkout `hasError` flag to be set to true. ### Emitting Events diff --git a/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md b/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md index 3d086b0bb8b..57b7217b205 100644 --- a/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md +++ b/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md @@ -180,7 +180,7 @@ A big part of the payment method integration is the interface that is exposed fo | `shouldSavePayment` | Boolean | Indicates whether or not the shopper has selected to save their payment method details (for payment methods that support saved payments). True if selected, false otherwise. Defaults to false. | - | - `isPristine`: This is true when the current payment status is `PRISTINE`. -- `isStarted`: This is true when the current payment status is `STARTED`. +- `isStarted`: This is true when the current payment status is `EXPRESS_STARTED`. - `isProcessing`: This is true when the current payment status is `PROCESSING`. - `isFinished`: This is true when the current payment status is one of `ERROR`, `FAILED`, or`SUCCESS`. - `hasError`: This is true when the current payment status is `ERROR`. diff --git a/docs/third-party-developers/extensibility/data-store/payment.md b/docs/third-party-developers/extensibility/data-store/payment.md index 53d317142c4..be2313a5ccb 100644 --- a/docs/third-party-developers/extensibility/data-store/payment.md +++ b/docs/third-party-developers/extensibility/data-store/payment.md @@ -27,25 +27,42 @@ with. We do not encourage extensions to dispatch actions onto this data store ye ## Selectors -### isPaymentPristine +### (@deprecated) isPaymentPristine -Queries if the status is `pristine` +_**This selector is deprecated and will be removed in a future release. Please use isPaymentIdle instead**_ #### _Returns_ -`boolean`: True if the payment status is `pristine`, false otherwise. +`boolean`: True if the payment status is `idle`, false otherwise. #### _Example_ ```js const store = select( 'wc/store/payment' ); -const isPaymentPristine = store.isPaymentPristine(); +const isPaymentIdle = store.isPaymentIdle(); ``` -### isPaymentStarted +### isPaymentIdle + +Queries if the status is `idle` + +#### _Returns_ + +`boolean`: True if the payment status is `idle`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentIdle = store.isPaymentIdle(); +``` + +### (@deprecated) isPaymentStarted Queries if the status is `started`. +_**This selector is deprecated and will be removed in a future release. Please use isExpressPaymentStarted instead**_ + #### _Returns_ `boolean`: True if the payment status is `started`, false otherwise. @@ -57,6 +74,23 @@ const store = select( 'wc/store/payment' ); const isPaymentStarted = store.isPaymentStarted(); ``` +### isExpressPaymentStarted + +Queries if an express payment method has been clicked. + +_**This selector is deprecated and will be removed in a future release. Please use isExpressPaymentStarted instead**_ + +#### _Returns_ + +`boolean`: True if the button for an express payment method has been clicked, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentStarted = store.isPaymentStarted(); +``` + ### isPaymentProcessing Queries if the status is `processing`. @@ -72,10 +106,12 @@ const store = select( 'wc/store/payment' ); const isPaymentProcessing = store.isPaymentProcessing(); ``` -### isPaymentSuccess +### (@deprecated) isPaymentSuccess Queries if the status is `success`. +_**This selector is deprecated and will be removed in a future release. Please use isPaymentReady instead**_ + #### _Returns_ `boolean`: True if the payment status is `success`, false otherwise. @@ -87,54 +123,57 @@ const store = select( 'wc/store/payment' ); const isPaymentSuccess = store.isPaymentSuccess(); ``` -### isPaymentFailed +### isPaymentReady -Queries if the status is `failed`. +Queries if the status is `ready`. #### _Returns_ -`boolean`: True if the payment status is `failed`, false otherwise. +`boolean`: True if the payment status is `ready`, false otherwise. #### _Example_ ```js const store = select( 'wc/store/payment' ); -const isPaymentFailed = store.isPaymentFailed(); +const isPaymentReady = store.isPaymentReady(); ``` -### hasPaymentError +### (@deprecated) isPaymentFailed -Queries if the status is `error`. +Queries if the status is `failed`. + +_**This selector is deprecated and will be removed in a future release. Please use hasPaymentError instead**_ #### _Returns_ -`boolean`: True if the payment status is `error`, false otherwise. +`boolean`: True if the payment status is `failed`, false otherwise. #### _Example_ ```js const store = select( 'wc/store/payment' ); -const hasPaymentError = store.hasPaymentError(); +const isPaymentFailed = store.isPaymentFailed(); ``` -### isPaymentFinished +### hasPaymentError -Checks wether the payment has finished processing. This includes failed payments, payments with errors or successful payments. +Queries if the status is `error`. #### _Returns_ -`boolean`: True if the payment status is `success`, `failed` or `error`, false otherwise. +`boolean`: True if the payment status is `error`, false otherwise. #### _Example_ ```js const store = select( 'wc/store/payment' ); -const isPaymentFinished = store.isPaymentFinished(); +const hasPaymentError = store.hasPaymentError(); ``` -### getCurrentStatus (deprecated) +### (@deprecated) getCurrentStatus Returns an object with booleans representing the payment status. + _**This selector is deprecated and will be removed in a future release. Please use the selectors above**_ #### _Returns_