Fix 3D secure payment errors (https://github.com/woocommerce/woocommerce-blocks/pull/3272)
* Add errors to UI in onCheckoutAfterProcessingWithError * Fix missing useEffect dependency * Typo * Reset source id if Stripe intent fails * Remove default error on onCheckoutAfterProcessingWithError * Add missing useEffect dependency * Only add default error message if there is no errors in any other context * Fix useEffect running too many times * Add type-defs and minor improvements * Only count error notices
This commit is contained in:
parent
b28c2f56e8
commit
4f60b6367e
|
@ -11,7 +11,12 @@ import {
|
|||
useCallback,
|
||||
} from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useStoreNotices, useEmitResponse } from '@woocommerce/base-hooks';
|
||||
import {
|
||||
useCheckoutNotices,
|
||||
useStoreNotices,
|
||||
useEmitResponse,
|
||||
usePrevious,
|
||||
} from '@woocommerce/base-hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -103,6 +108,11 @@ export const CheckoutStateProvider = ( {
|
|||
isFailResponse,
|
||||
shouldRetry,
|
||||
} = useEmitResponse();
|
||||
const {
|
||||
checkoutNotices,
|
||||
paymentNotices,
|
||||
expressPaymentNotices,
|
||||
} = useCheckoutNotices();
|
||||
|
||||
// set observers on ref so it's always current.
|
||||
useEffect( () => {
|
||||
|
@ -210,7 +220,16 @@ export const CheckoutStateProvider = ( {
|
|||
dispatch,
|
||||
] );
|
||||
|
||||
const previousStatus = usePrevious( checkoutState.status );
|
||||
const previousHasError = usePrevious( checkoutState.hasError );
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
checkoutState.status === previousStatus &&
|
||||
checkoutState.hasError === previousHasError
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if ( checkoutState.status === STATUS.AFTER_PROCESSING ) {
|
||||
const data = {
|
||||
redirectUrl: checkoutState.redirectUrl,
|
||||
|
@ -244,6 +263,17 @@ export const CheckoutStateProvider = ( {
|
|||
dispatch( actions.setIdle() );
|
||||
}
|
||||
} else {
|
||||
const hasErrorNotices =
|
||||
checkoutNotices.some(
|
||||
( notice ) => notice.status === 'error'
|
||||
) ||
|
||||
expressPaymentNotices.some(
|
||||
( notice ) => notice.status === 'error'
|
||||
) ||
|
||||
paymentNotices.some(
|
||||
( notice ) => notice.status === 'error'
|
||||
);
|
||||
if ( ! hasErrorNotices ) {
|
||||
// no error handling in place by anything so let's fall
|
||||
// back to default
|
||||
const message =
|
||||
|
@ -255,6 +285,8 @@ export const CheckoutStateProvider = ( {
|
|||
addErrorNotice( message, {
|
||||
id: 'checkout',
|
||||
} );
|
||||
}
|
||||
|
||||
dispatch( actions.setIdle() );
|
||||
}
|
||||
} );
|
||||
|
@ -280,7 +312,7 @@ export const CheckoutStateProvider = ( {
|
|||
dispatch( actions.setComplete( response ) );
|
||||
} else {
|
||||
// this will set an error which will end up
|
||||
// triggering the onCheckoutAfterProcessingWithErrors emitter.
|
||||
// triggering the onCheckoutAfterProcessingWithError emitter.
|
||||
// and then setting checkout to IDLE state.
|
||||
dispatch( actions.setHasError( true ) );
|
||||
}
|
||||
|
@ -300,11 +332,17 @@ export const CheckoutStateProvider = ( {
|
|||
checkoutState.customerId,
|
||||
checkoutState.customerNote,
|
||||
checkoutState.processingResponse,
|
||||
previousStatus,
|
||||
previousHasError,
|
||||
dispatchActions,
|
||||
addErrorNotice,
|
||||
isErrorResponse,
|
||||
isFailResponse,
|
||||
isSuccessResponse,
|
||||
shouldRetry,
|
||||
checkoutNotices,
|
||||
expressPaymentNotices,
|
||||
paymentNotices,
|
||||
] );
|
||||
|
||||
const onSubmit = useCallback( () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './use-checkout-redirect-url';
|
||||
export * from './use-checkout-address';
|
||||
export * from './use-checkout-notices';
|
||||
export * from './use-checkout-submit';
|
||||
export * from './use-emit-response';
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEmitResponse } from '@woocommerce/base-hooks';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/contexts').StoreNoticeObject} StoreNoticeObject
|
||||
* @typedef {import('@woocommerce/type-defs/hooks').CheckoutNotices} CheckoutNotices
|
||||
*/
|
||||
|
||||
/**
|
||||
* A hook that returns all notices visible in the Checkout block.
|
||||
*
|
||||
* @return {CheckoutNotices} Notices from the checkout form or payment methods.
|
||||
*/
|
||||
export const useCheckoutNotices = () => {
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
|
||||
/**
|
||||
* @type {StoreNoticeObject[]}
|
||||
*/
|
||||
const checkoutNotices = useSelect(
|
||||
( select ) => select( 'core/notices' ).getNotices( 'wc/checkout' ),
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {StoreNoticeObject[]}
|
||||
*/
|
||||
const expressPaymentNotices = useSelect(
|
||||
( select ) =>
|
||||
select( 'core/notices' ).getNotices(
|
||||
noticeContexts.EXPRESS_PAYMENTS
|
||||
),
|
||||
[ noticeContexts.EXPRESS_PAYMENTS ]
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {StoreNoticeObject[]}
|
||||
*/
|
||||
const paymentNotices = useSelect(
|
||||
( select ) =>
|
||||
select( 'core/notices' ).getNotices( noticeContexts.PAYMENTS ),
|
||||
[ noticeContexts.PAYMENTS ]
|
||||
);
|
||||
|
||||
return {
|
||||
checkoutNotices,
|
||||
expressPaymentNotices,
|
||||
paymentNotices,
|
||||
};
|
||||
};
|
|
@ -56,6 +56,7 @@ export const useCheckoutSubscriptions = (
|
|||
usePaymentIntents(
|
||||
stripe,
|
||||
onCheckoutAfterProcessingWithSuccess,
|
||||
setSourceId,
|
||||
emitResponse
|
||||
);
|
||||
usePaymentProcessing(
|
||||
|
|
|
@ -11,14 +11,22 @@ import { useEffect } from '@wordpress/element';
|
|||
/**
|
||||
* Opens the modal for PaymentIntent authorizations.
|
||||
*
|
||||
* @param {Stripe} stripe The stripe object.
|
||||
* @param {Object} paymentDetails The payment details from the server after checkout
|
||||
* processing.
|
||||
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer response
|
||||
* objects.
|
||||
* @param {Object} params Params object.
|
||||
* @param {Stripe} params.stripe The stripe object.
|
||||
* @param {Object} params.paymentDetails The payment details from the
|
||||
* server after checkout processing.
|
||||
* @param {string} params.errorContext Context where errors will be added.
|
||||
* @param {string} params.errorType Type of error responses.
|
||||
* @param {string} params.successType Type of success responses.
|
||||
*/
|
||||
const openIntentModal = ( stripe, paymentDetails, emitResponse ) => {
|
||||
const checkoutResponse = { type: emitResponse.responseTypes.SUCCESS };
|
||||
const openIntentModal = ( {
|
||||
stripe,
|
||||
paymentDetails,
|
||||
errorContext,
|
||||
errorType,
|
||||
successType,
|
||||
} ) => {
|
||||
const checkoutResponse = { type: successType };
|
||||
if (
|
||||
! paymentDetails.setup_intent &&
|
||||
! paymentDetails.payment_intent_secret
|
||||
|
@ -49,23 +57,48 @@ const openIntentModal = ( stripe, paymentDetails, emitResponse ) => {
|
|||
return checkoutResponse;
|
||||
} )
|
||||
.catch( function ( error ) {
|
||||
checkoutResponse.type = emitResponse.responseTypes.ERROR;
|
||||
checkoutResponse.type = errorType;
|
||||
checkoutResponse.message = error.message;
|
||||
checkoutResponse.retry = true;
|
||||
checkoutResponse.messageContext =
|
||||
emitResponse.noticeContexts.PAYMENTS;
|
||||
checkoutResponse.messageContext = errorContext;
|
||||
// Reports back to the server.
|
||||
window.fetch( verificationUrl + '&is_ajax' );
|
||||
return checkoutResponse;
|
||||
} );
|
||||
};
|
||||
|
||||
export const usePaymentIntents = ( stripe, subscriber, emitResponse ) => {
|
||||
export const usePaymentIntents = (
|
||||
stripe,
|
||||
subscriber,
|
||||
setSourceId,
|
||||
emitResponse
|
||||
) => {
|
||||
useEffect( () => {
|
||||
const unsubscribe = subscriber( ( { processingResponse } ) => {
|
||||
const unsubscribe = subscriber( async ( { processingResponse } ) => {
|
||||
const paymentDetails = processingResponse.paymentDetails || {};
|
||||
return openIntentModal( stripe, paymentDetails, emitResponse );
|
||||
const response = await openIntentModal( {
|
||||
stripe,
|
||||
paymentDetails,
|
||||
errorContext: emitResponse.noticeContexts.PAYMENTS,
|
||||
errorType: emitResponse.responseTypes.ERROR,
|
||||
successType: emitResponse.responseTypes.SUCCESS,
|
||||
} );
|
||||
if (
|
||||
response.type === emitResponse.responseTypes.ERROR &&
|
||||
response.retry
|
||||
) {
|
||||
setSourceId( '0' );
|
||||
}
|
||||
|
||||
return response;
|
||||
} );
|
||||
return () => unsubscribe();
|
||||
}, [ subscriber, stripe ] );
|
||||
}, [
|
||||
subscriber,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
emitResponse.responseTypes.SUCCESS,
|
||||
setSourceId,
|
||||
stripe,
|
||||
] );
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* @typedef {import('./cart').CartData} CartData
|
||||
* @typedef {import('./cart').CartShippingAddress} CartShippingAddress
|
||||
* @typedef {import('./contexts').StoreNoticeObject} StoreNoticeObject
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -75,6 +76,17 @@
|
|||
* the cart.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} CheckoutNotices
|
||||
*
|
||||
* @property {StoreNoticeObject[]} checkoutNotices Array of notices in the
|
||||
* checkout context.
|
||||
* @property {StoreNoticeObject[]} expressPaymentNotices Array of notices in the
|
||||
* express payment context.
|
||||
* @property {StoreNoticeObject[]} paymentNotices Array of notices in the
|
||||
* payment context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmitResponseTypes
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue