initial commit
This commit is contained in:
@ -0,0 +1 @@
|
||||
export const PAYMENT_METHOD_NAME = 'stripe';
|
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
CardElement,
|
||||
CardNumberElement,
|
||||
CardExpiryElement,
|
||||
CardCvcElement,
|
||||
} from '@stripe/react-stripe-js';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useElementOptions } from './use-element-options';
|
||||
|
||||
/** @typedef {import('react')} React */
|
||||
|
||||
const baseTextInputStyles = 'wc-block-gateway-input';
|
||||
|
||||
/**
|
||||
* InlineCard component
|
||||
*
|
||||
* @param {Object} props Incoming props for the component.
|
||||
* @param {React.ReactElement} props.inputErrorComponent
|
||||
* @param {function(any):any} props.onChange
|
||||
*/
|
||||
export const InlineCard = ( {
|
||||
inputErrorComponent: ValidationInputError,
|
||||
onChange,
|
||||
} ) => {
|
||||
const [ isEmpty, setIsEmpty ] = useState( true );
|
||||
const { options, onActive, error, setError } = useElementOptions( {
|
||||
hidePostalCode: true,
|
||||
} );
|
||||
const errorCallback = ( event ) => {
|
||||
if ( event.error ) {
|
||||
setError( event.error.message );
|
||||
} else {
|
||||
setError( '' );
|
||||
}
|
||||
setIsEmpty( event.empty );
|
||||
onChange( event );
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="wc-block-gateway-container wc-inline-card-element">
|
||||
<CardElement
|
||||
id="wc-stripe-inline-card-element"
|
||||
className={ baseTextInputStyles }
|
||||
options={ options }
|
||||
onBlur={ () => onActive( isEmpty ) }
|
||||
onFocus={ () => onActive( isEmpty ) }
|
||||
onChange={ errorCallback }
|
||||
/>
|
||||
<label htmlFor="wc-stripe-inline-card-element">
|
||||
{ __(
|
||||
'Credit Card Information',
|
||||
'woocommerce'
|
||||
) }
|
||||
</label>
|
||||
</div>
|
||||
<ValidationInputError errorMessage={ error } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* CardElements component.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {function(any):any} props.onChange
|
||||
* @param {React.ReactElement} props.inputErrorComponent
|
||||
*/
|
||||
export const CardElements = ( {
|
||||
onChange,
|
||||
inputErrorComponent: ValidationInputError,
|
||||
} ) => {
|
||||
const [ isEmpty, setIsEmpty ] = useState( {
|
||||
cardNumber: true,
|
||||
cardExpiry: true,
|
||||
cardCvc: true,
|
||||
} );
|
||||
const {
|
||||
options: cardNumOptions,
|
||||
onActive: cardNumOnActive,
|
||||
error: cardNumError,
|
||||
setError: cardNumSetError,
|
||||
} = useElementOptions( { showIcon: false } );
|
||||
const {
|
||||
options: cardExpiryOptions,
|
||||
onActive: cardExpiryOnActive,
|
||||
error: cardExpiryError,
|
||||
setError: cardExpirySetError,
|
||||
} = useElementOptions();
|
||||
const {
|
||||
options: cardCvcOptions,
|
||||
onActive: cardCvcOnActive,
|
||||
error: cardCvcError,
|
||||
setError: cardCvcSetError,
|
||||
} = useElementOptions();
|
||||
const errorCallback = ( errorSetter, elementId ) => ( event ) => {
|
||||
if ( event.error ) {
|
||||
errorSetter( event.error.message );
|
||||
} else {
|
||||
errorSetter( '' );
|
||||
}
|
||||
setIsEmpty( { ...isEmpty, [ elementId ]: event.empty } );
|
||||
onChange( event );
|
||||
};
|
||||
return (
|
||||
<div className="wc-block-card-elements">
|
||||
<div className="wc-block-gateway-container wc-card-number-element">
|
||||
<CardNumberElement
|
||||
onChange={ errorCallback( cardNumSetError, 'cardNumber' ) }
|
||||
options={ cardNumOptions }
|
||||
className={ baseTextInputStyles }
|
||||
id="wc-stripe-card-number-element"
|
||||
onFocus={ () => cardNumOnActive( isEmpty.cardNumber ) }
|
||||
onBlur={ () => cardNumOnActive( isEmpty.cardNumber ) }
|
||||
/>
|
||||
<label htmlFor="wc-stripe-card-number-element">
|
||||
{ __( 'Card Number', 'woo-gutenberg-product-blocks' ) }
|
||||
</label>
|
||||
<ValidationInputError errorMessage={ cardNumError } />
|
||||
</div>
|
||||
<div className="wc-block-gateway-container wc-card-expiry-element">
|
||||
<CardExpiryElement
|
||||
onChange={ errorCallback(
|
||||
cardExpirySetError,
|
||||
'cardExpiry'
|
||||
) }
|
||||
options={ cardExpiryOptions }
|
||||
className={ baseTextInputStyles }
|
||||
onFocus={ () => cardExpiryOnActive( isEmpty.cardExpiry ) }
|
||||
onBlur={ () => cardExpiryOnActive( isEmpty.cardExpiry ) }
|
||||
id="wc-stripe-card-expiry-element"
|
||||
/>
|
||||
<label htmlFor="wc-stripe-card-expiry-element">
|
||||
{ __( 'Expiry Date', 'woo-gutenberg-product-blocks' ) }
|
||||
</label>
|
||||
<ValidationInputError errorMessage={ cardExpiryError } />
|
||||
</div>
|
||||
<div className="wc-block-gateway-container wc-card-cvc-element">
|
||||
<CardCvcElement
|
||||
onChange={ errorCallback( cardCvcSetError, 'cardCvc' ) }
|
||||
options={ cardCvcOptions }
|
||||
className={ baseTextInputStyles }
|
||||
onFocus={ () => cardCvcOnActive( isEmpty.cardCvc ) }
|
||||
onBlur={ () => cardCvcOnActive( isEmpty.cardCvc ) }
|
||||
id="wc-stripe-card-code-element"
|
||||
/>
|
||||
<label htmlFor="wc-stripe-card-code-element">
|
||||
{ __( 'CVV/CVC', 'woo-gutenberg-product-blocks' ) }
|
||||
</label>
|
||||
<ValidationInputError errorMessage={ cardCvcError } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getStripeServerData, loadStripe } from '../stripe-utils';
|
||||
import { StripeCreditCard, getStripeCreditCardIcons } from './payment-method';
|
||||
import { PAYMENT_METHOD_NAME } from './constants';
|
||||
|
||||
const stripePromise = loadStripe();
|
||||
|
||||
const StripeComponent = ( props ) => {
|
||||
const [ errorMessage, setErrorMessage ] = useState( '' );
|
||||
|
||||
useEffect( () => {
|
||||
Promise.resolve( stripePromise ).then( ( { error } ) => {
|
||||
if ( error ) {
|
||||
setErrorMessage( error.message );
|
||||
}
|
||||
} );
|
||||
}, [ setErrorMessage ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( errorMessage ) {
|
||||
throw new Error( errorMessage );
|
||||
}
|
||||
}, [ errorMessage ] );
|
||||
|
||||
return <StripeCreditCard stripe={ stripePromise } { ...props } />;
|
||||
};
|
||||
|
||||
const StripeLabel = ( props ) => {
|
||||
const { PaymentMethodLabel } = props.components;
|
||||
|
||||
const labelText = getStripeServerData().title
|
||||
? getStripeServerData().title
|
||||
: __( 'Credit / Debit Card', 'woocommerce' );
|
||||
|
||||
return <PaymentMethodLabel text={ labelText } />;
|
||||
};
|
||||
|
||||
const cardIcons = getStripeCreditCardIcons();
|
||||
const stripeCcPaymentMethod = {
|
||||
name: PAYMENT_METHOD_NAME,
|
||||
label: <StripeLabel />,
|
||||
content: <StripeComponent />,
|
||||
edit: <StripeComponent />,
|
||||
icons: cardIcons,
|
||||
canMakePayment: () => stripePromise,
|
||||
ariaLabel: __(
|
||||
'Stripe Credit Card payment method',
|
||||
'woocommerce'
|
||||
),
|
||||
supports: {
|
||||
showSavedCards: getStripeServerData().showSavedCards,
|
||||
showSaveOption: getStripeServerData().showSaveOption,
|
||||
features: getStripeServerData()?.supports ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
export default stripeCcPaymentMethod;
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Elements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getStripeServerData } from '../stripe-utils';
|
||||
import { useCheckoutSubscriptions } from './use-checkout-subscriptions';
|
||||
import { InlineCard, CardElements } from './elements';
|
||||
|
||||
/**
|
||||
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
||||
* @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').RegisteredPaymentMethodProps} RegisteredPaymentMethodProps
|
||||
*/
|
||||
|
||||
export const getStripeCreditCardIcons = () => {
|
||||
return Object.entries( getStripeServerData().icons ).map(
|
||||
( [ id, { src, alt } ] ) => {
|
||||
return {
|
||||
id,
|
||||
src,
|
||||
alt,
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stripe Credit Card component
|
||||
*
|
||||
* @param {RegisteredPaymentMethodProps} props Incoming props
|
||||
*/
|
||||
const CreditCardComponent = ( {
|
||||
billing,
|
||||
eventRegistration,
|
||||
emitResponse,
|
||||
components,
|
||||
} ) => {
|
||||
const { ValidationInputError, PaymentMethodIcons } = components;
|
||||
const [ sourceId, setSourceId ] = useState( '' );
|
||||
const stripe = useStripe();
|
||||
const onStripeError = useCheckoutSubscriptions(
|
||||
eventRegistration,
|
||||
billing,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
emitResponse,
|
||||
stripe
|
||||
);
|
||||
const onChange = ( paymentEvent ) => {
|
||||
if ( paymentEvent.error ) {
|
||||
onStripeError( paymentEvent );
|
||||
}
|
||||
setSourceId( '0' );
|
||||
};
|
||||
const cardIcons = getStripeCreditCardIcons();
|
||||
|
||||
const renderedCardElement = getStripeServerData().inline_cc_form ? (
|
||||
<InlineCard
|
||||
onChange={ onChange }
|
||||
inputErrorComponent={ ValidationInputError }
|
||||
/>
|
||||
) : (
|
||||
<CardElements
|
||||
onChange={ onChange }
|
||||
inputErrorComponent={ ValidationInputError }
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{ renderedCardElement }
|
||||
{ PaymentMethodIcons && cardIcons.length && (
|
||||
<PaymentMethodIcons icons={ cardIcons } align="left" />
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const StripeCreditCard = ( props ) => {
|
||||
const { locale } = getStripeServerData().button;
|
||||
const { stripe } = props;
|
||||
|
||||
return (
|
||||
<Elements stripe={ stripe } locale={ locale }>
|
||||
<CreditCardComponent { ...props } />
|
||||
</Elements>
|
||||
);
|
||||
};
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useCallback, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
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<string>} SourceIdDispatch
|
||||
*/
|
||||
|
||||
/**
|
||||
* A custom hook for the Stripe processing and event observer logic.
|
||||
*
|
||||
* @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 {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.
|
||||
*/
|
||||
export const useCheckoutSubscriptions = (
|
||||
eventRegistration,
|
||||
billing,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
emitResponse,
|
||||
stripe
|
||||
) => {
|
||||
const [ error, setError ] = useState( '' );
|
||||
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,
|
||||
onCheckoutAfterProcessingWithSuccess,
|
||||
setSourceId,
|
||||
emitResponse
|
||||
);
|
||||
usePaymentProcessing(
|
||||
onStripeError,
|
||||
error,
|
||||
stripe,
|
||||
billing,
|
||||
emitResponse,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
onPaymentProcessing
|
||||
);
|
||||
// hook into and register callbacks for events.
|
||||
useEffect( () => {
|
||||
const onError = ( { processingResponse } ) => {
|
||||
if ( processingResponse?.paymentDetails?.errorMessage ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: processingResponse.paymentDetails.errorMessage,
|
||||
messageContext: emitResponse.noticeContexts.PAYMENTS,
|
||||
};
|
||||
}
|
||||
// so we don't break the observers.
|
||||
return true;
|
||||
};
|
||||
const unsubscribeAfterProcessing = onCheckoutAfterProcessingWithError(
|
||||
onError
|
||||
);
|
||||
return () => {
|
||||
unsubscribeAfterProcessing();
|
||||
};
|
||||
}, [
|
||||
onCheckoutAfterProcessingWithError,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
] );
|
||||
return onStripeError;
|
||||
};
|
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useEffect, useCallback } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* @typedef {import('../stripe-utils/type-defs').StripeElementOptions} StripeElementOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the value of a specific CSS property for the element matched by the provided selector.
|
||||
*
|
||||
* @param {string} selector CSS selector that matches the element to query.
|
||||
* @param {string} property Name of the property to retrieve the style
|
||||
* value from.
|
||||
* @param {string} defaultValue Fallback value if the value for the property
|
||||
* could not be retrieved.
|
||||
*
|
||||
* @return {string} The style value of that property in the document element.
|
||||
*/
|
||||
const getComputedStyle = ( selector, property, defaultValue ) => {
|
||||
let elementStyle = {};
|
||||
|
||||
if (
|
||||
typeof document === 'object' &&
|
||||
typeof document.querySelector === 'function' &&
|
||||
typeof window.getComputedStyle === 'function'
|
||||
) {
|
||||
const element = document.querySelector( selector );
|
||||
if ( element ) {
|
||||
elementStyle = window.getComputedStyle( element );
|
||||
}
|
||||
}
|
||||
|
||||
return elementStyle[ property ] || defaultValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default options for the stripe elements.
|
||||
*/
|
||||
const elementOptions = {
|
||||
style: {
|
||||
base: {
|
||||
iconColor: '#666EE8',
|
||||
color: '#31325F',
|
||||
fontSize: getComputedStyle(
|
||||
'.wc-block-checkout',
|
||||
'fontSize',
|
||||
'16px'
|
||||
),
|
||||
lineHeight: 1.375, // With a font-size of 16px, line-height will be 22px.
|
||||
'::placeholder': {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
classes: {
|
||||
focus: 'focused',
|
||||
empty: 'empty',
|
||||
invalid: 'has-error',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom hook handling options implemented on the stripe elements.
|
||||
*
|
||||
* @param {Object} [overloadedOptions] An array of extra options to merge with
|
||||
* the options provided for the element.
|
||||
*
|
||||
* @return {StripeElementOptions} The stripe element options interface
|
||||
*/
|
||||
export const useElementOptions = ( overloadedOptions ) => {
|
||||
const [ isActive, setIsActive ] = useState( false );
|
||||
const [ options, setOptions ] = useState( {
|
||||
...elementOptions,
|
||||
...overloadedOptions,
|
||||
} );
|
||||
const [ error, setError ] = useState( '' );
|
||||
|
||||
useEffect( () => {
|
||||
const color = isActive ? '#CFD7E0' : '#fff';
|
||||
|
||||
setOptions( ( prevOptions ) => {
|
||||
const showIcon =
|
||||
typeof prevOptions.showIcon !== 'undefined'
|
||||
? { showIcon: isActive }
|
||||
: {};
|
||||
return {
|
||||
...prevOptions,
|
||||
style: {
|
||||
...prevOptions.style,
|
||||
base: {
|
||||
...prevOptions.style.base,
|
||||
'::placeholder': {
|
||||
color,
|
||||
},
|
||||
},
|
||||
},
|
||||
...showIcon,
|
||||
};
|
||||
} );
|
||||
}, [ isActive ] );
|
||||
|
||||
const onActive = useCallback(
|
||||
( isEmpty ) => {
|
||||
if ( ! isEmpty ) {
|
||||
setIsActive( true );
|
||||
} else {
|
||||
setIsActive( ( prevActive ) => ! prevActive );
|
||||
}
|
||||
},
|
||||
[ setIsActive ]
|
||||
);
|
||||
return { options, onActive, error, setError };
|
||||
};
|
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/registered-payment-method-props').EmitResponseProps} EmitResponseProps
|
||||
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opens the modal for PaymentIntent authorizations.
|
||||
*
|
||||
* @param {Object} params Params object.
|
||||
* @param {Stripe} params.stripe The stripe object.
|
||||
* @param {Object} params.paymentDetails The payment details from the
|
||||
* server after checkout processing.
|
||||
* @param {string} params.errorContext Context where errors will be added.
|
||||
* @param {string} params.errorType Type of error responses.
|
||||
* @param {string} params.successType Type of success responses.
|
||||
*/
|
||||
const openIntentModal = ( {
|
||||
stripe,
|
||||
paymentDetails,
|
||||
errorContext,
|
||||
errorType,
|
||||
successType,
|
||||
} ) => {
|
||||
const checkoutResponse = { type: successType };
|
||||
if (
|
||||
! paymentDetails.setup_intent &&
|
||||
! paymentDetails.payment_intent_secret
|
||||
) {
|
||||
return checkoutResponse;
|
||||
}
|
||||
const isSetupIntent = !! paymentDetails.setupIntent;
|
||||
const verificationUrl = paymentDetails.verification_endpoint;
|
||||
const intentSecret = isSetupIntent
|
||||
? paymentDetails.setup_intent
|
||||
: paymentDetails.payment_intent_secret;
|
||||
return stripe[ isSetupIntent ? 'confirmCardSetup' : 'confirmCardPayment' ](
|
||||
intentSecret
|
||||
)
|
||||
.then( function ( response ) {
|
||||
if ( response.error ) {
|
||||
throw response.error;
|
||||
}
|
||||
const intent =
|
||||
response[ isSetupIntent ? 'setupIntent' : 'paymentIntent' ];
|
||||
if (
|
||||
intent.status !== 'requires_capture' &&
|
||||
intent.status !== 'succeeded'
|
||||
) {
|
||||
return checkoutResponse;
|
||||
}
|
||||
checkoutResponse.redirectUrl = verificationUrl;
|
||||
return checkoutResponse;
|
||||
} )
|
||||
.catch( function ( error ) {
|
||||
checkoutResponse.type = errorType;
|
||||
checkoutResponse.message = error.message;
|
||||
checkoutResponse.retry = true;
|
||||
checkoutResponse.messageContext = errorContext;
|
||||
// Reports back to the server.
|
||||
window.fetch( verificationUrl + '&is_ajax' );
|
||||
return checkoutResponse;
|
||||
} );
|
||||
};
|
||||
|
||||
export const usePaymentIntents = (
|
||||
stripe,
|
||||
subscriber,
|
||||
setSourceId,
|
||||
emitResponse
|
||||
) => {
|
||||
useEffect( () => {
|
||||
const unsubscribe = subscriber( async ( { processingResponse } ) => {
|
||||
const paymentDetails = processingResponse.paymentDetails || {};
|
||||
const response = await openIntentModal( {
|
||||
stripe,
|
||||
paymentDetails,
|
||||
errorContext: emitResponse.noticeContexts.PAYMENTS,
|
||||
errorType: emitResponse.responseTypes.ERROR,
|
||||
successType: emitResponse.responseTypes.SUCCESS,
|
||||
} );
|
||||
if (
|
||||
response.type === emitResponse.responseTypes.ERROR &&
|
||||
response.retry
|
||||
) {
|
||||
setSourceId( '0' );
|
||||
}
|
||||
|
||||
return response;
|
||||
} );
|
||||
return () => unsubscribe();
|
||||
}, [
|
||||
subscriber,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
emitResponse.responseTypes.SUCCESS,
|
||||
setSourceId,
|
||||
stripe,
|
||||
] );
|
||||
};
|
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 {EventRegistration} onPaymentProcessing The event emitter for processing payment.
|
||||
*/
|
||||
export const usePaymentProcessing = (
|
||||
onStripeError,
|
||||
error,
|
||||
stripe,
|
||||
billing,
|
||||
emitResponse,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
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 ( sourceId !== '' && sourceId !== '0' ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
paymentMethod: PAYMENT_METHOD_NAME,
|
||||
paymentRequestType: 'cc',
|
||||
stripe_source: sourceId,
|
||||
},
|
||||
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',
|
||||
},
|
||||
billingData,
|
||||
},
|
||||
};
|
||||
} catch ( e ) {
|
||||
return {
|
||||
type: emitResponse.responseTypes.ERROR,
|
||||
message: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
const unsubscribeProcessing = onPaymentProcessing( onSubmit );
|
||||
return () => {
|
||||
unsubscribeProcessing();
|
||||
};
|
||||
}, [
|
||||
onPaymentProcessing,
|
||||
billing.billingData,
|
||||
stripe,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
onStripeError,
|
||||
error,
|
||||
emitResponse.noticeContexts.PAYMENTS,
|
||||
emitResponse.responseTypes.ERROR,
|
||||
emitResponse.responseTypes.SUCCESS,
|
||||
elements,
|
||||
] );
|
||||
};
|
Reference in New Issue
Block a user