Don't throw an error when registering a payment method fails (https://github.com/woocommerce/woocommerce-blocks/pull/3134)
* Show all payment methods when it's an admin and let the error boundary handle errors * Use StoreNoticesContainer in Payment method error boundary so notices have styling * Filter out saved payment methods for admin users if they don't accept payments * Simplify update options logic * For admins, only show payment methods that errored but canPay was not false * Simplify how new payment method option is appended * Wrap canMakePayment in a try catch block to handle payment methods that throw an error * Add an id to payment method error boundary errors * Add an error boundary to express payment methods * Hardcode failing content and savePaymentInfo to false if the payment method failed * Add some new comments * Add a notice instead of registering the payment method if it fails and user is admin * Throw error early if stripe failed to load * Split express and standard payment method error notices * Don't add payment methods in the editor and instead add a notice * Fix error id * Use noticeContext constant * Add missing JSdoc param * Remove unnecessary removeNotice
This commit is contained in:
parent
1871b4e573
commit
d641d2e1a4
|
@ -15,6 +15,7 @@ import {
|
|||
useEditorContext,
|
||||
usePaymentMethodDataContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import PaymentMethodErrorBoundary from './payment-method-error-boundary';
|
||||
|
||||
const ExpressPaymentMethods = () => {
|
||||
const { isEditor } = useEditorContext();
|
||||
|
@ -59,9 +60,11 @@ const ExpressPaymentMethods = () => {
|
|||
<li key="noneRegistered">No registered Payment Methods</li>
|
||||
);
|
||||
return (
|
||||
<ul className="wc-block-components-express-payment__event-buttons">
|
||||
{ content }
|
||||
</ul>
|
||||
<PaymentMethodErrorBoundary isEditor={ isEditor }>
|
||||
<ul className="wc-block-components-express-payment__event-buttons">
|
||||
{ content }
|
||||
</ul>
|
||||
</PaymentMethodErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useExpressPaymentMethods } from '@woocommerce/base-hooks';
|
||||
import { StoreNoticesProvider } from '@woocommerce/base-context';
|
||||
import {
|
||||
useEmitResponse,
|
||||
useExpressPaymentMethods,
|
||||
} from '@woocommerce/base-hooks';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
useEditorContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import Title from '@woocommerce/base-components/title';
|
||||
|
||||
/**
|
||||
|
@ -11,14 +17,26 @@ import Title from '@woocommerce/base-components/title';
|
|||
*/
|
||||
import ExpressPaymentMethods from '../express-payment-methods';
|
||||
import './style.scss';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/block-settings';
|
||||
|
||||
const CheckoutExpressPayment = () => {
|
||||
const { paymentMethods, isInitialized } = useExpressPaymentMethods();
|
||||
const { isEditor } = useEditorContext();
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
|
||||
if (
|
||||
! isInitialized ||
|
||||
( isInitialized && Object.keys( paymentMethods ).length === 0 )
|
||||
) {
|
||||
// Make sure errors are shown in the editor and for admins. For example,
|
||||
// when a payment method fails to register.
|
||||
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
|
||||
return (
|
||||
<StoreNoticesProvider
|
||||
context={ noticeContexts.EXPRESS_PAYMENTS }
|
||||
></StoreNoticesProvider>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -37,7 +55,9 @@ const CheckoutExpressPayment = () => {
|
|||
</Title>
|
||||
</div>
|
||||
<div className="wc-block-components-express-payment__content">
|
||||
<StoreNoticesProvider context="wc/express-payment-area">
|
||||
<StoreNoticesProvider
|
||||
context={ noticeContexts.EXPRESS_PAYMENTS }
|
||||
>
|
||||
<p>
|
||||
{ __(
|
||||
'In a hurry? Use one of our express checkout options below:',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from 'react';
|
||||
import { Notice } from 'wordpress-components';
|
||||
import { StoreNoticesContainer } from '@woocommerce/base-components/store-notices-container';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/block-settings';
|
||||
|
||||
|
@ -36,11 +36,15 @@ class PaymentMethodErrorBoundary extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Notice isDismissible={ false } status="error">
|
||||
{ errorText }
|
||||
</Notice>
|
||||
);
|
||||
const notices = [
|
||||
{
|
||||
id: '0',
|
||||
content: errorText,
|
||||
isDismissible: false,
|
||||
status: 'error',
|
||||
},
|
||||
];
|
||||
return <StoreNoticesContainer notices={ notices } />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
usePaymentMethodDataContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import RadioControl from '@woocommerce/base-components/radio-control';
|
||||
import { getPaymentMethods } from '@woocommerce/blocks-registry';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/contexts').CustomerPaymentMethod} CustomerPaymentMethod
|
||||
|
@ -92,6 +93,7 @@ const SavedPaymentMethodOptions = ( { onSelect } ) => {
|
|||
setActivePaymentMethod,
|
||||
} = usePaymentMethodDataContext();
|
||||
const [ selectedToken, setSelectedToken ] = useState( '' );
|
||||
const standardMethods = getPaymentMethods();
|
||||
|
||||
/**
|
||||
* @type {Object} Options
|
||||
|
@ -99,56 +101,45 @@ const SavedPaymentMethodOptions = ( { onSelect } ) => {
|
|||
*/
|
||||
const currentOptions = useRef( [] );
|
||||
useEffect( () => {
|
||||
let options = [];
|
||||
const paymentMethodKeys = Object.keys( customerPaymentMethods );
|
||||
if ( paymentMethodKeys.length > 0 ) {
|
||||
paymentMethodKeys.forEach( ( type ) => {
|
||||
const paymentMethods = customerPaymentMethods[ type ];
|
||||
if ( paymentMethods.length > 0 ) {
|
||||
options = options.concat(
|
||||
paymentMethods.map( ( paymentMethod ) => {
|
||||
const option =
|
||||
type === 'cc' || type === 'echeck'
|
||||
? getCcOrEcheckPaymentMethodOption(
|
||||
paymentMethod,
|
||||
setActivePaymentMethod,
|
||||
setPaymentStatus
|
||||
)
|
||||
: getDefaultPaymentMethodOptions(
|
||||
paymentMethod,
|
||||
setActivePaymentMethod,
|
||||
setPaymentStatus
|
||||
);
|
||||
if (
|
||||
paymentMethod.is_default &&
|
||||
selectedToken === ''
|
||||
) {
|
||||
setSelectedToken( paymentMethod.tokenId + '' );
|
||||
option.onChange( paymentMethod.tokenId );
|
||||
}
|
||||
return option;
|
||||
} )
|
||||
);
|
||||
}
|
||||
} );
|
||||
if ( options.length > 0 ) {
|
||||
currentOptions.current = options;
|
||||
currentOptions.current.push( {
|
||||
value: '0',
|
||||
label: __(
|
||||
'Use a new payment method',
|
||||
'woo-gutenberg-product-blocks'
|
||||
),
|
||||
name: `wc-saved-payment-method-token-new`,
|
||||
const types = Object.keys( customerPaymentMethods );
|
||||
const options = types
|
||||
.flatMap( ( type ) => {
|
||||
const typeMethods = customerPaymentMethods[ type ];
|
||||
return typeMethods.map( ( paymentMethod ) => {
|
||||
const method =
|
||||
standardMethods[ paymentMethod.method.gateway ];
|
||||
if ( ! method?.supports?.savePaymentInfo ) {
|
||||
return null;
|
||||
}
|
||||
const option =
|
||||
type === 'cc' || type === 'echeck'
|
||||
? getCcOrEcheckPaymentMethodOption(
|
||||
paymentMethod,
|
||||
setActivePaymentMethod,
|
||||
setPaymentStatus
|
||||
)
|
||||
: getDefaultPaymentMethodOptions(
|
||||
paymentMethod,
|
||||
setActivePaymentMethod,
|
||||
setPaymentStatus
|
||||
);
|
||||
if ( paymentMethod.is_default && selectedToken === '' ) {
|
||||
setSelectedToken( paymentMethod.tokenId + '' );
|
||||
option.onChange( paymentMethod.tokenId );
|
||||
}
|
||||
return option;
|
||||
} );
|
||||
}
|
||||
}
|
||||
} )
|
||||
.filter( Boolean );
|
||||
currentOptions.current = options;
|
||||
}, [
|
||||
customerPaymentMethods,
|
||||
selectedToken,
|
||||
setActivePaymentMethod,
|
||||
setPaymentStatus,
|
||||
standardMethods,
|
||||
] );
|
||||
|
||||
const updateToken = useCallback(
|
||||
( token ) => {
|
||||
if ( token === '0' ) {
|
||||
|
@ -167,12 +158,17 @@ const SavedPaymentMethodOptions = ( { onSelect } ) => {
|
|||
|
||||
// In the editor, show `Use a new payment method` option as selected.
|
||||
const selectedOption = isEditor ? '0' : selectedToken + '';
|
||||
const newPaymentMethodOption = {
|
||||
value: '0',
|
||||
label: __( 'Use a new payment method', 'woo-gutenberg-product-blocks' ),
|
||||
name: `wc-saved-payment-method-token-new`,
|
||||
};
|
||||
return currentOptions.current.length > 0 ? (
|
||||
<RadioControl
|
||||
id={ 'wc-payment-method-saved-tokens' }
|
||||
selected={ selectedOption }
|
||||
onChange={ updateToken }
|
||||
options={ currentOptions.current }
|
||||
options={ [ ...currentOptions.current, newPaymentMethodOption ] }
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
|
|
@ -11,32 +11,17 @@ import {
|
|||
useEditorContext,
|
||||
useShippingDataContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import { useStoreCart, useShallowEqual } from '@woocommerce/base-hooks';
|
||||
import {
|
||||
useEmitResponse,
|
||||
useShallowEqual,
|
||||
useStoreCart,
|
||||
useStoreNotices,
|
||||
} from '@woocommerce/base-hooks';
|
||||
import {
|
||||
CURRENT_USER_IS_ADMIN,
|
||||
PAYMENT_GATEWAY_SORT_ORDER,
|
||||
} from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* If there was an error registering a payment method, alert the admin.
|
||||
*
|
||||
* @param {Object} error Error object.
|
||||
*/
|
||||
const handleRegistrationError = ( error ) => {
|
||||
if ( CURRENT_USER_IS_ADMIN ) {
|
||||
throw new Error(
|
||||
sprintf(
|
||||
__(
|
||||
// translators: %s is the error method returned by the payment method.
|
||||
'Problem with payment method initialization: %s',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
error.message
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This hook handles initializing registered payment methods and exposing all
|
||||
* registered payment methods that can be used in the current environment (via
|
||||
|
@ -50,6 +35,8 @@ const handleRegistrationError = ( error ) => {
|
|||
* @param {Array} paymentMethodsSortOrder Array of payment method names to
|
||||
* sort by. This should match keys of
|
||||
* registeredPaymentMethods.
|
||||
* @param {string} noticeContext Id of the context to append
|
||||
* notices to.
|
||||
*
|
||||
* @return {boolean} Whether the payment methods have been initialized or not. True when all payment
|
||||
* methods have been initialized.
|
||||
|
@ -57,7 +44,8 @@ const handleRegistrationError = ( error ) => {
|
|||
const usePaymentMethodRegistration = (
|
||||
dispatcher,
|
||||
registeredPaymentMethods,
|
||||
paymentMethodsSortOrder
|
||||
paymentMethodsSortOrder,
|
||||
noticeContext
|
||||
) => {
|
||||
const [ isInitialized, setIsInitialized ] = useState( false );
|
||||
const { isEditor } = useEditorContext();
|
||||
|
@ -71,6 +59,7 @@ const usePaymentMethodRegistration = (
|
|||
shippingAddress,
|
||||
selectedShippingMethods,
|
||||
} );
|
||||
const { addErrorNotice } = useStoreNotices();
|
||||
|
||||
useEffect( () => {
|
||||
canPayArgument.current = {
|
||||
|
@ -102,12 +91,6 @@ const usePaymentMethodRegistration = (
|
|||
continue;
|
||||
}
|
||||
|
||||
// In editor, shortcut so all payment methods show as available.
|
||||
if ( isEditor ) {
|
||||
addAvailablePaymentMethod( paymentMethod );
|
||||
continue;
|
||||
}
|
||||
|
||||
// In front end, ask payment method if it should be available.
|
||||
try {
|
||||
const canPay = await Promise.resolve(
|
||||
|
@ -120,8 +103,20 @@ const usePaymentMethodRegistration = (
|
|||
addAvailablePaymentMethod( paymentMethod );
|
||||
}
|
||||
} catch ( e ) {
|
||||
// If user is admin, show payment `canMakePayment` errors as a notice.
|
||||
handleRegistrationError( e );
|
||||
if ( CURRENT_USER_IS_ADMIN || isEditor ) {
|
||||
const errorText = sprintf(
|
||||
/* translators: %s the id of the payment method being registered (bank transfer, Stripe...) */
|
||||
__(
|
||||
`There was an error registering the payment method with id '%s': `,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
paymentMethod.paymentMethodId
|
||||
);
|
||||
addErrorNotice( `${ errorText } ${ e }`, {
|
||||
context: noticeContext,
|
||||
id: `wc-${ paymentMethod.paymentMethodId }-registration-error`,
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,10 +128,12 @@ const usePaymentMethodRegistration = (
|
|||
// That's why we track "is initialised" state here.
|
||||
setIsInitialized( true );
|
||||
}, [
|
||||
addErrorNotice,
|
||||
dispatcher,
|
||||
isEditor,
|
||||
registeredPaymentMethods,
|
||||
noticeContext,
|
||||
paymentMethodsOrder,
|
||||
registeredPaymentMethods,
|
||||
] );
|
||||
|
||||
// Determine which payment methods are available initially and whenever
|
||||
|
@ -158,6 +155,7 @@ const usePaymentMethodRegistration = (
|
|||
*/
|
||||
export const usePaymentMethods = ( dispatcher ) => {
|
||||
const standardMethods = getPaymentMethods();
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
// Ensure all methods are present in order.
|
||||
// Some payment methods may not be present in PAYMENT_GATEWAY_SORT_ORDER if they
|
||||
// depend on state, e.g. COD can depend on shipping method.
|
||||
|
@ -168,7 +166,8 @@ export const usePaymentMethods = ( dispatcher ) => {
|
|||
return usePaymentMethodRegistration(
|
||||
dispatcher,
|
||||
standardMethods,
|
||||
Array.from( displayOrder )
|
||||
Array.from( displayOrder ),
|
||||
noticeContexts.PAYMENTS
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -181,9 +180,11 @@ export const usePaymentMethods = ( dispatcher ) => {
|
|||
*/
|
||||
export const useExpressPaymentMethods = ( dispatcher ) => {
|
||||
const expressMethods = getExpressPaymentMethods();
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
return usePaymentMethodRegistration(
|
||||
dispatcher,
|
||||
expressMethods,
|
||||
Object.keys( expressMethods )
|
||||
Object.keys( expressMethods ),
|
||||
noticeContexts.EXPRESS_PAYMENTS
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { __experimentalCreateInterpolateElement } from 'wordpress-element';
|
||||
import { useRef } from '@wordpress/element';
|
||||
import { EditorProvider, useEditorContext } from '@woocommerce/base-context';
|
||||
import {
|
||||
EditorProvider,
|
||||
useEditorContext,
|
||||
StoreNoticesProvider,
|
||||
} from '@woocommerce/base-context';
|
||||
import PageSelector from '@woocommerce/editor-components/page-selector';
|
||||
import {
|
||||
previewCart,
|
||||
|
@ -320,9 +324,11 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<Disabled>
|
||||
<Block attributes={ attributes } />
|
||||
</Disabled>
|
||||
<StoreNoticesProvider context="wc/checkout">
|
||||
<Disabled>
|
||||
<Block attributes={ attributes } />
|
||||
</Disabled>
|
||||
</StoreNoticesProvider>
|
||||
</BlockErrorBoundary>
|
||||
</div>
|
||||
</EditorProvider>
|
||||
|
|
|
@ -32,6 +32,9 @@ function paymentRequestAvailable( currencyCode ) {
|
|||
isStripeInitialized = true;
|
||||
return canPay;
|
||||
}
|
||||
if ( stripe.error && stripe.error instanceof Error ) {
|
||||
throw stripe.error;
|
||||
}
|
||||
// Do a test payment to confirm if payment method is available.
|
||||
const paymentRequest = stripe.paymentRequest( {
|
||||
total: {
|
||||
|
|
Loading…
Reference in New Issue