Skip to content

Commit

Permalink
feat: error message on 1M+ amount (#8793)
Browse files Browse the repository at this point in the history
  • Loading branch information
frosso authored May 17, 2024
1 parent f78e96c commit 9eab5e4
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 30 deletions.
4 changes: 4 additions & 0 deletions changelog/feat-error-notice-on-1m-amount
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: add

feat: error message on 1M+ amount
71 changes: 48 additions & 23 deletions client/checkout/blocks/payment-elements.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
* External dependencies
*/
import { useEffect, useState, RawHTML } from '@wordpress/element';
import { Elements } from '@stripe/react-stripe-js';
// eslint-disable-next-line import/no-unresolved
import { StoreNotice } from '@woocommerce/blocks-checkout';

/**
* Internal dependencies
*/
Expand All @@ -6,14 +14,16 @@ import { getAppearance, getFontRulesFromPage } from 'wcpay/checkout/upe-styles';
import { getUPEConfig } from 'wcpay/utils/checkout';
import { useFingerprint } from './hooks';
import { LoadableBlock } from 'wcpay/components/loadable';
import { Elements } from '@stripe/react-stripe-js';
import { useEffect, useState } from 'react';
import PaymentProcessor from './payment-processor';
import { getPaymentMethodTypes } from 'wcpay/checkout/utils/upe';

const PaymentElements = ( { api, ...props } ) => {
const stripe = api.getStripeForUPE( props.paymentMethodId );
const [ errorMessage, setErrorMessage ] = useState( null );
const [
paymentProcessorLoadErrorMessage,
setPaymentProcessorLoadErrorMessage,
] = useState( undefined );
const [ appearance, setAppearance ] = useState(
getUPEConfig( 'wcBlocksUPEAppearance' )
);
Expand Down Expand Up @@ -50,27 +60,42 @@ const PaymentElements = ( { api, ...props } ) => {
] );

return (
<LoadableBlock isLoading={ ! appearance } numLines={ 3 }>
<Elements
stripe={ stripe }
options={ {
mode: amount < 1 ? 'setup' : 'payment',
amount: amount,
currency: currency,
paymentMethodCreation: 'manual',
paymentMethodTypes: paymentMethodTypes,
appearance: appearance,
fonts: fontRules,
} }
>
<PaymentProcessor
api={ api }
errorMessage={ errorMessage }
fingerprint={ fingerprint }
{ ...props }
/>
</Elements>
</LoadableBlock>
<>
<LoadableBlock isLoading={ ! appearance } numLines={ 3 }>
<Elements
stripe={ stripe }
options={ {
mode: amount < 1 ? 'setup' : 'payment',
amount: amount,
currency: currency,
paymentMethodCreation: 'manual',
paymentMethodTypes: paymentMethodTypes,
appearance: appearance,
fonts: fontRules,
} }
>
{ paymentProcessorLoadErrorMessage?.error?.message && (
<div className="wc-block-components-notices">
<StoreNotice status="error" isDismissible={ false }>
<RawHTML>
{
paymentProcessorLoadErrorMessage.error
.message
}
</RawHTML>
</StoreNotice>
</div>
) }
<PaymentProcessor
api={ api }
errorMessage={ errorMessage }
fingerprint={ fingerprint }
onLoadError={ setPaymentProcessorLoadErrorMessage }
{ ...props }
/>
</Elements>
</LoadableBlock>
</>
);
};

Expand Down
14 changes: 9 additions & 5 deletions client/checkout/blocks/payment-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const getFraudPreventionToken = () => {
return window.wcpayFraudPreventionToken ?? '';
};

const noop = () => null;

const PaymentProcessor = ( {
api,
activePaymentMethod,
Expand All @@ -60,10 +62,11 @@ const PaymentProcessor = ( {
errorMessage,
shouldSavePayment,
fingerprint,
onLoadError = noop,
} ) => {
const stripe = useStripe();
const elements = useElements();
const isPaymentElementCompleteRef = useRef( false );
const isPaymentInformationCompleteRef = useRef( false );

const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' );
const isTestMode = getUPEConfig( 'testMode' );
Expand Down Expand Up @@ -137,7 +140,7 @@ const PaymentProcessor = ( {
return;
}

if ( ! isPaymentElementCompleteRef.current ) {
if ( ! isPaymentInformationCompleteRef.current ) {
return {
type: 'error',
message: __(
Expand Down Expand Up @@ -234,8 +237,8 @@ const PaymentProcessor = ( {
shouldSavePayment
);

const updatePaymentElementCompletionStatus = ( event ) => {
isPaymentElementCompleteRef.current = event.complete;
const setPaymentInformationCompletionStatus = ( event ) => {
isPaymentInformationCompleteRef.current = event.complete;
};

return (
Expand All @@ -253,7 +256,8 @@ const PaymentProcessor = ( {
shouldSavePayment,
paymentMethodsConfig
) }
onChange={ updatePaymentElementCompletionStatus }
onLoadError={ onLoadError }
onChange={ setPaymentInformationCompletionStatus }
className="wcpay-payment-element"
/>
</>
Expand Down
32 changes: 30 additions & 2 deletions client/checkout/classic/payment-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ for ( const paymentMethodType in getUPEConfig( 'paymentMethodsConfig' ) ) {
gatewayUPEComponents[ paymentMethodType ] = {
elements: null,
upeElement: null,
isPaymentInformationComplete: false,
};
}

Expand Down Expand Up @@ -400,6 +401,27 @@ export async function mountStripePaymentElement( api, domElement ) {
gatewayUPEComponents[ paymentMethodType ].upeElement ||
( await createStripePaymentElement( api, paymentMethodType ) );
upeElement.mount( domElement );
upeElement.on( 'change', ( e ) => {
gatewayUPEComponents[ paymentMethodType ].isPaymentInformationComplete =
e.complete;
} );
upeElement.on( 'loaderror', ( e ) => {
// unset any styling to ensure the WC error message wrapper can take more width.
domElement.style.padding = '0';
// creating a new element to be added to the DOM, so that the message can be displayed.
const messageWrapper = document.createElement( 'div' );
messageWrapper.classList.add( 'woocommerce-error' );
messageWrapper.innerHTML = e.error.message;
messageWrapper.style.margin = '0';
domElement.appendChild( messageWrapper );
// hiding any "save payment method" checkboxes.
const savePaymentMethodWrapper = domElement
.closest( '.payment_box' )
?.querySelector( '.woocommerce-SavedPaymentMethods-saveNew' );
if ( savePaymentMethodWrapper ) {
savePaymentMethodWrapper.style.display = 'none';
}
} );
}

export async function mountStripePaymentMethodMessagingElement(
Expand Down Expand Up @@ -493,9 +515,15 @@ export const processPayment = (
return;
}

blockUI( $form );
const { elements, isPaymentInformationComplete } = gatewayUPEComponents[
paymentMethodType
];
if ( ! isPaymentInformationComplete ) {
showErrorCheckout( 'Your payment information is incomplete.' );
return false;
}

const elements = gatewayUPEComponents[ paymentMethodType ].elements;
blockUI( $form );

( async () => {
try {
Expand Down
22 changes: 22 additions & 0 deletions client/checkout/classic/test/payment-processing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,25 @@ const mockUpdateFunction = jest.fn();

const mockMountFunction = jest.fn();

let eventHandlersFromElementsCreate = {};
const mockCreateFunction = jest.fn( () => ( {
mount: mockMountFunction,
update: mockUpdateFunction,
on: ( event, handler ) => {
if ( ! eventHandlersFromElementsCreate[ event ] ) {
eventHandlersFromElementsCreate[ event ] = [];
}
eventHandlersFromElementsCreate[ event ].push( handler );
},
} ) );
const callAllCreateHandlersWith = ( event, ...args ) => {
eventHandlersFromElementsCreate[ event ]?.forEach( ( handler ) => {
handler.apply( null, args );
} );
};
const markAllPaymentElementsAsComplete = () => {
callAllCreateHandlersWith( 'change', { complete: true } );
};

const mockSubmit = jest.fn( () => ( {
then: jest.fn(),
Expand All @@ -95,6 +110,7 @@ describe( 'Stripe Payment Element mounting', () => {

beforeEach( () => {
mockDomElement = document.createElement( 'div' );
eventHandlersFromElementsCreate = {};
getUPEConfig.mockImplementation( ( argument ) => {
if (
argument === 'wcBlocksUPEAppearance' ||
Expand Down Expand Up @@ -380,6 +396,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const mockJqueryForm = {
submit: jest.fn(),
Expand Down Expand Up @@ -426,6 +443,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const checkoutForm = {
submit: jest.fn(),
Expand Down Expand Up @@ -467,6 +485,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const checkoutForm = {
submit: jest.fn(),
Expand Down Expand Up @@ -504,6 +523,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const checkoutForm = {
submit: jest.fn(),
Expand Down Expand Up @@ -538,6 +558,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const checkoutForm = {
submit: jest.fn(),
Expand Down Expand Up @@ -570,6 +591,7 @@ describe( 'Payment processing', () => {
mockDomElement.dataset.paymentMethodType = 'card';

await mountStripePaymentElement( apiMock, mockDomElement );
markAllPaymentElementsAsComplete();

const addPaymentMethodForm = {
submit: jest.fn(),
Expand Down

0 comments on commit 9eab5e4

Please sign in to comment.