Fix multiple extra stripe createSource requests than necessary (https://github.com/woocommerce/woocommerce-blocks/pull/2444)
* refactor to move payment processing into it’s own effect - this is the first step in trying to improve the createSource behaviour. * memoize setValidationErrors so onPaymentProcessing event effect doesn’t re-run unnecessarily. * Update assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-processing.js Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com> * fix docs alignment Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
parent
657e5005ff
commit
ea3d817db1
|
@ -102,7 +102,7 @@ export const ValidationContextProvider = ( { children } ) => {
|
|||
* validation error is for and values are the
|
||||
* validation error message displayed to the user.
|
||||
*/
|
||||
const setValidationErrors = ( newErrors ) => {
|
||||
const setValidationErrors = useCallback( ( newErrors ) => {
|
||||
if ( ! newErrors ) {
|
||||
return;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ export const ValidationContextProvider = ( { children } ) => {
|
|||
...newErrors,
|
||||
};
|
||||
} );
|
||||
};
|
||||
}, [] );
|
||||
|
||||
/**
|
||||
* Used to update a validation error.
|
||||
|
|
|
@ -9,7 +9,7 @@ import { InlineCard, CardElements } from './elements';
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Elements, useElements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { Elements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
|
@ -32,12 +32,11 @@ const CreditCardComponent = ( {
|
|||
} ) => {
|
||||
const { ValidationInputError, CheckboxControl } = components;
|
||||
const { customerId } = billing;
|
||||
const [ sourceId, setSourceId ] = useState( 0 );
|
||||
const [ sourceId, setSourceId ] = useState( '' );
|
||||
const stripe = useStripe();
|
||||
const [ shouldSavePayment, setShouldSavePayment ] = useState(
|
||||
customerId ? true : false
|
||||
);
|
||||
const elements = useElements();
|
||||
const onStripeError = useCheckoutSubscriptions(
|
||||
eventRegistration,
|
||||
billing,
|
||||
|
@ -45,8 +44,7 @@ const CreditCardComponent = ( {
|
|||
setSourceId,
|
||||
shouldSavePayment,
|
||||
emitResponse,
|
||||
stripe,
|
||||
elements
|
||||
stripe
|
||||
);
|
||||
const onChange = ( paymentEvent ) => {
|
||||
if ( paymentEvent.error ) {
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useRef, useState } from '@wordpress/element';
|
||||
import { CardElement, CardNumberElement } from '@stripe/react-stripe-js';
|
||||
import { useEffect, useCallback, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PAYMENT_METHOD_NAME } from './constants';
|
||||
import {
|
||||
getStripeServerData,
|
||||
getErrorMessageForTypeAndCode,
|
||||
} from '../stripe-utils';
|
||||
import { getErrorMessageForTypeAndCode } from '../stripe-utils';
|
||||
import { usePaymentIntents } from './use-payment-intents';
|
||||
import { usePaymentProcessing } from './use-payment-processing';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EventRegistrationProps} EventRegistrationProps
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').BillingDataProps} BillingDataProps
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
|
||||
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
||||
* @typedef {import('react').Dispatch<number>} SourceIdDispatch
|
||||
* @typedef {import('react').Dispatch<string>} SourceIdDispatch
|
||||
*/
|
||||
|
||||
/**
|
||||
* A custom hook for the Stripe processing and event observer logic.
|
||||
*
|
||||
* @param {EventRegistrationProps} eventRegistration Event registration functions.
|
||||
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer
|
||||
* response objects.
|
||||
* @param {BillingDataProps} billing Various billing data items.
|
||||
* @param {number} sourceId Current set stripe source id.
|
||||
* @param {SourceIdDispatch} setSourceId Setter for stripe source id.
|
||||
* @param {boolean} shouldSavePayment Whether to save the payment or not.
|
||||
* @param {Stripe} stripe The stripe.js object.
|
||||
* @param {Object} elements Stripe Elements object.
|
||||
* @param {EventRegistrationProps} eventRegistration Event registration functions.
|
||||
* @param {BillingDataProps} billing Various billing data items.
|
||||
* @param {string} sourceId Current set stripe source id.
|
||||
* @param {SourceIdDispatch} setSourceId Setter for stripe source id.
|
||||
* @param {boolean} shouldSavePayment Whether to save the payment or not.
|
||||
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer
|
||||
* response objects.
|
||||
* @param {Stripe} stripe The stripe.js object.
|
||||
*
|
||||
* @return {function(Object):Object} Returns a function for handling stripe error.
|
||||
*/
|
||||
|
@ -44,113 +39,40 @@ export const useCheckoutSubscriptions = (
|
|||
setSourceId,
|
||||
shouldSavePayment,
|
||||
emitResponse,
|
||||
stripe,
|
||||
elements
|
||||
stripe
|
||||
) => {
|
||||
const [ error, setError ] = useState( '' );
|
||||
const onStripeError = useRef( ( event ) => {
|
||||
return event;
|
||||
} );
|
||||
const onStripeError = useCallback( ( event ) => {
|
||||
const type = event.error.type;
|
||||
const code = event.error.code || '';
|
||||
const message =
|
||||
getErrorMessageForTypeAndCode( type, code ) ?? event.error.message;
|
||||
setError( message );
|
||||
return message;
|
||||
}, [] );
|
||||
const {
|
||||
onCheckoutAfterProcessingWithSuccess,
|
||||
onPaymentProcessing,
|
||||
onCheckoutAfterProcessingWithError,
|
||||
} = eventRegistration;
|
||||
usePaymentIntents(
|
||||
stripe,
|
||||
eventRegistration.onCheckoutAfterProcessingWithSuccess,
|
||||
onCheckoutAfterProcessingWithSuccess,
|
||||
emitResponse
|
||||
);
|
||||
usePaymentProcessing(
|
||||
onStripeError,
|
||||
error,
|
||||
stripe,
|
||||
billing,
|
||||
emitResponse,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
shouldSavePayment,
|
||||
onPaymentProcessing
|
||||
);
|
||||
// hook into and register callbacks for events.
|
||||
useEffect( () => {
|
||||
onStripeError.current = ( event ) => {
|
||||
const type = event.error.type;
|
||||
const code = event.error.code || '';
|
||||
const message =
|
||||
getErrorMessageForTypeAndCode( type, code ) ??
|
||||
event.error.message;
|
||||
setError( error );
|
||||
return message;
|
||||
};
|
||||
const createSource = async ( ownerInfo ) => {
|
||||
const elementToGet = getStripeServerData().inline_cc_form
|
||||
? CardElement
|
||||
: CardNumberElement;
|
||||
return await stripe.createSource(
|
||||
elements.getElement( elementToGet ),
|
||||
{
|
||||
type: 'card',
|
||||
owner: ownerInfo,
|
||||
}
|
||||
);
|
||||
};
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const { billingData } = billing;
|
||||
// if there's an error return that.
|
||||
if ( error ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: error,
|
||||
};
|
||||
}
|
||||
// use token if it's set.
|
||||
if ( sourceId !== 0 ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
paymentMethod: PAYMENT_METHOD_NAME,
|
||||
paymentRequestType: 'cc',
|
||||
stripe_source: sourceId,
|
||||
shouldSavePayment,
|
||||
},
|
||||
billingData,
|
||||
},
|
||||
};
|
||||
}
|
||||
const ownerInfo = {
|
||||
address: {
|
||||
line1: billingData.address_1,
|
||||
line2: billingData.address_2,
|
||||
city: billingData.city,
|
||||
state: billingData.state,
|
||||
postal_code: billingData.postcode,
|
||||
country: billingData.country,
|
||||
},
|
||||
};
|
||||
if ( billingData.phone ) {
|
||||
ownerInfo.phone = billingData.phone;
|
||||
}
|
||||
if ( billingData.email ) {
|
||||
ownerInfo.email = billingData.email;
|
||||
}
|
||||
if ( billingData.first_name || billingData.last_name ) {
|
||||
ownerInfo.name = `${ billingData.first_name } ${ billingData.last_name }`;
|
||||
}
|
||||
|
||||
const response = await createSource( ownerInfo );
|
||||
if ( response.error ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: onStripeError.current( response ),
|
||||
};
|
||||
}
|
||||
setSourceId( response.source.id );
|
||||
return {
|
||||
type: emitResponse.responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
stripe_source: response.source.id,
|
||||
paymentMethod: PAYMENT_METHOD_NAME,
|
||||
paymentRequestType: 'cc',
|
||||
shouldSavePayment,
|
||||
},
|
||||
billingData,
|
||||
},
|
||||
};
|
||||
} catch ( e ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
const onError = ( { processingResponse } ) => {
|
||||
if ( processingResponse?.paymentDetails?.errorMessage ) {
|
||||
return {
|
||||
|
@ -162,25 +84,16 @@ export const useCheckoutSubscriptions = (
|
|||
// so we don't break the observers.
|
||||
return true;
|
||||
};
|
||||
const unsubscribeProcessing = eventRegistration.onPaymentProcessing(
|
||||
onSubmit
|
||||
);
|
||||
const unsubscribeAfterProcessing = eventRegistration.onCheckoutAfterProcessingWithError(
|
||||
const unsubscribeAfterProcessing = onCheckoutAfterProcessingWithError(
|
||||
onError
|
||||
);
|
||||
return () => {
|
||||
unsubscribeProcessing();
|
||||
unsubscribeAfterProcessing();
|
||||
};
|
||||
}, [
|
||||
eventRegistration.onPaymentProcessing,
|
||||
eventRegistration.onCheckoutAfterProcessingWithError,
|
||||
stripe,
|
||||
sourceId,
|
||||
billing.billingData,
|
||||
setSourceId,
|
||||
shouldSavePayment,
|
||||
error,
|
||||
onCheckoutAfterProcessingWithError,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
] );
|
||||
return onStripeError.current;
|
||||
return onStripeError;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import {
|
||||
CardElement,
|
||||
CardNumberElement,
|
||||
useElements,
|
||||
} from '@stripe/react-stripe-js';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PAYMENT_METHOD_NAME } from './constants';
|
||||
import {
|
||||
getStripeServerData,
|
||||
getErrorMessageForTypeAndCode,
|
||||
} from '../stripe-utils';
|
||||
import { errorTypes } from '../stripe-utils/constants';
|
||||
|
||||
/**
|
||||
* @typedef {import('@stripe/stripe-js').Stripe} Stripe
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EventRegistrationProps} EventRegistrationProps
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').BillingDataProps} BillingDataProps
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
|
||||
* @typedef {import('react').Dispatch<string>} SourceIdDispatch
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {function(function():any):function():void} EventRegistration
|
||||
*/
|
||||
|
||||
/**
|
||||
* A custom hook that registers stripe payment processing with the
|
||||
* onPaymentProcessing event from checkout.
|
||||
*
|
||||
* @param {function(any):string} onStripeError Sets an error for stripe.
|
||||
* @param {string} error Any set error message (an empty string if no
|
||||
* error).
|
||||
* @param {Stripe} stripe The stripe utility
|
||||
* @param {BillingDataProps} billing Various billing data items.
|
||||
* @param {EmitResponseProps} emitResponse Various helpers for usage with observer
|
||||
* response objects.
|
||||
* @param {string} sourceId Current set stripe source id.
|
||||
* @param {SourceIdDispatch} setSourceId Setter for stripe source id.
|
||||
* @param {boolean} shouldSavePayment Whether to save the payment or not.
|
||||
* @param {EventRegistration} onPaymentProcessing The event emitter for processing payment.
|
||||
*/
|
||||
export const usePaymentProcessing = (
|
||||
onStripeError,
|
||||
error,
|
||||
stripe,
|
||||
billing,
|
||||
emitResponse,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
shouldSavePayment,
|
||||
onPaymentProcessing
|
||||
) => {
|
||||
const elements = useElements();
|
||||
// hook into and register callbacks for events
|
||||
useEffect( () => {
|
||||
const createSource = async ( ownerInfo ) => {
|
||||
const elementToGet = getStripeServerData().inline_cc_form
|
||||
? CardElement
|
||||
: CardNumberElement;
|
||||
return await stripe.createSource(
|
||||
// @ts-ignore
|
||||
elements?.getElement( elementToGet ),
|
||||
{
|
||||
type: 'card',
|
||||
owner: ownerInfo,
|
||||
}
|
||||
);
|
||||
};
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const billingData = billing.billingData;
|
||||
// if there's an error return that.
|
||||
if ( error ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: error,
|
||||
};
|
||||
}
|
||||
// use token if it's set.
|
||||
if ( parseInt( sourceId, 10 ) !== 0 ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
paymentMethod: PAYMENT_METHOD_NAME,
|
||||
paymentRequestType: 'cc',
|
||||
stripe_source: sourceId,
|
||||
shouldSavePayment,
|
||||
},
|
||||
billingData,
|
||||
},
|
||||
};
|
||||
}
|
||||
const ownerInfo = {
|
||||
address: {
|
||||
line1: billingData.address_1,
|
||||
line2: billingData.address_2,
|
||||
city: billingData.city,
|
||||
state: billingData.state,
|
||||
postal_code: billingData.postcode,
|
||||
country: billingData.country,
|
||||
},
|
||||
};
|
||||
if ( billingData.phone ) {
|
||||
ownerInfo.phone = billingData.phone;
|
||||
}
|
||||
if ( billingData.email ) {
|
||||
ownerInfo.email = billingData.email;
|
||||
}
|
||||
if ( billingData.first_name || billingData.last_name ) {
|
||||
ownerInfo.name = `${ billingData.first_name } ${ billingData.last_name }`;
|
||||
}
|
||||
|
||||
const response = await createSource( ownerInfo );
|
||||
if ( response.error ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: onStripeError( response ),
|
||||
};
|
||||
}
|
||||
if ( ! response.source || ! response.source.id ) {
|
||||
throw new Error(
|
||||
getErrorMessageForTypeAndCode( errorTypes.API_ERROR )
|
||||
);
|
||||
}
|
||||
setSourceId( response.source.id );
|
||||
return {
|
||||
type: emitResponse.responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
stripe_source: response.source.id,
|
||||
paymentMethod: PAYMENT_METHOD_NAME,
|
||||
paymentRequestType: 'cc',
|
||||
shouldSavePayment,
|
||||
},
|
||||
billingData,
|
||||
},
|
||||
};
|
||||
} catch ( e ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
const unsubscribeProcessing = onPaymentProcessing( onSubmit );
|
||||
return () => {
|
||||
unsubscribeProcessing();
|
||||
};
|
||||
}, [
|
||||
onPaymentProcessing,
|
||||
billing.billingData,
|
||||
stripe,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
shouldSavePayment,
|
||||
onStripeError,
|
||||
error,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
emitResponse.responseTypes.SUCCESS,
|
||||
elements,
|
||||
] );
|
||||
};
|
Loading…
Reference in New Issue