* 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:
Albert Juhé Lluveras 2020-10-20 11:50:33 +02:00 committed by GitHub
parent b28c2f56e8
commit 4f60b6367e
6 changed files with 164 additions and 26 deletions

View File

@ -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,17 +263,30 @@ export const CheckoutStateProvider = ( {
dispatch( actions.setIdle() );
}
} else {
// 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'
const hasErrorNotices =
checkoutNotices.some(
( notice ) => notice.status === 'error'
) ||
expressPaymentNotices.some(
( notice ) => notice.status === 'error'
) ||
paymentNotices.some(
( notice ) => notice.status === 'error'
);
addErrorNotice( message, {
id: 'checkout',
} );
if ( ! hasErrorNotices ) {
// 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() );
}
} );
@ -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( () => {

View File

@ -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';

View File

@ -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,
};
};

View File

@ -56,6 +56,7 @@ export const useCheckoutSubscriptions = (
usePaymentIntents(
stripe,
onCheckoutAfterProcessingWithSuccess,
setSourceId,
emitResponse
);
usePaymentProcessing(

View File

@ -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,
] );
};

View File

@ -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
*