diff --git a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment-methods.js b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment-methods.js index a22d189ac9c..b8731ea9247 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment-methods.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment-methods.js @@ -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 = () => {
  • No registered Payment Methods
  • ); return ( - + + + ); }; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment/checkout-express-payment.js b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment/checkout-express-payment.js index 6140a98368c..61e97fdaf97 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment/checkout-express-payment.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/express-payment/checkout-express-payment.js @@ -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 ( + + ); + } return null; } @@ -37,7 +55,9 @@ const CheckoutExpressPayment = () => {
    - +

    { __( 'In a hurry? Use one of our express checkout options below:', diff --git a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/payment-method-error-boundary.js b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/payment-method-error-boundary.js index bd6e64ae49a..f599b1b557f 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/payment-method-error-boundary.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/payment-method-error-boundary.js @@ -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 ( - - { errorText } - - ); + const notices = [ + { + id: '0', + content: errorText, + isDismissible: false, + status: 'error', + }, + ]; + return ; } return this.props.children; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/saved-payment-method-options.js b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/saved-payment-method-options.js index ee08552e659..7f504635d97 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/saved-payment-method-options.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/payment-methods/saved-payment-method-options.js @@ -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 ? ( ) : null; }; diff --git a/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/use-payment-method-registration.js b/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/use-payment-method-registration.js index c18ca0f3d41..c716929dfaf 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/use-payment-method-registration.js +++ b/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/use-payment-method-registration.js @@ -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 ); }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js index 213c92afabe..07a74941aab 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js @@ -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' ) } > - - - + + + + +

    diff --git a/plugins/woocommerce-blocks/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js b/plugins/woocommerce-blocks/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js index ae01269b7f0..83834e92926 100644 --- a/plugins/woocommerce-blocks/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js +++ b/plugins/woocommerce-blocks/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js @@ -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: {