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,
|
useCallback,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { useStoreNotices, useEmitResponse } from '@woocommerce/base-hooks';
|
import {
|
||||||
|
useCheckoutNotices,
|
||||||
|
useStoreNotices,
|
||||||
|
useEmitResponse,
|
||||||
|
usePrevious,
|
||||||
|
} from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -103,6 +108,11 @@ export const CheckoutStateProvider = ( {
|
||||||
isFailResponse,
|
isFailResponse,
|
||||||
shouldRetry,
|
shouldRetry,
|
||||||
} = useEmitResponse();
|
} = useEmitResponse();
|
||||||
|
const {
|
||||||
|
checkoutNotices,
|
||||||
|
paymentNotices,
|
||||||
|
expressPaymentNotices,
|
||||||
|
} = useCheckoutNotices();
|
||||||
|
|
||||||
// set observers on ref so it's always current.
|
// set observers on ref so it's always current.
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
@ -210,7 +220,16 @@ export const CheckoutStateProvider = ( {
|
||||||
dispatch,
|
dispatch,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
|
const previousStatus = usePrevious( checkoutState.status );
|
||||||
|
const previousHasError = usePrevious( checkoutState.hasError );
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
if (
|
||||||
|
checkoutState.status === previousStatus &&
|
||||||
|
checkoutState.hasError === previousHasError
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ( checkoutState.status === STATUS.AFTER_PROCESSING ) {
|
if ( checkoutState.status === STATUS.AFTER_PROCESSING ) {
|
||||||
const data = {
|
const data = {
|
||||||
redirectUrl: checkoutState.redirectUrl,
|
redirectUrl: checkoutState.redirectUrl,
|
||||||
|
@ -244,17 +263,30 @@ export const CheckoutStateProvider = ( {
|
||||||
dispatch( actions.setIdle() );
|
dispatch( actions.setIdle() );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no error handling in place by anything so let's fall
|
const hasErrorNotices =
|
||||||
// back to default
|
checkoutNotices.some(
|
||||||
const message =
|
( notice ) => notice.status === 'error'
|
||||||
data.processingResponse?.message ||
|
) ||
|
||||||
__(
|
expressPaymentNotices.some(
|
||||||
'Something went wrong. Please contact us to get assistance.',
|
( notice ) => notice.status === 'error'
|
||||||
'woo-gutenberg-products-block'
|
) ||
|
||||||
|
paymentNotices.some(
|
||||||
|
( notice ) => notice.status === 'error'
|
||||||
);
|
);
|
||||||
addErrorNotice( message, {
|
if ( ! hasErrorNotices ) {
|
||||||
id: 'checkout',
|
// no error handling in place by anything so let's fall
|
||||||
} );
|
// back to default
|
||||||
|
const message =
|
||||||
|
data.processingResponse?.message ||
|
||||||
|
__(
|
||||||
|
'Something went wrong. Please contact us to get assistance.',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
);
|
||||||
|
addErrorNotice( message, {
|
||||||
|
id: 'checkout',
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
dispatch( actions.setIdle() );
|
dispatch( actions.setIdle() );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
@ -280,7 +312,7 @@ export const CheckoutStateProvider = ( {
|
||||||
dispatch( actions.setComplete( response ) );
|
dispatch( actions.setComplete( response ) );
|
||||||
} else {
|
} else {
|
||||||
// this will set an error which will end up
|
// this will set an error which will end up
|
||||||
// triggering the onCheckoutAfterProcessingWithErrors emitter.
|
// triggering the onCheckoutAfterProcessingWithError emitter.
|
||||||
// and then setting checkout to IDLE state.
|
// and then setting checkout to IDLE state.
|
||||||
dispatch( actions.setHasError( true ) );
|
dispatch( actions.setHasError( true ) );
|
||||||
}
|
}
|
||||||
|
@ -300,11 +332,17 @@ export const CheckoutStateProvider = ( {
|
||||||
checkoutState.customerId,
|
checkoutState.customerId,
|
||||||
checkoutState.customerNote,
|
checkoutState.customerNote,
|
||||||
checkoutState.processingResponse,
|
checkoutState.processingResponse,
|
||||||
|
previousStatus,
|
||||||
|
previousHasError,
|
||||||
dispatchActions,
|
dispatchActions,
|
||||||
addErrorNotice,
|
addErrorNotice,
|
||||||
isErrorResponse,
|
isErrorResponse,
|
||||||
isFailResponse,
|
isFailResponse,
|
||||||
isSuccessResponse,
|
isSuccessResponse,
|
||||||
|
shouldRetry,
|
||||||
|
checkoutNotices,
|
||||||
|
expressPaymentNotices,
|
||||||
|
paymentNotices,
|
||||||
] );
|
] );
|
||||||
|
|
||||||
const onSubmit = useCallback( () => {
|
const onSubmit = useCallback( () => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './use-checkout-redirect-url';
|
export * from './use-checkout-redirect-url';
|
||||||
export * from './use-checkout-address';
|
export * from './use-checkout-address';
|
||||||
|
export * from './use-checkout-notices';
|
||||||
export * from './use-checkout-submit';
|
export * from './use-checkout-submit';
|
||||||
export * from './use-emit-response';
|
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(
|
usePaymentIntents(
|
||||||
stripe,
|
stripe,
|
||||||
onCheckoutAfterProcessingWithSuccess,
|
onCheckoutAfterProcessingWithSuccess,
|
||||||
|
setSourceId,
|
||||||
emitResponse
|
emitResponse
|
||||||
);
|
);
|
||||||
usePaymentProcessing(
|
usePaymentProcessing(
|
||||||
|
|
|
@ -11,14 +11,22 @@ import { useEffect } from '@wordpress/element';
|
||||||
/**
|
/**
|
||||||
* Opens the modal for PaymentIntent authorizations.
|
* Opens the modal for PaymentIntent authorizations.
|
||||||
*
|
*
|
||||||
* @param {Stripe} stripe The stripe object.
|
* @param {Object} params Params object.
|
||||||
* @param {Object} paymentDetails The payment details from the server after checkout
|
* @param {Stripe} params.stripe The stripe object.
|
||||||
* processing.
|
* @param {Object} params.paymentDetails The payment details from the
|
||||||
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer response
|
* server after checkout processing.
|
||||||
* objects.
|
* @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 openIntentModal = ( {
|
||||||
const checkoutResponse = { type: emitResponse.responseTypes.SUCCESS };
|
stripe,
|
||||||
|
paymentDetails,
|
||||||
|
errorContext,
|
||||||
|
errorType,
|
||||||
|
successType,
|
||||||
|
} ) => {
|
||||||
|
const checkoutResponse = { type: successType };
|
||||||
if (
|
if (
|
||||||
! paymentDetails.setup_intent &&
|
! paymentDetails.setup_intent &&
|
||||||
! paymentDetails.payment_intent_secret
|
! paymentDetails.payment_intent_secret
|
||||||
|
@ -49,23 +57,48 @@ const openIntentModal = ( stripe, paymentDetails, emitResponse ) => {
|
||||||
return checkoutResponse;
|
return checkoutResponse;
|
||||||
} )
|
} )
|
||||||
.catch( function ( error ) {
|
.catch( function ( error ) {
|
||||||
checkoutResponse.type = emitResponse.responseTypes.ERROR;
|
checkoutResponse.type = errorType;
|
||||||
checkoutResponse.message = error.message;
|
checkoutResponse.message = error.message;
|
||||||
checkoutResponse.retry = true;
|
checkoutResponse.retry = true;
|
||||||
checkoutResponse.messageContext =
|
checkoutResponse.messageContext = errorContext;
|
||||||
emitResponse.noticeContexts.PAYMENTS;
|
|
||||||
// Reports back to the server.
|
// Reports back to the server.
|
||||||
window.fetch( verificationUrl + '&is_ajax' );
|
window.fetch( verificationUrl + '&is_ajax' );
|
||||||
return checkoutResponse;
|
return checkoutResponse;
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePaymentIntents = ( stripe, subscriber, emitResponse ) => {
|
export const usePaymentIntents = (
|
||||||
|
stripe,
|
||||||
|
subscriber,
|
||||||
|
setSourceId,
|
||||||
|
emitResponse
|
||||||
|
) => {
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const unsubscribe = subscriber( ( { processingResponse } ) => {
|
const unsubscribe = subscriber( async ( { processingResponse } ) => {
|
||||||
const paymentDetails = processingResponse.paymentDetails || {};
|
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();
|
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').CartData} CartData
|
||||||
* @typedef {import('./cart').CartShippingAddress} CartShippingAddress
|
* @typedef {import('./cart').CartShippingAddress} CartShippingAddress
|
||||||
|
* @typedef {import('./contexts').StoreNoticeObject} StoreNoticeObject
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,6 +76,17 @@
|
||||||
* the cart.
|
* 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
|
* @typedef {Object} EmitResponseTypes
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue