woocommerce/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx

207 lines
6.3 KiB
TypeScript

/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
useExpressPaymentMethods,
usePaymentMethodInterface,
} from '@woocommerce/base-context/hooks';
import {
cloneElement,
isValidElement,
useCallback,
useRef,
} from '@wordpress/element';
import { useEditorContext } from '@woocommerce/base-context';
import deprecated from '@wordpress/deprecated';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCheckoutBlockContext } from '@woocommerce/blocks/checkout/context';
/**
* Internal dependencies
*/
import PaymentMethodErrorBoundary from './payment-method-error-boundary';
import { STORE_KEY as PAYMENT_STORE_KEY } from '../../../data/payment/constants';
import { useExpressCheckoutContext } from '../../checkout/inner-blocks/checkout-express-payment-block/context';
const ExpressPaymentMethods = () => {
const { isEditor } = useEditorContext();
const { hasDarkControls } = useCheckoutBlockContext();
const { showButtonStyles, buttonHeight, buttonBorderRadius } =
useExpressCheckoutContext();
// API for passing styles to express payment buttons
const buttonAttributes = showButtonStyles
? {
height: buttonHeight,
borderRadius: buttonBorderRadius,
darkMode: hasDarkControls,
}
: undefined;
const { activePaymentMethod, paymentMethodData } = useSelect(
( select ) => {
const store = select( PAYMENT_STORE_KEY );
return {
activePaymentMethod: store.getActivePaymentMethod(),
paymentMethodData: store.getPaymentMethodData(),
};
}
);
const {
__internalSetActivePaymentMethod,
__internalSetExpressPaymentStarted,
__internalSetPaymentIdle,
__internalSetPaymentError,
__internalSetPaymentMethodData,
__internalSetExpressPaymentError,
} = useDispatch( PAYMENT_STORE_KEY );
const { paymentMethods } = useExpressPaymentMethods();
const paymentMethodInterface = usePaymentMethodInterface();
const previousActivePaymentMethod = useRef( activePaymentMethod );
const previousPaymentMethodData = useRef( paymentMethodData );
/**
* onExpressPaymentClick should be triggered when the express payment button is clicked.
*
* This will store the previous active payment method, set the express method as active, and set the payment status
* to started.
*/
const onExpressPaymentClick = useCallback(
( paymentMethodId ) => () => {
previousActivePaymentMethod.current = activePaymentMethod;
previousPaymentMethodData.current = paymentMethodData;
__internalSetExpressPaymentStarted();
__internalSetActivePaymentMethod( paymentMethodId );
},
[
activePaymentMethod,
paymentMethodData,
__internalSetActivePaymentMethod,
__internalSetExpressPaymentStarted,
]
);
/**
* onExpressPaymentClose should be triggered when the express payment process is cancelled or closed.
*
* This restores the active method and returns the state to pristine.
*/
const onExpressPaymentClose = useCallback( () => {
__internalSetPaymentIdle();
__internalSetActivePaymentMethod(
previousActivePaymentMethod.current,
previousPaymentMethodData.current
);
}, [ __internalSetActivePaymentMethod, __internalSetPaymentIdle ] );
/**
* onExpressPaymentError should be triggered when the express payment process errors.
*
* This shows an error message then restores the active method and returns the state to pristine.
*/
const onExpressPaymentError = useCallback(
( errorMessage ) => {
__internalSetPaymentError();
__internalSetPaymentMethodData( errorMessage );
__internalSetExpressPaymentError( errorMessage );
__internalSetActivePaymentMethod(
previousActivePaymentMethod.current,
previousPaymentMethodData.current
);
},
[
__internalSetActivePaymentMethod,
__internalSetPaymentError,
__internalSetPaymentMethodData,
__internalSetExpressPaymentError,
]
);
/**
* Calling setExpressPaymentError directly is deprecated.
*/
const deprecatedSetExpressPaymentError = useCallback(
( errorMessage = '' ) => {
deprecated(
'Express Payment Methods should use the provided onError handler instead.',
{
alternative: 'onError',
plugin: 'woocommerce-gutenberg-products-block',
link: 'https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4228',
}
);
if ( errorMessage ) {
onExpressPaymentError( errorMessage );
} else {
__internalSetExpressPaymentError( '' );
}
},
[ __internalSetExpressPaymentError, onExpressPaymentError ]
);
// In the editor, we apply styles to the button containers to show the changes of the height and border-radius controls,
// which would be passed to the payment APIs on the front-end
const stylesForButtonContainers = isEditor
? {
height: `${ showButtonStyles ? buttonHeight : '48' }px`,
borderRadius: `${
showButtonStyles ? buttonBorderRadius : '4'
}px`,
pointerEvents: 'none',
userSelect: 'none',
ariaDisabled: true,
}
: {};
/**
* @todo Find a way to Memoize Express Payment Method Content
*
* Payment method content could potentially become a bottleneck if lots of logic is ran in the content component. It
* Currently re-renders excessively but is not easy to useMemo because paymentMethodInterface could become stale.
* paymentMethodInterface itself also updates on most renders.
*/
const entries = Object.entries( paymentMethods );
const content =
entries.length > 0 ? (
entries.map( ( [ id, paymentMethod ] ) => {
const expressPaymentMethod = isEditor
? paymentMethod.edit
: paymentMethod.content;
return isValidElement( expressPaymentMethod ) ? (
<li
key={ id }
id={ `express-payment-method-${ id }` }
style={ stylesForButtonContainers }
>
{ cloneElement( expressPaymentMethod, {
...paymentMethodInterface,
onClick: onExpressPaymentClick( id ),
onClose: onExpressPaymentClose,
onError: onExpressPaymentError,
setExpressPaymentError:
deprecatedSetExpressPaymentError,
buttonAttributes,
} ) }
</li>
) : null;
} )
) : (
<li key="noneRegistered">
{ __( 'No registered Payment Methods', 'woocommerce' ) }
</li>
);
return (
<PaymentMethodErrorBoundary isEditor={ isEditor }>
<ul className="wc-block-components-express-payment__event-buttons">
{ content }
</ul>
</PaymentMethodErrorBoundary>
);
};
export default ExpressPaymentMethods;