Remove Built-in Stripe Integration in favour of Stripe Extension (https://github.com/woocommerce/woocommerce-blocks/pull/5449)

* Remove Stripe from PHP side

* Remove stripe from client

* fix const name in tests

* Update docs
This commit is contained in:
Mike Jolley 2022-01-11 10:53:38 +00:00 committed by GitHub
parent 7073e3bb5f
commit e19abbc0c2
38 changed files with 25 additions and 3031 deletions

View File

@ -44,7 +44,7 @@ jest.mock( '@woocommerce/settings', () => {
cc: [ cc: [
{ {
method: { method: {
gateway: 'stripe', gateway: 'credit-card',
last4: '4242', last4: '4242',
brand: 'Visa', brand: 'Visa',
}, },
@ -75,7 +75,7 @@ const registerMockPaymentMethods = ( savedCards = true ) => {
ariaLabel: name, ariaLabel: name,
} ); } );
} ); } );
[ 'stripe' ].forEach( ( name ) => { [ 'credit-card' ].forEach( ( name ) => {
registerPaymentMethod( { registerPaymentMethod( {
name, name,
label: name, label: name,
@ -121,7 +121,7 @@ const registerMockPaymentMethods = ( savedCards = true ) => {
}; };
const resetMockPaymentMethods = () => { const resetMockPaymentMethods = () => {
[ 'cheque', 'bacs', 'stripe' ].forEach( ( name ) => { [ 'cheque', 'bacs', 'credit-card' ].forEach( ( name ) => {
__experimentalDeRegisterPaymentMethod( name ); __experimentalDeRegisterPaymentMethod( name );
} ); } );
[ 'express-payment' ].forEach( ( name ) => { [ 'express-payment' ].forEach( ( name ) => {
@ -255,8 +255,8 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned
<CheckoutExpressPayment /> <CheckoutExpressPayment />
<SavedPaymentMethodOptions onChange={ () => void null } /> <SavedPaymentMethodOptions onChange={ () => void null } />
{ 'Active Payment Method: ' + activePaymentMethod } { 'Active Payment Method: ' + activePaymentMethod }
{ paymentMethodData[ 'wc-stripe-payment-token' ] && ( { paymentMethodData[ 'wc-credit-card-payment-token' ] && (
<span>Stripe token</span> <span>credit-card token</span>
) } ) }
</> </>
); );
@ -276,11 +276,11 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned
// Should initialize by default the default saved payment method. // Should initialize by default the default saved payment method.
await waitFor( () => { await waitFor( () => {
const activePaymentMethod = screen.queryByText( const activePaymentMethod = screen.queryByText(
/Active Payment Method: stripe/ /Active Payment Method: credit-card/
); );
const stripeToken = screen.queryByText( /Stripe token/ ); const creditCardToken = screen.queryByText( /credit-card token/ );
expect( activePaymentMethod ).not.toBeNull(); expect( activePaymentMethod ).not.toBeNull();
expect( stripeToken ).not.toBeNull(); expect( creditCardToken ).not.toBeNull();
} ); } );
act( () => { act( () => {
@ -294,9 +294,9 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned
const activePaymentMethod = screen.queryByText( const activePaymentMethod = screen.queryByText(
/Active Payment Method: express-payment/ /Active Payment Method: express-payment/
); );
const stripeToken = screen.queryByText( /Stripe token/ ); const creditCardToken = screen.queryByText( /credit-card token/ );
expect( activePaymentMethod ).not.toBeNull(); expect( activePaymentMethod ).not.toBeNull();
expect( stripeToken ).toBeNull(); expect( creditCardToken ).toBeNull();
} ); } );
act( () => { act( () => {
@ -310,11 +310,11 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned
await waitFor( () => { await waitFor( () => {
const activePaymentMethod = screen.queryByText( const activePaymentMethod = screen.queryByText(
/Active Payment Method: stripe/ /Active Payment Method: credit-card/
); );
const stripeToken = screen.queryByText( /Stripe token/ ); const creditCardToken = screen.queryByText( /credit-card token/ );
expect( activePaymentMethod ).not.toBeNull(); expect( activePaymentMethod ).not.toBeNull();
expect( stripeToken ).not.toBeNull(); expect( creditCardToken ).not.toBeNull();
} ); } );
} ); } );
} ); } );

View File

@ -135,7 +135,7 @@ const usePaymentMethodRegistration = (
} catch ( e ) { } catch ( e ) {
if ( CURRENT_USER_IS_ADMIN || isEditor ) { if ( CURRENT_USER_IS_ADMIN || isEditor ) {
const errorText = sprintf( const errorText = sprintf(
/* translators: %s the id of the payment method being registered (bank transfer, Stripe...) */ /* translators: %s the id of the payment method being registered (bank transfer, cheque...) */
__( __(
`There was an error registering the payment method with id '%s': `, `There was an error registering the payment method with id '%s': `,
'woo-gutenberg-products-block' 'woo-gutenberg-products-block'
@ -153,8 +153,7 @@ const usePaymentMethodRegistration = (
// Re-dispatch available payment methods to store. // Re-dispatch available payment methods to store.
dispatcher( availablePaymentMethods ); dispatcher( availablePaymentMethods );
// Note: some payment methods use the `canMakePayment` callback to initialize / setup. // Note: Some 4rd party payment methods use the `canMakePayment` callback to initialize / setup.
// Example: Stripe CC, Stripe Payment Request.
// That's why we track "is initialized" state here. // That's why we track "is initialized" state here.
setIsInitialized( true ); setIsInitialized( true );
}, [ }, [

View File

@ -38,7 +38,7 @@ const NoPaymentMethodsPlaceholder = () => {
> >
<span className="wc-block-checkout__no-payment-methods-placeholder-description"> <span className="wc-block-checkout__no-payment-methods-placeholder-description">
{ __( { __(
'Your store does not have any payment methods configured that support the checkout block. Once you have configured a compatible payment method (e.g. Stripe) it will be shown here.', 'Your store does not have any payment methods configured that support the checkout block. Once you have configured a compatible payment method it will be shown here.',
'woo-gutenberg-products-block' 'woo-gutenberg-products-block'
) } ) }
</span> </span>

View File

@ -35,7 +35,7 @@ jest.mock(
() => ( { onChange } ) => ( () => ( { onChange } ) => (
<> <>
<span>Payment method options</span> <span>Payment method options</span>
<button onClick={ () => onChange( 'stripe' ) }> <button onClick={ () => onChange( 'credit-card' ) }>
Select new payment Select new payment
</button> </button>
</> </>
@ -43,7 +43,7 @@ jest.mock(
); );
const registerMockPaymentMethods = () => { const registerMockPaymentMethods = () => {
[ 'stripe' ].forEach( ( name ) => { [ 'credit-card' ].forEach( ( name ) => {
registerPaymentMethod( { registerPaymentMethod( {
name, name,
label: name, label: name,
@ -62,7 +62,7 @@ const registerMockPaymentMethods = () => {
}; };
const resetMockPaymentMethods = () => { const resetMockPaymentMethods = () => {
[ 'stripe' ].forEach( ( name ) => { [ 'credit-card' ].forEach( ( name ) => {
__experimentalDeRegisterPaymentMethod( name ); __experimentalDeRegisterPaymentMethod( name );
} ); } );
}; };
@ -137,7 +137,7 @@ describe( 'PaymentMethods', () => {
expect( savedPaymentMethodOptions ).not.toBeNull(); expect( savedPaymentMethodOptions ).not.toBeNull();
expect( paymentMethodOptions ).not.toBeNull(); expect( paymentMethodOptions ).not.toBeNull();
const savedToken = screen.queryByText( const savedToken = screen.queryByText(
/Active Payment Method: stripe/ /Active Payment Method: credit-card/
); );
expect( savedToken ).toBeNull(); expect( savedToken ).toBeNull();
} ); } );
@ -146,7 +146,7 @@ describe( 'PaymentMethods', () => {
await waitFor( () => { await waitFor( () => {
const activePaymentMethod = screen.queryByText( const activePaymentMethod = screen.queryByText(
/Active Payment Method: stripe/ /Active Payment Method: credit-card/
); );
expect( activePaymentMethod ).not.toBeNull(); expect( activePaymentMethod ).not.toBeNull();
} ); } );

View File

@ -1,161 +0,0 @@
/**
* 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',
'woo-gutenberg-products-block'
) }
</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-products-block' ) }
</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-products-block' ) }
</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-products-block' ) }
</label>
<ValidationInputError errorMessage={ cardCvcError } />
</div>
</div>
);
};

View File

@ -1,65 +0,0 @@
/**
* 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', 'woo-gutenberg-products-block' );
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',
'woo-gutenberg-products-block'
),
supports: {
showSavedCards: getStripeServerData().showSavedCards,
showSaveOption: getStripeServerData().showSaveOption,
features: getStripeServerData()?.supports ?? [],
},
};
export default stripeCcPaymentMethod;

View File

@ -1,92 +0,0 @@
/**
* 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/payment-method-interface').PaymentMethodInterface} 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>
);
};

View File

@ -1,97 +0,0 @@
/**
* 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/payment-method-interface').EventRegistrationProps} EventRegistrationProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').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;
};

View File

@ -1,115 +0,0 @@
/**
* 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 };
};

View File

@ -1,104 +0,0 @@
/**
* External dependencies
*/
import { useEffect } from '@wordpress/element';
/**
* @typedef {import('@woocommerce/type-defs/payment-method-interface').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,
] );
};

View File

@ -1,166 +0,0 @@
/**
* 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/payment-method-interface').EventRegistrationProps} EventRegistrationProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').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,
] );
};

View File

@ -1,22 +0,0 @@
/**
* External dependencies
*/
import {
registerPaymentMethod,
registerExpressPaymentMethod,
} from '@woocommerce/blocks-registry';
/**
* Internal dependencies
*/
import stripeCcPaymentMethod from './credit-card';
import paymentRequestPaymentMethod from './payment-request';
import { getStripeServerData } from './stripe-utils';
// Register Stripe Credit Card.
registerPaymentMethod( stripeCcPaymentMethod );
// Register Stripe Payment Request (Apple/Chrome Pay) if enabled.
if ( getStripeServerData().allowPaymentRequest ) {
registerExpressPaymentMethod( paymentRequestPaymentMethod );
}

View File

@ -1,2 +0,0 @@
export const applePayImage =
"data:image/svg+xml,%3Csvg width='264' height='48' viewBox='0 0 264 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='264' height='48' rx='3' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.114 16.6407C125.682 15.93 126.067 14.9756 125.966 14C125.135 14.0415 124.121 14.549 123.533 15.2602C123.006 15.8693 122.539 16.8641 122.661 17.7983C123.594 17.8797 124.526 17.3317 125.114 16.6407Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.955 17.982C124.601 17.9011 123.448 18.7518 122.801 18.7518C122.154 18.7518 121.163 18.0224 120.092 18.0421C118.696 18.0629 117.402 18.8524 116.694 20.1079C115.238 22.6196 116.31 26.3453 117.726 28.3909C118.414 29.4028 119.242 30.5174 120.334 30.4769C121.366 30.4365 121.77 29.8087 123.024 29.8087C124.277 29.8087 124.641 30.4769 125.733 30.4567C126.865 30.4365 127.573 29.4443 128.261 28.4313C129.049 27.2779 129.373 26.1639 129.393 26.1027C129.373 26.0825 127.209 25.2515 127.189 22.7606C127.169 20.6751 128.888 19.6834 128.969 19.6217C127.998 18.1847 126.481 18.0224 125.955 17.982Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M136.131 23.1804H138.834C140.886 23.1804 142.053 22.0752 142.053 20.1592C142.053 18.2432 140.886 17.1478 138.845 17.1478H136.131V23.1804ZM139.466 15.1582C142.411 15.1582 144.461 17.1903 144.461 20.1483C144.461 23.1172 142.369 25.1596 139.392 25.1596H136.131V30.3498H133.775V15.1582H139.466Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M152.198 26.224V25.3712L149.579 25.5397C148.106 25.6341 147.339 26.182 147.339 27.14C147.339 28.0664 148.138 28.6667 149.39 28.6667C150.988 28.6667 152.198 27.6449 152.198 26.224ZM145.046 27.2032C145.046 25.2551 146.529 24.1395 149.263 23.971L152.198 23.7922V22.9498C152.198 21.7181 151.388 21.0442 149.947 21.0442C148.758 21.0442 147.896 21.6548 147.717 22.5916H145.592C145.656 20.6232 147.507 19.1914 150.01 19.1914C152.703 19.1914 154.459 20.602 154.459 22.7917V30.351H152.282V28.5298H152.229C151.609 29.719 150.241 30.4666 148.758 30.4666C146.571 30.4666 145.046 29.1612 145.046 27.2032Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M156.461 34.4145V32.5934C156.608 32.6141 156.965 32.6354 157.155 32.6354C158.196 32.6354 158.785 32.1932 159.142 31.0564L159.353 30.3824L155.366 19.3281H157.827L160.604 28.298H160.657L163.434 19.3281H165.832L161.698 30.9402C160.752 33.6038 159.668 34.4778 157.376 34.4778C157.197 34.4778 156.618 34.4565 156.461 34.4145Z' fill='white'/%3E%3C/svg%3E%0A";

View File

@ -1,7 +0,0 @@
export const PAYMENT_METHOD_NAME = 'payment_request';
export const DEFAULT_STRIPE_EVENT_HANDLERS = {
shippingAddressChange: null,
shippingOptionChange: null,
source: null,
};

View File

@ -1,76 +0,0 @@
/**
* External dependencies
*/
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import { PAYMENT_METHOD_NAME } from './constants';
import { PaymentRequestExpress } from './payment-request-express';
import { applePayImage } from './apple-pay-preview';
import { getStripeServerData, loadStripe } from '../stripe-utils';
const ApplePayPreview = () => <img src={ applePayImage } alt="" />;
const canPayStripePromise = loadStripe();
const componentStripePromise = loadStripe();
let isStripeInitialized = false,
canPay = false;
// Initialise stripe API client and determine if payment method can be used
// in current environment (e.g. geo + shopper has payment settings configured).
function paymentRequestAvailable( { currencyCode, totalPrice } ) {
// Stripe only supports carts of greater value than 30 cents.
if ( totalPrice < 30 ) {
return false;
}
// If we've already initialised, return the cached results.
if ( isStripeInitialized ) {
return canPay;
}
return canPayStripePromise.then( ( stripe ) => {
if ( stripe === null ) {
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: {
label: 'Total',
amount: totalPrice,
pending: true,
},
country: getSetting( 'baseLocation', {} )?.country,
currency: currencyCode,
} );
return paymentRequest.canMakePayment().then( ( result ) => {
canPay = !! result;
isStripeInitialized = true;
return canPay;
} );
} );
}
const paymentRequestPaymentMethod = {
name: PAYMENT_METHOD_NAME,
content: <PaymentRequestExpress stripe={ componentStripePromise } />,
edit: <ApplePayPreview />,
canMakePayment: ( cartData ) =>
paymentRequestAvailable( {
currencyCode: cartData?.cartTotals?.currency_code?.toLowerCase(),
totalPrice: parseInt( cartData?.cartTotals?.total_price || 0, 10 ),
} ),
paymentMethodId: 'stripe',
supports: {
features: getStripeServerData()?.supports ?? [],
},
};
export default paymentRequestPaymentMethod;

View File

@ -1,113 +0,0 @@
/**
* External dependencies
*/
import { Elements, PaymentRequestButtonElement } from '@stripe/react-stripe-js';
/**
* Internal dependencies
*/
import { getStripeServerData } from '../stripe-utils';
import { useInitialization } from './use-initialization';
import { useCheckoutSubscriptions } from './use-checkout-subscriptions';
/**
* @typedef {import('../stripe-utils/type-defs').Stripe} Stripe
* @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest
* @typedef {import('@woocommerce/type-defs/payment-method-interface').PaymentMethodInterface} RegisteredPaymentMethodProps
*/
/**
* @typedef {Object} WithStripe
*
* @property {Stripe} [stripe] Stripe api (might not be present)
*/
/**
* @typedef {RegisteredPaymentMethodProps & WithStripe} StripeRegisteredPaymentMethodProps
*/
/**
* PaymentRequestExpressComponent
*
* @param {StripeRegisteredPaymentMethodProps} props Incoming props
*/
const PaymentRequestExpressComponent = ( {
shippingData,
billing,
eventRegistration,
onSubmit,
setExpressPaymentError,
emitResponse,
onClick,
onClose,
} ) => {
const {
paymentRequest,
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
isProcessing,
canMakePayment,
onButtonClick,
abortPayment,
completePayment,
paymentRequestType,
} = useInitialization( {
billing,
shippingData,
setExpressPaymentError,
onClick,
onClose,
onSubmit,
} );
useCheckoutSubscriptions( {
canMakePayment,
isProcessing,
eventRegistration,
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
billing,
shippingData,
emitResponse,
paymentRequestType,
completePayment,
abortPayment,
} );
// locale is not a valid value for the paymentRequestButton style.
const { theme } = getStripeServerData().button;
const paymentRequestButtonStyle = {
paymentRequestButton: {
type: 'default',
theme,
height: '48px',
},
};
return canMakePayment && paymentRequest ? (
<PaymentRequestButtonElement
onClick={ onButtonClick }
options={ {
// @ts-ignore
style: paymentRequestButtonStyle,
// @ts-ignore
paymentRequest,
} }
/>
) : null;
};
/**
* PaymentRequestExpress with stripe provider
*
* @param {StripeRegisteredPaymentMethodProps} props
*/
export const PaymentRequestExpress = ( props ) => {
const { locale } = getStripeServerData().button;
const { stripe } = props;
return (
<Elements stripe={ stripe } locale={ locale }>
<PaymentRequestExpressComponent { ...props } />
</Elements>
);
};

View File

@ -1,241 +0,0 @@
/**
* External dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import {
normalizeShippingOptions,
getTotalPaymentItem,
normalizeLineItems,
getBillingData,
getPaymentMethodData,
getShippingData,
} from '../stripe-utils';
/**
* @typedef {import('@woocommerce/type-defs/payment-method-interface').EventRegistrationProps} EventRegistrationProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').ShippingDataProps} ShippingDataProps
* @typedef {import('@woocommerce/type-defs/payment-method-interface').EmitResponseProps} EmitResponseProps
*/
/**
* @param {Object} props
*
* @param {boolean} props.canMakePayment Whether the payment request
* can make payment or not.
* @param {boolean} props.isProcessing Whether the express payment
* method is processing or not.
* @param {EventRegistrationProps} props.eventRegistration Various functions for
* registering observers to
* events.
* @param {Object} props.paymentRequestEventHandlers Cached handlers registered
* for paymentRequest events.
* @param {function(string):void} props.clearPaymentRequestEventHandler Clears the cached payment
* request event handler.
* @param {BillingDataProps} props.billing
* @param {ShippingDataProps} props.shippingData
* @param {EmitResponseProps} props.emitResponse
* @param {string} props.paymentRequestType The derived payment request
* type for the express
* payment being processed.
* @param {function(any):void} props.completePayment This is a callback
* receiving the source event
* and setting it to
* successful payment.
* @param {function(any,string):any} props.abortPayment This is a callback
* receiving the source
* event and setting it to
* failed payment.
*/
export const useCheckoutSubscriptions = ( {
canMakePayment,
isProcessing,
eventRegistration,
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
billing,
shippingData,
emitResponse,
paymentRequestType,
completePayment,
abortPayment,
} ) => {
const {
onShippingRateSuccess,
onShippingRateFail,
onShippingRateSelectSuccess,
onShippingRateSelectFail,
onPaymentProcessing,
onCheckoutAfterProcessingWithSuccess,
onCheckoutAfterProcessingWithError,
} = eventRegistration;
const { noticeContexts, responseTypes } = emitResponse;
const eventHandlers = useRef( paymentRequestEventHandlers );
const currentBilling = useRef( billing );
const currentShipping = useRef( shippingData );
const currentPaymentRequestType = useRef( paymentRequestType );
useEffect( () => {
eventHandlers.current = paymentRequestEventHandlers;
currentBilling.current = billing;
currentShipping.current = shippingData;
currentPaymentRequestType.current = paymentRequestType;
}, [
paymentRequestEventHandlers,
billing,
shippingData,
paymentRequestType,
] );
// subscribe to events.
useEffect( () => {
const onShippingRatesEvent = ( shippingRates ) => {
const handlers = eventHandlers.current;
const billingData = currentBilling.current;
if ( handlers.shippingAddressChange && isProcessing ) {
handlers.shippingAddressChange.updateWith( {
status: 'success',
shippingOptions: normalizeShippingOptions( shippingRates ),
total: getTotalPaymentItem( billingData.cartTotal ),
displayItems: normalizeLineItems(
billingData.cartTotalItems
),
} );
clearPaymentRequestEventHandler( 'shippingAddressChange' );
}
};
const onShippingRatesEventFail = ( currentErrorStatus ) => {
const handlers = eventHandlers.current;
if ( handlers.shippingAddressChange && isProcessing ) {
handlers.shippingAddressChange.updateWith( {
status: currentErrorStatus.hasInvalidAddress
? 'invalid_shipping_address'
: 'fail',
shippingOptions: [],
} );
}
clearPaymentRequestEventHandler( 'shippingAddressChange' );
};
const onShippingSelectedRate = ( forSuccess = true ) => () => {
const handlers = eventHandlers.current;
const shipping = currentShipping.current;
const billingData = currentBilling.current;
if (
handlers.shippingOptionChange &&
! shipping.isSelectingRate &&
isProcessing
) {
const updateObject = forSuccess
? {
status: 'success',
total: getTotalPaymentItem( billingData.cartTotal ),
displayItems: normalizeLineItems(
billingData.cartTotalItems
),
}
: {
status: 'fail',
};
handlers.shippingOptionChange.updateWith( updateObject );
clearPaymentRequestEventHandler( 'shippingOptionChange' );
}
};
const onProcessingPayment = () => {
const handlers = eventHandlers.current;
if ( handlers.sourceEvent && isProcessing ) {
const response = {
type: responseTypes.SUCCESS,
meta: {
billingData: getBillingData( handlers.sourceEvent ),
paymentMethodData: getPaymentMethodData(
handlers.sourceEvent,
currentPaymentRequestType.current
),
shippingData: getShippingData( handlers.sourceEvent ),
},
};
return response;
}
return { type: responseTypes.SUCCESS };
};
const onCheckoutComplete = ( checkoutResponse ) => {
const handlers = eventHandlers.current;
let response = { type: responseTypes.SUCCESS };
if ( handlers.sourceEvent && isProcessing ) {
const {
paymentStatus,
paymentDetails,
} = checkoutResponse.processingResponse;
if ( paymentStatus === responseTypes.SUCCESS ) {
completePayment( handlers.sourceEvent );
}
if (
paymentStatus === responseTypes.ERROR ||
paymentStatus === responseTypes.FAIL
) {
abortPayment( handlers.sourceEvent );
response = {
type: responseTypes.ERROR,
message: paymentDetails?.errorMessage,
messageContext: noticeContexts.EXPRESS_PAYMENTS,
retry: true,
};
}
clearPaymentRequestEventHandler( 'sourceEvent' );
}
return response;
};
if ( canMakePayment && isProcessing ) {
const unsubscribeShippingRateSuccess = onShippingRateSuccess(
onShippingRatesEvent
);
const unsubscribeShippingRateFail = onShippingRateFail(
onShippingRatesEventFail
);
const unsubscribeShippingRateSelectSuccess = onShippingRateSelectSuccess(
onShippingSelectedRate()
);
const unsubscribeShippingRateSelectFail = onShippingRateSelectFail(
onShippingRatesEventFail
);
const unsubscribePaymentProcessing = onPaymentProcessing(
onProcessingPayment
);
const unsubscribeCheckoutCompleteSuccess = onCheckoutAfterProcessingWithSuccess(
onCheckoutComplete
);
const unsubscribeCheckoutCompleteFail = onCheckoutAfterProcessingWithError(
onCheckoutComplete
);
return () => {
unsubscribeCheckoutCompleteFail();
unsubscribeCheckoutCompleteSuccess();
unsubscribePaymentProcessing();
unsubscribeShippingRateFail();
unsubscribeShippingRateSuccess();
unsubscribeShippingRateSelectSuccess();
unsubscribeShippingRateSelectFail();
};
}
return undefined;
}, [
canMakePayment,
isProcessing,
onShippingRateSuccess,
onShippingRateFail,
onShippingRateSelectSuccess,
onShippingRateSelectFail,
onPaymentProcessing,
onCheckoutAfterProcessingWithSuccess,
onCheckoutAfterProcessingWithError,
responseTypes,
noticeContexts,
completePayment,
abortPayment,
clearPaymentRequestEventHandler,
] );
};

View File

@ -1,48 +0,0 @@
/**
* External dependencies
*/
import { useState, useCallback } from '@wordpress/element';
/**
* Internal dependencies
*/
import { DEFAULT_STRIPE_EVENT_HANDLERS } from './constants';
/**
* A utility hook for maintaining an event handler cache.
*/
export const useEventHandlers = () => {
const [ paymentRequestEventHandlers, setEventHandlers ] = useState(
DEFAULT_STRIPE_EVENT_HANDLERS
);
const setPaymentRequestEventHandler = useCallback(
( eventName, handler ) => {
setEventHandlers( ( prevEventHandlers ) => {
return {
...prevEventHandlers,
[ eventName ]: handler,
};
} );
},
[ setEventHandlers ]
);
const clearPaymentRequestEventHandler = useCallback(
( eventName ) => {
// @ts-ignore
setEventHandlers( ( prevEventHandlers ) => {
// @ts-ignore
// eslint-disable-next-line no-unused-vars
const { [ eventName ]: __, ...newHandlers } = prevEventHandlers;
return newHandlers;
} );
},
[ setEventHandlers ]
);
return {
paymentRequestEventHandlers,
setPaymentRequestEventHandler,
clearPaymentRequestEventHandler,
};
};

View File

@ -1,251 +0,0 @@
/**
* External dependencies
*/
import { useEffect, useState, useRef, useCallback } from '@wordpress/element';
import { useStripe } from '@stripe/react-stripe-js';
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';
import isShallowEqual from '@wordpress/is-shallow-equal';
/**
* Internal dependencies
*/
import {
getPaymentRequest,
updatePaymentRequest,
canDoPaymentRequest,
normalizeShippingAddressForCheckout,
normalizeShippingOptionSelectionsForCheckout,
getStripeServerData,
pluckAddress,
normalizeShippingOptions,
} from '../stripe-utils';
import { useEventHandlers } from './use-event-handlers';
/**
* @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest
*/
export const useInitialization = ( {
billing,
shippingData,
setExpressPaymentError,
onClick,
onClose,
onSubmit,
} ) => {
const stripe = useStripe();
/**
* @type {[ StripePaymentRequest|null, function( StripePaymentRequest ):void]}
*/
// @ts-ignore
const [ paymentRequest, setPaymentRequest ] = useState( null );
const [ isFinished, setIsFinished ] = useState( false );
const [ isProcessing, setIsProcessing ] = useState( false );
const [ canMakePayment, setCanMakePayment ] = useState( false );
const [ paymentRequestType, setPaymentRequestType ] = useState( '' );
const currentShipping = useRef( shippingData );
const {
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
setPaymentRequestEventHandler,
} = useEventHandlers();
// Update refs when any change.
useEffect( () => {
currentShipping.current = shippingData;
}, [ shippingData ] );
// Create the initial paymentRequest object. Note, we can't do anything if stripe isn't available yet or we have zero total.
useEffect( () => {
if (
! stripe ||
! billing.cartTotal.value ||
isFinished ||
isProcessing ||
paymentRequest
) {
return;
}
const pr = getPaymentRequest( {
total: billing.cartTotal,
currencyCode: billing.currency.code.toLowerCase(),
countryCode: getSetting( 'baseLocation', {} )?.country,
shippingRequired: shippingData.needsShipping,
cartTotalItems: billing.cartTotalItems,
stripe,
} );
canDoPaymentRequest( pr ).then( ( result ) => {
setPaymentRequest( pr );
setPaymentRequestType( result.requestType || '' );
setCanMakePayment( result.canPay );
} );
}, [
billing.cartTotal,
billing.currency.code,
shippingData.needsShipping,
billing.cartTotalItems,
stripe,
isProcessing,
isFinished,
paymentRequest,
] );
// When the payment button is clicked, update the request and show it.
const onButtonClick = useCallback( () => {
setIsProcessing( true );
setIsFinished( false );
setExpressPaymentError( '' );
updatePaymentRequest( {
// @ts-ignore
paymentRequest,
total: billing.cartTotal,
currencyCode: billing.currency.code.toLowerCase(),
cartTotalItems: billing.cartTotalItems,
} );
onClick();
}, [
onClick,
paymentRequest,
setExpressPaymentError,
billing.cartTotal,
billing.currency.code,
billing.cartTotalItems,
] );
const abortPayment = useCallback( ( paymentMethod ) => {
paymentMethod.complete( 'fail' );
setIsProcessing( false );
setIsFinished( true );
}, [] );
const completePayment = useCallback( ( paymentMethod ) => {
paymentMethod.complete( 'success' );
setIsFinished( true );
setIsProcessing( false );
}, [] );
// whenever paymentRequest changes, hook in event listeners.
useEffect( () => {
const noop = { removeAllListeners: () => void null };
let shippingAddressChangeEvent = noop,
shippingOptionChangeEvent = noop,
sourceChangeEvent = noop,
cancelChangeEvent = noop;
if ( paymentRequest ) {
const cancelHandler = () => {
setIsFinished( false );
setIsProcessing( false );
setPaymentRequest( null );
onClose();
};
const shippingAddressChangeHandler = ( event ) => {
const newShippingAddress = normalizeShippingAddressForCheckout(
event.shippingAddress
);
if (
isShallowEqual(
pluckAddress( newShippingAddress ),
pluckAddress( currentShipping.current.shippingAddress )
)
) {
// the address is the same so no change needed.
event.updateWith( {
status: 'success',
shippingOptions: normalizeShippingOptions(
currentShipping.current.shippingRates
),
} );
} else {
// the address is different so let's set the new address and
// register the handler to be picked up by the shipping rate
// change event.
currentShipping.current.setShippingAddress(
normalizeShippingAddressForCheckout(
event.shippingAddress
)
);
setPaymentRequestEventHandler(
'shippingAddressChange',
event
);
}
};
const shippingOptionChangeHandler = ( event ) => {
currentShipping.current.setSelectedRates(
normalizeShippingOptionSelectionsForCheckout(
event.shippingOption
)
);
setPaymentRequestEventHandler( 'shippingOptionChange', event );
};
const sourceHandler = ( paymentMethod ) => {
if (
// eslint-disable-next-line no-undef
! getStripeServerData().allowPrepaidCard &&
paymentMethod.source.card.funding
) {
setExpressPaymentError(
/* eslint-disable-next-line @wordpress/i18n-text-domain */
__(
"Sorry, we're not accepting prepaid cards at this time.",
'woocommerce-gateway-stripe'
)
);
return;
}
setPaymentRequestEventHandler( 'sourceEvent', paymentMethod );
// kick off checkout processing step.
onSubmit();
};
// @ts-ignore
shippingAddressChangeEvent = paymentRequest.on(
'shippingaddresschange',
shippingAddressChangeHandler
);
// @ts-ignore
shippingOptionChangeEvent = paymentRequest.on(
'shippingoptionchange',
shippingOptionChangeHandler
);
// @ts-ignore
sourceChangeEvent = paymentRequest.on( 'source', sourceHandler );
// @ts-ignore
cancelChangeEvent = paymentRequest.on( 'cancel', cancelHandler );
}
return () => {
if ( paymentRequest ) {
shippingAddressChangeEvent.removeAllListeners();
shippingOptionChangeEvent.removeAllListeners();
sourceChangeEvent.removeAllListeners();
cancelChangeEvent.removeAllListeners();
}
};
}, [
paymentRequest,
canMakePayment,
isProcessing,
setPaymentRequestEventHandler,
setExpressPaymentError,
onSubmit,
onClose,
] );
return {
paymentRequest,
paymentRequestEventHandlers,
clearPaymentRequestEventHandler,
isProcessing,
canMakePayment,
onButtonClick,
abortPayment,
completePayment,
paymentRequestType,
};
};

View File

@ -1,28 +0,0 @@
export const errorTypes = {
INVALID_EMAIL: 'email_invalid',
INVALID_REQUEST: 'invalid_request_error',
API_CONNECTION: 'api_connection_error',
API_ERROR: 'api_error',
AUTHENTICATION_ERROR: 'authentication_error',
RATE_LIMIT_ERROR: 'rate_limit_error',
CARD_ERROR: 'card_error',
VALIDATION_ERROR: 'validation_error',
};
export const errorCodes = {
INVALID_NUMBER: 'invalid_number',
INVALID_EXPIRY_MONTH: 'invalid_expiry_month',
INVALID_EXPIRY_YEAR: 'invalid_expiry_year',
INVALID_CVC: 'invalid_cvc',
INCORRECT_NUMBER: 'incorrect_number',
INCOMPLETE_NUMBER: 'incomplete_number',
INCOMPLETE_CVC: 'incomplete_cvc',
INCOMPLETE_EXPIRY: 'incomplete_expiry',
EXPIRED_CARD: 'expired_card',
INCORRECT_CVC: 'incorrect_cvc',
INCORRECT_ZIP: 'incorrect_zip',
INVALID_EXPIRY_YEAR_PAST: 'invalid_expiry_year_past',
CARD_DECLINED: 'card_declined',
MISSING: 'missing',
PROCESSING_ERROR: 'processing_error',
};

View File

@ -1,3 +0,0 @@
export * from './normalize';
export * from './utils';
export * from './load-stripe';

View File

@ -1,22 +0,0 @@
/**
* External dependencies
*/
import { loadStripe } from '@stripe/stripe-js';
/**
* Internal dependencies
*/
import { getApiKey } from './utils';
const stripePromise = () =>
new Promise( ( resolve ) => {
try {
resolve( loadStripe( getApiKey() ) );
} catch ( error ) {
// In order to avoid showing console error publicly to users,
// we resolve instead of rejecting when there is an error.
resolve( { error } );
}
} );
export { stripePromise as loadStripe };

View File

@ -1,176 +0,0 @@
/**
* @typedef {import('./type-defs').StripePaymentItem} StripePaymentItem
* @typedef {import('./type-defs').StripeShippingOption} StripeShippingOption
* @typedef {import('./type-defs').StripeShippingAddress} StripeShippingAddress
* @typedef {import('./type-defs').StripePaymentResponse} StripePaymentResponse
* @typedef {import('@woocommerce/type-defs/payment-method-interface').PreparedCartTotalItem} CartTotalItem
* @typedef {import('@woocommerce/type-defs/cart').CartShippingOption} CartShippingOption
* @typedef {import('@woocommerce/type-defs/shipping').ShippingAddress} CartShippingAddress
* @typedef {import('@woocommerce/type-defs/billing').BillingData} CartBillingAddress
*/
/**
* Normalizes incoming cart total items for use as a displayItems with the
* Stripe api.
*
* @param {CartTotalItem[]} cartTotalItems CartTotalItems to normalize
* @param {boolean} pending Whether to mark items as pending or
* not
*
* @return {StripePaymentItem[]} An array of PaymentItems
*/
const normalizeLineItems = ( cartTotalItems, pending = false ) => {
return cartTotalItems
.map( ( cartTotalItem ) => {
return cartTotalItem.value
? {
amount: cartTotalItem.value,
label: cartTotalItem.label,
pending,
}
: false;
} )
.filter( Boolean );
};
/**
* Normalizes incoming cart shipping option items for use as shipping options
* with the Stripe api.
*
* @param {CartShippingOption[]} shippingOptions An array of CartShippingOption items.
*
* @return {StripeShippingOption[]} An array of Stripe shipping option items.
*/
const normalizeShippingOptions = ( shippingOptions ) => {
const rates = shippingOptions[ 0 ].shipping_rates;
return rates.map( ( rate ) => {
return {
id: rate.rate_id,
label: rate.name,
detail: rate.description,
amount: parseInt( rate.price, 10 ),
};
} );
};
/**
* Normalize shipping address information from stripe's address object to
* the cart shipping address object shape.
*
* @param {StripeShippingAddress} shippingAddress Stripe's shipping address item
*
* @return {CartShippingAddress} The shipping address in the shape expected by
* the cart.
*/
const normalizeShippingAddressForCheckout = ( shippingAddress ) => {
const address = {
first_name: shippingAddress.recipient
.split( ' ' )
.slice( 0, 1 )
.join( ' ' ),
last_name: shippingAddress.recipient
.split( ' ' )
.slice( 1 )
.join( ' ' ),
company: '',
address_1:
typeof shippingAddress.addressLine[ 0 ] === 'undefined'
? ''
: shippingAddress.addressLine[ 0 ],
address_2:
typeof shippingAddress.addressLine[ 1 ] === 'undefined'
? ''
: shippingAddress.addressLine[ 1 ],
city: shippingAddress.city,
state: shippingAddress.region,
country: shippingAddress.country,
postcode: shippingAddress.postalCode.replace( ' ', '' ),
};
return address;
};
/**
* Normalizes shipping option shape selection from Stripe's shipping option
* object to the expected shape for cart shipping option selections.
*
* @param {StripeShippingOption} shippingOption The customer's selected shipping
* option.
*
* @return {string[]} An array of ids (in this case will just be one)
*/
const normalizeShippingOptionSelectionsForCheckout = ( shippingOption ) => {
return shippingOption.id;
};
/**
* Returns the billing data extracted from the stripe payment response to the
* CartBillingData shape.
*
* @param {StripePaymentResponse} paymentResponse Stripe's payment response
* object.
*
* @return {CartBillingAddress} The cart billing data
*/
const getBillingData = ( paymentResponse ) => {
const source = paymentResponse.source;
const name = source && source.owner.name;
const billing = source && source.owner.address;
const payerEmail = paymentResponse.payerEmail || '';
const payerPhone = paymentResponse.payerPhone || '';
return {
first_name: name ? name.split( ' ' ).slice( 0, 1 ).join( ' ' ) : '',
last_name: name ? name.split( ' ' ).slice( 1 ).join( ' ' ) : '',
email: ( source && source.owner.email ) || payerEmail,
phone:
( source && source.owner.phone ) ||
payerPhone.replace( '/[() -]/g', '' ),
country: ( billing && billing.country ) || '',
address_1: ( billing && billing.line1 ) || '',
address_2: ( billing && billing.line2 ) || '',
city: ( billing && billing.city ) || '',
state: ( billing && billing.state ) || '',
postcode: ( billing && billing.postal_code ) || '',
company: '',
};
};
/**
* This returns extra payment method data to add to the payment method update
* request made by the checkout processor.
*
* @param {StripePaymentResponse} paymentResponse A stripe payment response
* object.
* @param {string} paymentRequestType The payment request type
* used for payment.
*
* @return {Object} An object with the extra payment data.
*/
const getPaymentMethodData = ( paymentResponse, paymentRequestType ) => {
return {
payment_method: 'stripe',
stripe_source: paymentResponse.source
? paymentResponse.source.id
: null,
payment_request_type: paymentRequestType,
};
};
const getShippingData = ( paymentResponse ) => {
return paymentResponse.shippingAddress
? {
address: normalizeShippingAddressForCheckout(
paymentResponse.shippingAddress
),
}
: null;
};
export {
normalizeLineItems,
normalizeShippingOptions,
normalizeShippingAddressForCheckout,
normalizeShippingOptionSelectionsForCheckout,
getBillingData,
getPaymentMethodData,
getShippingData,
};

View File

@ -1,324 +0,0 @@
/**
* Stripe PaymentItem object
*
* @typedef {Object} StripePaymentItem
*
* @property {string} label The label for the payment item.
* @property {number} amount The amount for the payment item (in subunits)
* @property {boolean} [pending] Whether or not the amount is pending update on
* recalculation.
*/
/**
* Stripe ShippingOption object
*
* @typedef {Object} StripeShippingOption
*
* @property {string} id A unique ID for the shipping option.
* @property {string} label A short label for the shipping option.
* @property {string} detail A longer description for the shipping option.
* @property {number} amount The amount to show for the shipping option
* (in subunits)
*/
/**
* @typedef {Object} StripeShippingAddress
*
* @property {string} country Two letter country code, capitalized
* (ISO3166 alpha-2).
* @property {Array} addressLine An array of address line items.
* @property {string} region The most coarse subdivision of a
* country. (state etc)
* @property {string} city The name of a city, town, village etc.
* @property {string} postalCode The postal or ZIP code.
* @property {string} recipient The name of the recipient.
* @property {string} phone The phone number of the recipient.
* @property {string} [sortingCode] The sorting code as used in France.
* Not present on Apple platforms.
* @property {string} [dependentLocality] A logical subdivision of a city.
* Not present on Apple platforms.
*/
/**
* @typedef {Object} StripeBillingDetails
*
* @property {Object} address The billing address
* @property {string} address.city The billing address city
* @property {string} address.country The billing address country
* @property {string} address.line1 The first line for the address
* @property {string} address.line2 The second line fro the address
* @property {string} address.postal_code The postal/zip code
* @property {string} address.state The state
* @property {string} email The billing email
* @property {string} name The billing name
* @property {string} phone The billing phone
* @property {Object} [verified_address] The verified address of the owner.
* @property {string} [verified_email] Provided by the payment provider.
* @property {string} [verified_phone] Provided by the payment provider.
* @property {string} [verified_name] Provided by the payment provider.
*/
/**
* @typedef {Object} StripeBillingCard
*
* @property {string} brand The card brand
* @property {Object} checks Various security checks
* @property {string} checks.address_line1_check If an address line1 was
* provided, results of the
* check.
* @property {string} checks.address_postal_code_check If a postal code was
* provided, results of the
* check.
* @property {string} checks.cvc_check If CVC provided, results
* of the check.
* @property {string} country Two-letter ISO code for
* the country on the card.
* @property {number} exp_month Two-digit number for
* card expiry month.
* @property {number} exp_year Two-digit number for
* card expiry year.
* @property {string} fingerprint Uniquely identifies this
* particular card number
* @property {string} funding The card funding type
* @property {Object} generated_from Details of the original
* PaymentMethod that
* created this object.
* @property {string} last4 The last 4 digits of the
* card
* @property {Object} three_d_secure_usage Contains details on how
* this card may be used for
* 3d secure
* @property {Object} wallet If this card is part of a
* card wallet, this
* contains the details of
* the card wallet.
*/
/**
* @typedef {Object} StripePaymentMethod
*
* @property {string} id Unique identifier for the
* object
* @property {StripeBillingDetails} billing_details The billing details for the
* payment method
* @property {StripeBillingCard} card Details on the card used to
* pay
* @property {string} customer The ID of the customer to
* which this payment method
* is saved.
* @property {Object} metadata Set of key-value pairs that
* can be attached to the
* object.
* @property {string} type Type of payment method
* @property {string} object The type of object. Always
* 'payment_method'. Can use
* to validate!
* @property {Object} card_present If this is a card present
* payment method, contains
* details about that card
* @property {number} created The timestamp for when the
* card was created.
* @property {Object} fpx If this is an fpx payment
* method, contains details
* about it.
* @property {Object} ideal If this is an ideal payment
* method, contains details
* about it.
* @property {boolean} livemode True if the object exists
* in live mode or if in test
* mode.
* @property {Object} sepa_debit If this is a sepa_debit
* payment method, contains
* details about it.
*/
/**
* @typedef {Object} StripeSource
*
* @property {string} id Unique identifier for
* object
* @property {number} amount A positive number in
* the smallest currency
* unit.
* @property {string} currency The three-letter ISO
* code for the currency
* @property {string} customer The ID of the customer
* to which this source
* is attached.
* @property {Object} metadata Arbitrary key-value
* pairs that can be
* attached.
* @property {StripeBillingDetails} owner Information about the
* owner of the payment
* made.
* @property {Object} [redirect] Information related to
* the redirect flow
* (present if the source
* is authenticated by
* redirect)
* @property {string} statement_descriptor Extra information
* about a source (will
* appear on customer's
* statement)
* @property {string} status The status of the
* source.
* @property {string} type The type of the source
* (it is a payment
* method type)
* @property {string} object Value is "source" can
* be used to validate.
* @property {string} client_secret The client secret of
* the source. Used for
* client-side retrieval
* using a publishable
* key.
* @property {Object} [code_verification] Information related to
* the code verification
* flow.
* @property {number} created When the source object
* was instantiated
* (timestamp).
* @property {string} flow The authentication
* flow of the source.
* @property {boolean} livemode If true then payment
* is made in live mode
* otherwise test mode.
* @property {Object} [receiver] Information related to
* the receiver flow.
* @property {Object} source_order Information about the
* items and shipping
* associated with the
* source.
* @property {string} usage Whether source should
* be reusable or not.
*/
/**
* @typedef {Object} StripePaymentResponse
*
* @property {Object} token A stripe token object
* @property {StripePaymentMethod} paymentMethod The stripe payment method
* object
* @property {?StripeSource} source Present if this was the
* result of a source event
* listener
* @property {Function} complete Call this when the token
* data has been processed.
* @property {string} [payerName] The customer's name.
* @property {string} [payerEmail] The customer's email.
* @property {string} [payerPhone] The customer's phone.
* @property {StripeShippingAddress} [shippingAddress] The final shipping
* address the customer
* indicated
* @property {StripeShippingOption} [shippingOption] The final shipping
* option the customer
* selected.
* @property {string} methodName The unique name of the
* payment handler the
* customer chose to
* authorize payment
*/
/**
* @typedef {Object} StripePaymentRequestOptions The configuration of stripe
* payment request options to
* pass in.
*
* @property {string} country Two-letter (ISO)
* country code.
* @property {string} currency Three letter currency
* code.
* @property {StripePaymentItem} total Shown to the customer.
* @property {StripePaymentItem[]} displayItems Line items shown to the
* customer.
* @property {boolean} requestPayerName Whether or not to
* collect the payer's
* name.
* @property {boolean} requestPayerEmail Whether or not to
* collect the payer's
* email.
* @property {boolean} requestPayerPhone Whether or not to
* collect the payer's
* phone.
* @property {boolean} requestShipping Whether to collect
* shipping address.
* @property {StripeShippingOption[]} shippingOptions Available shipping
* options.
*/
/**
* @typedef {Object} StripePaymentRequest Stripe payment request object.
*
* @property {function():Promise} canMakePayment Returns a promise that resolves
* with an object detailing if a
* browser payment API is
* available.
* @property {function()} show Shows the browser's payment
* interface (called automatically
* if payment request button in
* use)
* @property {function()} update Used to update a PaymentRequest
* object.
* @property {function()} on For registering callbacks on
* payment request events.
*/
/**
* @typedef {Object} Stripe Stripe api object.
* @property {any} api Various api properties
*/
/**
* @typedef {Object} CreditCardIcon
*
* @property {string} url Url to icon.
* @property {string} alt Alt text for icon.
*/
/* eslint-disable jsdoc/valid-types */
// [k:string]:CreditCardIcon triggers the above rule even though VSCode interprets it fine.
/**
* @typedef {Object} StripeServerData
*
* @property {string} stripeTotalLabel The string used for payment
* descriptor.
* @property {string} publicKey The public api key for stripe
* requests.
* @property {boolean} allowPrepaidCard True means that prepaid cards
* can be used for payment.
* @property {Object} button Contains button styles
* @property {string} button.type The type of button.
* @property {string} button.theme The theme for the button.
* @property {string} button.height The height (in pixels) for
* the button.
* @property {string} button.locale The locale to use for stripe
* elements.
* @property {boolean} inline_cc_form Whether stripe cc should use
* inline cc
* form or separate inputs.
* @property {{[k:string]:CreditCardIcon}} icons Contains supported cc icons.
* @property {boolean} showSavedCards Used to indicate whether saved cards
* can be used.
* @property {boolean} showSaveOption Used to indicate whether the option to
* save card can be displayed.
* @property {boolean} allowPaymentRequest True if merchant has enabled payment
* request (Chrome/Apple Pay).
* @property {Object} supports List of features supported by the payment gateway
*/
/* eslint-enable jsdoc/valid-types */
/**
* @typedef {Object} StripeElementOptions
*
* @property {Object} options The configuration object for stripe
* elements.
* @property {function(boolean)} onActive A callback for setting whether an
* element is active or not. "Active"
* means it's not empty.
* @property {string} error Any error message from the stripe
* element.
* @property {function(string)} setError A callback for setting an error
* message.
*/
export {};

View File

@ -1,281 +0,0 @@
/**
* External dependencies
*/
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { normalizeLineItems } from './normalize';
import { errorTypes, errorCodes } from './constants';
/**
* @typedef {import('./type-defs').StripeServerData} StripeServerData
* @typedef {import('./type-defs').StripePaymentItem} StripePaymentItem
* @typedef {import('./type-defs').StripePaymentRequest} StripePaymentRequest
* @typedef {import('@woocommerce/type-defs/payment-method-interface').PreparedCartTotalItem} CartTotalItem
*/
/**
* Stripe data comes form the server passed on a global object.
*
* @return {StripeServerData} Stripe server data.
*/
const getStripeServerData = () => {
const stripeServerData = getSetting( 'stripe_data', null );
if ( ! stripeServerData ) {
throw new Error( 'Stripe initialization data is not available' );
}
return stripeServerData;
};
/**
* Returns the public api key for the stripe payment method
*
* @throws Error
* @return {string} The public api key for the stripe payment method.
*/
const getApiKey = () => {
const apiKey = getStripeServerData().publicKey;
if ( ! apiKey ) {
throw new Error(
'There is no api key available for stripe. Make sure it is available on the wc.stripe_data.stripe.key property.'
);
}
return apiKey;
};
/**
* The total PaymentItem object used for the stripe PaymentRequest object.
*
* @param {CartTotalItem} total The total amount.
*
* @return {StripePaymentItem} The PaymentItem object used for stripe.
*/
const getTotalPaymentItem = ( total ) => {
return {
label:
getStripeServerData().stripeTotalLabel ||
__( 'Total', 'woo-gutenberg-products-block' ),
amount: total.value,
};
};
/**
* Returns a stripe payment request object
*
* @param {Object} config A configuration object for
* getting the payment request.
* @param {Object} config.stripe The stripe api.
* @param {CartTotalItem} config.total The amount for the total
* (in subunits) provided by
* checkout/cart.
* @param {string} config.currencyCode The currency code provided
* by checkout/cart.
* @param {string} config.countryCode The country code provided by
* checkout/cart.
* @param {boolean} config.shippingRequired Whether or not shipping is
* required.
* @param {CartTotalItem[]} config.cartTotalItems Array of line items provided
* by checkout/cart.
*
* @return {StripePaymentRequest} A stripe payment request object
*/
const getPaymentRequest = ( {
stripe,
total,
currencyCode,
countryCode,
shippingRequired,
cartTotalItems,
} ) => {
const options = {
total: getTotalPaymentItem( total ),
currency: currencyCode,
country: countryCode || 'US',
requestPayerName: true,
requestPayerEmail: true,
requestPayerPhone: true,
requestShipping: shippingRequired,
displayItems: normalizeLineItems( cartTotalItems ),
};
return stripe.paymentRequest( options );
};
/**
* Utility function for updating the Stripe PaymentRequest object
*
* @param {Object} update An object containing the
* things needed for the
* update
* @param {StripePaymentRequest} update.paymentRequest A Stripe payment request
* object
* @param {CartTotalItem} update.total A total line item.
* @param {string} update.currencyCode The currency code for the
* amount provided.
* @param {CartTotalItem[]} update.cartTotalItems An array of line items
* provided by the
* cart/checkout.
*/
const updatePaymentRequest = ( {
paymentRequest,
total,
currencyCode,
cartTotalItems,
} ) => {
paymentRequest.update( {
total: getTotalPaymentItem( total ),
currency: currencyCode,
displayItems: normalizeLineItems( cartTotalItems ),
} );
};
/**
* Returns whether or not the current session can do apple pay.
*
* @param {StripePaymentRequest} paymentRequest A Stripe PaymentRequest instance.
*
* @return {Promise<Object>} True means apple pay can be done.
*/
const canDoPaymentRequest = ( paymentRequest ) => {
return new Promise( ( resolve ) => {
paymentRequest.canMakePayment().then( ( result ) => {
if ( result ) {
const paymentRequestType = result.applePay
? 'apple_pay'
: 'payment_request_api';
resolve( { canPay: true, requestType: paymentRequestType } );
return;
}
resolve( { canPay: false } );
} );
} );
};
const isNonFriendlyError = ( type ) =>
[
errorTypes.INVALID_REQUEST,
errorTypes.API_CONNECTION,
errorTypes.API_ERROR,
errorTypes.AUTHENTICATION_ERROR,
errorTypes.RATE_LIMIT_ERROR,
].includes( type );
const getErrorMessageForCode = ( code ) => {
const messages = {
/* eslint-disable @wordpress/i18n-text-domain */
[ errorCodes.INVALID_NUMBER ]: __(
'The card number is not a valid credit card number.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INVALID_EXPIRY_MONTH ]: __(
'The card expiration month is invalid.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INVALID_EXPIRY_YEAR ]: __(
'The card expiration year is invalid.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INVALID_CVC ]: __(
'The card security code is invalid.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCORRECT_NUMBER ]: __(
'The card number is incorrect.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCOMPLETE_NUMBER ]: __(
'The card number is incomplete.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCOMPLETE_CVC ]: __(
'The card security code is incomplete.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCOMPLETE_EXPIRY ]: __(
'The card expiration date is incomplete.',
'woocommerce-gateway-stripe'
),
[ errorCodes.EXPIRED_CARD ]: __(
'The card has expired.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCORRECT_CVC ]: __(
'The card security code is incorrect.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INCORRECT_ZIP ]: __(
'The card zip code failed validation.',
'woocommerce-gateway-stripe'
),
[ errorCodes.INVALID_EXPIRY_YEAR_PAST ]: __(
'The card expiration year is in the past',
'woocommerce-gateway-stripe'
),
[ errorCodes.CARD_DECLINED ]: __(
'The card was declined.',
'woocommerce-gateway-stripe'
),
[ errorCodes.MISSING ]: __(
'There is no card on a customer that is being charged.',
'woocommerce-gateway-stripe'
),
[ errorCodes.PROCESSING_ERROR ]: __(
'An error occurred while processing the card.',
'woocommerce-gateway-stripe'
),
/* eslint-enable @wordpress/i18n-text-domain */
};
return messages[ code ] || null;
};
const getErrorMessageForTypeAndCode = ( type, code = '' ) => {
switch ( type ) {
case errorTypes.INVALID_EMAIL:
return __(
'Invalid email address, please correct and try again.',
'woo-gutenberg-products-block'
);
case isNonFriendlyError( type ):
return __(
'Unable to process this payment, please try again or use alternative method.',
'woo-gutenberg-products-block'
);
case errorTypes.CARD_ERROR:
return getErrorMessageForCode( code );
case errorTypes.VALIDATION_ERROR:
return ''; // These are shown inline.
}
return null;
};
/**
* pluckAddress takes a full address object and returns relevant fields for calculating
* shipping, so we can track when one of them change to update rates.
*
* @param {Object} address An object containing all address information
* @param {string} address.country
* @param {string} address.state
* @param {string} address.city
* @param {string} address.postcode
*
* @return {Object} pluckedAddress An object containing shipping address that are needed to fetch an address.
*/
const pluckAddress = ( { country, state, city, postcode } ) => ( {
country,
state,
city,
postcode: postcode.replace( ' ', '' ).toUpperCase(),
} );
export {
getStripeServerData,
getApiKey,
getTotalPaymentItem,
getPaymentRequest,
updatePaymentRequest,
canDoPaymentRequest,
getErrorMessageForTypeAndCode,
pluckAddress,
};

View File

@ -2,7 +2,7 @@ export const previewSavedPaymentMethods = {
cc: [ cc: [
{ {
method: { method: {
gateway: 'stripe', gateway: 'credit-card',
last4: '5678', last4: '5678',
brand: 'Visa', brand: 'Visa',
}, },

View File

@ -69,7 +69,7 @@ export interface PaymentMethodConfiguration {
paymentMethodId?: string; paymentMethodId?: string;
// Object that describes various features provided by the payment method. // Object that describes various features provided by the payment method.
supports: SupportsConfiguration; supports: SupportsConfiguration;
// Array of card types (brands) supported by the payment method. (See stripe/credit-card for example.) // Array of card types (brands) supported by the payment method.
icons?: null | PaymentMethodIcons; icons?: null | PaymentMethodIcons;
// A react node that will be used as a label for the payment method in the checkout. // A react node that will be used as a label for the payment method in the checkout.
label: ReactNode; label: ReactNode;

View File

@ -83,100 +83,6 @@
}, },
"args": 2 "args": 2
}, },
{
"name": "wc_stripe_allow_prepaid_card",
"file": "Payments/Integrations/Stripe.php",
"type": "filter",
"doc": {
"description": "Filters if prepaid cards are supported by Stripe.",
"long_description": "",
"tags": [
{
"name": "param",
"content": "True if prepaid cards are allowed.",
"types": [
"boolean"
],
"variable": "$allow_prepaid_card"
},
{
"name": "return",
"content": "",
"types": [
"boolean"
]
}
],
"long_description_html": ""
},
"args": 1
},
{
"name": "wc_stripe_display_save_payment_method_checkbox",
"file": "Payments/Integrations/Stripe.php",
"type": "filter",
"doc": {
"description": "Filters if the save payment method checkbox is shown for Stripe.",
"long_description": "This assumes that Stripe supports `tokenization` - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95",
"tags": [
{
"name": "see",
"content": "",
"refers": "https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271"
},
{
"name": "see",
"content": "",
"refers": "https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API"
},
{
"name": "param",
"content": "True if saved cards functionality is enabled.",
"types": [
"boolean"
],
"variable": "$saved_cards"
},
{
"name": "return",
"content": "",
"types": [
"boolean"
]
}
],
"long_description_html": "<p>This assumes that Stripe supports <code>tokenization</code> - currently this is true, based on <a href=\"https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95\">https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95</a></p>"
},
"args": 1
},
{
"name": "wc_stripe_payment_request_button_locale",
"file": "Payments/Integrations/Stripe.php",
"type": "filter",
"doc": {
"description": "Filters the payment request button locale.",
"long_description": "",
"tags": [
{
"name": "param",
"content": "Current locale. Defaults to en_US.",
"types": [
"string"
],
"variable": "$locale"
},
{
"name": "return",
"content": "",
"types": [
"string"
]
}
],
"long_description_html": ""
},
"args": 1
},
{ {
"name": "woocommerce_add_cart_item", "name": "woocommerce_add_cart_item",
"file": "StoreApi/Utilities/CartController.php", "file": "StoreApi/Utilities/CartController.php",

View File

@ -134,8 +134,6 @@ const entries = {
'./assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx', './assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx',
}, },
payments: { payments: {
'wc-payment-method-stripe':
'./assets/js/payment-method-extensions/payment-methods/stripe/index.js',
'wc-payment-method-cheque': 'wc-payment-method-cheque':
'./assets/js/payment-method-extensions/payment-methods/cheque/index.js', './assets/js/payment-method-extensions/payment-methods/cheque/index.js',
'wc-payment-method-paypal': 'wc-payment-method-paypal':

View File

@ -128,7 +128,7 @@ The following snippet explains how the WooCommerce Blocks repository is structur
│ The middleware code to handle Store API calls. │ The middleware code to handle Store API calls.
├── assets/js/payment-method-extensions ├── assets/js/payment-method-extensions
│ Functionality for the payment options such as PayPal and Stripe. │ Functionality for the payment options such as PayPal.
├── assets/js/previews ├── assets/js/previews
│ The previews of various components such the All Products Block. │ The previews of various components such the All Products Block.

View File

@ -10,9 +10,6 @@
- [__experimental_woocommerce_blocks_add_data_attributes_to_block](#__experimental_woocommerce_blocks_add_data_attributes_to_block) - [__experimental_woocommerce_blocks_add_data_attributes_to_block](#__experimental_woocommerce_blocks_add_data_attributes_to_block)
- [__experimental_woocommerce_blocks_add_data_attributes_to_namespace](#__experimental_woocommerce_blocks_add_data_attributes_to_namespace) - [__experimental_woocommerce_blocks_add_data_attributes_to_namespace](#__experimental_woocommerce_blocks_add_data_attributes_to_namespace)
- [__experimental_woocommerce_blocks_payment_gateway_features_list](#__experimental_woocommerce_blocks_payment_gateway_features_list) - [__experimental_woocommerce_blocks_payment_gateway_features_list](#__experimental_woocommerce_blocks_payment_gateway_features_list)
- [wc_stripe_allow_prepaid_card](#wc_stripe_allow_prepaid_card)
- [wc_stripe_display_save_payment_method_checkbox](#wc_stripe_display_save_payment_method_checkbox)
- [wc_stripe_payment_request_button_locale](#wc_stripe_payment_request_button_locale)
- [woocommerce_add_cart_item](#woocommerce_add_cart_item) - [woocommerce_add_cart_item](#woocommerce_add_cart_item)
- [woocommerce_add_cart_item_data](#woocommerce_add_cart_item_data) - [woocommerce_add_cart_item_data](#woocommerce_add_cart_item_data)
- [woocommerce_add_to_cart_sold_individually_quantity](#woocommerce_add_to_cart_sold_individually_quantity) - [woocommerce_add_to_cart_sold_individually_quantity](#woocommerce_add_to_cart_sold_individually_quantity)
@ -133,97 +130,6 @@ add_filter( '__experimental_woocommerce_blocks_payment_gateway_features_list', '
--- ---
## wc_stripe_allow_prepaid_card
Filters if prepaid cards are supported by Stripe.
```php
apply_filters( 'wc_stripe_allow_prepaid_card', boolean $allow_prepaid_card )
```
### Parameters
| Argument | Type | Description |
| -------- | ---- | ----------- |
| $allow_prepaid_card | boolean | True if prepaid cards are allowed. |
### Returns
`boolean`
### Source
- [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php)
---
## wc_stripe_display_save_payment_method_checkbox
Filters if the save payment method checkbox is shown for Stripe.
```php
apply_filters( 'wc_stripe_display_save_payment_method_checkbox', boolean $saved_cards )
```
### Description
<p>This assumes that Stripe supports <code>tokenization</code> - currently this is true, based on <a href="https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95">https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95</a></p>
### Parameters
| Argument | Type | Description |
| -------- | ---- | ----------- |
| $saved_cards | boolean | True if saved cards functionality is enabled. |
### Returns
`boolean`
### See
- https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271
- https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API
### Source
- [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php)
---
## wc_stripe_payment_request_button_locale
Filters the payment request button locale.
```php
apply_filters( 'wc_stripe_payment_request_button_locale', string $locale )
```
### Parameters
| Argument | Type | Description |
| -------- | ---- | ----------- |
| $locale | string | Current locale. Defaults to en_US. |
### Returns
`string`
### Source
- [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php)
---
## woocommerce_add_cart_item ## woocommerce_add_cart_item

View File

@ -131,6 +131,6 @@ In the `wp:woocommerce/product-search` substitute the URL used for the `action`
* [ ] Do critical flows for the Cart and Checkout blocks work? * [ ] Do critical flows for the Cart and Checkout blocks work?
* [ ] Address and shipping calculations * [ ] Address and shipping calculations
* [ ] Payment with core payment methods * [ ] Payment with core payment methods
* [ ] Payment with Stripe and saved payment methods * [ ] Payment with Stripe (extension) and saved payment methods
* [ ] Payment with Express payment methods (Chrome Pay or Apple Pay) * [ ] Payment with Express payment methods (Chrome Pay or Apple Pay)
* [ ] Make sure you test with logged in user and in browser incognito mode. * [ ] Make sure you test with logged in user and in browser incognito mode.

View File

@ -10,8 +10,6 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0+", "license": "GPL-3.0+",
"dependencies": { "dependencies": {
"@stripe/react-stripe-js": "1.6.0",
"@stripe/stripe-js": "1.16.0",
"@wordpress/autop": "3.2.3", "@wordpress/autop": "3.2.3",
"@wordpress/deprecated": "3.2.3", "@wordpress/deprecated": "3.2.3",
"@wordpress/icons": "6.1.1", "@wordpress/icons": "6.1.1",
@ -7340,24 +7338,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@stripe/react-stripe-js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.6.0.tgz",
"integrity": "sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.19.1",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.16.0.tgz",
"integrity": "sha512-ZSHbiwTrISoaTbpercmYGuY7QTg7HxfFyNgbJBaYbwHWbzMhpEdGTsmMpaBXIU6iiqwEEDaIyD8O6yJ+H5DWCg=="
},
"node_modules/@stylelint/postcss-css-in-js": { "node_modules/@stylelint/postcss-css-in-js": {
"version": "0.37.2", "version": "0.37.2",
"resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz",
@ -46739,19 +46719,6 @@
} }
} }
}, },
"@stripe/react-stripe-js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.6.0.tgz",
"integrity": "sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@stripe/stripe-js": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.16.0.tgz",
"integrity": "sha512-ZSHbiwTrISoaTbpercmYGuY7QTg7HxfFyNgbJBaYbwHWbzMhpEdGTsmMpaBXIU6iiqwEEDaIyD8O6yJ+H5DWCg=="
},
"@stylelint/postcss-css-in-js": { "@stylelint/postcss-css-in-js": {
"version": "0.37.2", "version": "0.37.2",
"resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz",

View File

@ -193,8 +193,6 @@
"npm": "^8.0.0" "npm": "^8.0.0"
}, },
"dependencies": { "dependencies": {
"@stripe/react-stripe-js": "1.6.0",
"@stripe/stripe-js": "1.16.0",
"@wordpress/autop": "3.2.3", "@wordpress/autop": "3.2.3",
"@wordpress/deprecated": "3.2.3", "@wordpress/deprecated": "3.2.3",
"@wordpress/icons": "6.1.1", "@wordpress/icons": "6.1.1",

View File

@ -12,7 +12,6 @@ use Automattic\WooCommerce\Blocks\Registry\Container;
use Automattic\WooCommerce\Blocks\RestApi; use Automattic\WooCommerce\Blocks\RestApi;
use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi; use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
@ -300,18 +299,8 @@ class Bootstrap {
/** /**
* Register payment method integrations with the container. * Register payment method integrations with the container.
*
* @internal Stripe is a temporary method that is used for setting up payment method integrations with Cart and
* Checkout blocks. This logic should get moved to the payment gateway extensions.
*/ */
protected function register_payment_methods() { protected function register_payment_methods() {
$this->container->register(
Stripe::class,
function( Container $container ) {
$asset_api = $container->get( AssetApi::class );
return new Stripe( $asset_api );
}
);
$this->container->register( $this->container->register(
Cheque::class, Cheque::class,
function( Container $container ) { function( Container $container ) {

View File

@ -4,7 +4,6 @@ namespace Automattic\WooCommerce\Blocks\Payments;
use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
@ -105,12 +104,6 @@ class Api {
* @param PaymentMethodRegistry $payment_method_registry Payment method registry instance. * @param PaymentMethodRegistry $payment_method_registry Payment method registry instance.
*/ */
public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) { public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) {
// This is temporarily registering Stripe until it's moved to the extension.
if ( class_exists( '\WC_Stripe', false ) && ! $payment_method_registry->is_registered( 'stripe' ) ) {
$payment_method_registry->register(
Package::container()->get( Stripe::class )
);
}
$payment_method_registry->register( $payment_method_registry->register(
Package::container()->get( Cheque::class ) Package::container()->get( Cheque::class )
); );

View File

@ -1,367 +0,0 @@
<?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
use Exception;
use WC_Stripe_Payment_Request;
use WC_Stripe_Helper;
use WC_Gateway_Stripe;
use Automattic\WooCommerce\Blocks\Assets\Api;
use Automattic\WooCommerce\Blocks\Payments\PaymentContext;
use Automattic\WooCommerce\Blocks\Payments\PaymentResult;
/**
* Stripe payment method integration
*
* Temporary integration of the stripe payment method for the new cart and
* checkout blocks. Once the api is demonstrated to be stable, this integration
* will be moved to the Stripe extension
*
* @since 2.6.0
*/
final class Stripe extends AbstractPaymentMethodType {
/**
* Payment method name defined by payment methods extending this class.
*
* @var string
*/
protected $name = 'stripe';
/**
* An instance of the Asset Api
*
* @var Api
*/
private $asset_api;
/**
* Constructor
*
* @param Api $asset_api An instance of Api.
*/
public function __construct( Api $asset_api ) {
$this->asset_api = $asset_api;
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 );
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 );
}
/**
* Initializes the payment method type.
*/
public function initialize() {
$this->settings = get_option( 'woocommerce_stripe_settings', [] );
}
/**
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
*
* @return boolean
*/
public function is_active() {
return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'];
}
/**
* Returns an array of scripts/handles to be registered for this payment method.
*
* @return array
*/
public function get_payment_method_script_handles() {
$this->asset_api->register_script(
'wc-payment-method-stripe',
'build/wc-payment-method-stripe.js',
[]
);
return [ 'wc-payment-method-stripe' ];
}
/**
* Returns an array of key=>value pairs of data made available to the payment methods script.
*
* @return array
*/
public function get_payment_method_data() {
return [
'stripeTotalLabel' => $this->get_total_label(),
'publicKey' => $this->get_publishable_key(),
'allowPrepaidCard' => $this->get_allow_prepaid_card(),
'title' => $this->get_title(),
'button' => [
'type' => $this->get_button_type(),
'theme' => $this->get_button_theme(),
'height' => $this->get_button_height(),
'locale' => $this->get_button_locale(),
],
'inline_cc_form' => $this->get_inline_cc_form(),
'icons' => $this->get_icons(),
'showSavedCards' => $this->get_show_saved_cards(),
'allowPaymentRequest' => $this->get_allow_payment_request(),
'showSaveOption' => $this->get_show_save_option(),
'supports' => $this->get_supported_features(),
];
}
/**
* Determine if store allows cards to be saved during checkout.
*
* @return bool True if merchant allows shopper to save card (payment method) during checkout).
*/
private function get_show_saved_cards() {
return isset( $this->settings['saved_cards'] ) ? 'yes' === $this->settings['saved_cards'] : false;
}
/**
* Determine if the checkbox to enable the user to save their payment method should be shown.
*
* @return bool True if the save payment checkbox should be displayed to the user.
*/
private function get_show_save_option() {
$saved_cards = $this->get_show_saved_cards();
/**
* Filters if the save payment method checkbox is shown for Stripe.
*
* This assumes that Stripe supports `tokenization` - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95
*
* @see https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271
* @see https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API
*
* @param boolean $saved_cards True if saved cards functionality is enabled.
* @return boolean
*/
return apply_filters( 'wc_stripe_display_save_payment_method_checkbox', filter_var( $saved_cards, FILTER_VALIDATE_BOOLEAN ) );
}
/**
* Returns the label to use accompanying the total in the stripe statement.
*
* @return string Statement descriptor.
*/
private function get_total_label() {
return ! empty( $this->settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->settings['statement_descriptor'] ) : '';
}
/**
* Returns the publishable api key for the Stripe service.
*
* @return string Public api key.
*/
private function get_publishable_key() {
$test_mode = ( ! empty( $this->settings['testmode'] ) && 'yes' === $this->settings['testmode'] );
$setting_key = $test_mode ? 'test_publishable_key' : 'publishable_key';
return ! empty( $this->settings[ $setting_key ] ) ? $this->settings[ $setting_key ] : '';
}
/**
* Returns whether to allow prepaid cards for payments.
*
* @return bool True means to allow prepaid card (default).
*/
private function get_allow_prepaid_card() {
/**
* Filters if prepaid cards are supported by Stripe.
*
* @param boolean $allow_prepaid_card True if prepaid cards are allowed.
* @return boolean
*/
return apply_filters( 'wc_stripe_allow_prepaid_card', true );
}
/**
* Returns the title string to use in the UI (customisable via admin settings screen).
*
* @return string Title / label string
*/
private function get_title() {
return isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'Credit / Debit Card', 'woo-gutenberg-products-block' );
}
/**
* Determine if store allows Payment Request buttons - e.g. Apple Pay / Chrome Pay.
*
* @return bool True if merchant has opted into payment request.
*/
private function get_allow_payment_request() {
$option = isset( $this->settings['payment_request'] ) ? $this->settings['payment_request'] : false;
return filter_var( $option, FILTER_VALIDATE_BOOLEAN );
}
/**
* Return the button type for the payment button.
*
* @return string Defaults to 'default'.
*/
private function get_button_type() {
return isset( $this->settings['payment_request_button_type'] ) ? $this->settings['payment_request_button_type'] : 'default';
}
/**
* Return the theme to use for the payment button.
*
* @return string Defaults to 'dark'.
*/
private function get_button_theme() {
return isset( $this->settings['payment_request_button_theme'] ) ? $this->settings['payment_request_button_theme'] : 'dark';
}
/**
* Return the height for the payment button.
*
* @return string A pixel value for the height (defaults to '64').
*/
private function get_button_height() {
return isset( $this->settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->settings['payment_request_button_height'] ) : '64';
}
/**
* Return the inline cc option.
*
* @return boolean True if the inline CC form option is enabled.
*/
private function get_inline_cc_form() {
return isset( $this->settings['inline_cc_form'] ) && 'yes' === $this->settings['inline_cc_form'];
}
/**
* Return the locale for the payment button.
*
* @return string Defaults to en_US.
*/
private function get_button_locale() {
/**
* Filters the payment request button locale.
*
* @param string $locale Current locale. Defaults to en_US.
* @return string
*/
return apply_filters( 'wc_stripe_payment_request_button_locale', substr( get_locale(), 0, 2 ) );
}
/**
* Return the icons urls.
*
* @return array Arrays of icons metadata.
*/
private function get_icons() {
$icons_src = [
'visa' => [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg',
'alt' => __( 'Visa', 'woo-gutenberg-products-block' ),
],
'amex' => [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg',
'alt' => __( 'American Express', 'woo-gutenberg-products-block' ),
],
'mastercard' => [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg',
'alt' => __( 'Mastercard', 'woo-gutenberg-products-block' ),
],
];
if ( 'USD' === get_woocommerce_currency() ) {
$icons_src['discover'] = [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg',
'alt' => __( 'Discover', 'woo-gutenberg-products-block' ),
];
$icons_src['jcb'] = [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg',
'alt' => __( 'JCB', 'woo-gutenberg-products-block' ),
];
$icons_src['diners'] = [
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg',
'alt' => __( 'Diners', 'woo-gutenberg-products-block' ),
];
}
return $icons_src;
}
/**
* Add payment request data to the order meta as hooked on the
* woocommerce_rest_checkout_process_payment_with_context action.
*
* @param PaymentContext $context Holds context for the payment.
* @param PaymentResult $result Result object for the payment.
*/
public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) {
$data = $context->payment_data;
if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) {
// phpcs:ignore WordPress.Security.NonceVerification
$post_data = $_POST;
$_POST = $context->payment_data;
$this->add_order_meta( $context->order, $data['payment_request_type'] );
$_POST = $post_data;
}
// hook into stripe error processing so that we can capture the error to
// payment details (which is added to notices and thus not helpful for
// this context).
if ( 'stripe' === $context->payment_method ) {
add_action(
'wc_gateway_stripe_process_payment_error',
function( $error ) use ( &$result ) {
$payment_details = $result->payment_details;
$payment_details['errorMessage'] = wp_strip_all_tags( $error->getLocalizedMessage() );
$result->set_payment_details( $payment_details );
}
);
}
}
/**
* Handles any potential stripe intents on the order that need handled.
*
* This is configured to execute after legacy payment processing has
* happened on the woocommerce_rest_checkout_process_payment_with_context
* action hook.
*
* @param PaymentContext $context Holds context for the payment.
* @param PaymentResult $result Result object for the payment.
*/
public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) {
if ( 'stripe' === $context->payment_method
&& (
! empty( $result->payment_details['payment_intent_secret'] )
|| ! empty( $result->payment_details['setup_intent_secret'] )
)
) {
$payment_details = $result->payment_details;
$payment_details['verification_endpoint'] = add_query_arg(
[
'order' => $context->order->get_id(),
'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ),
'redirect_to' => rawurlencode( $result->redirect_url ),
],
home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' )
);
$result->set_payment_details( $payment_details );
$result->set_status( 'success' );
}
}
/**
* Handles adding information about the payment request type used to the order meta.
*
* @param \WC_Order $order The order being processed.
* @param string $payment_request_type The payment request type used for payment.
*/
private function add_order_meta( \WC_Order $order, string $payment_request_type ) {
if ( 'apple_pay' === $payment_request_type ) {
$order->set_payment_method_title( 'Apple Pay (Stripe)' );
$order->save();
}
if ( 'payment_request_api' === $payment_request_type ) {
$order->set_payment_method_title( 'Chrome Payment Request (Stripe)' );
$order->save();
}
}
/**
* Returns an array of supported features.
*
* @return string[]
*/
public function get_supported_features() {
$gateway = new WC_Gateway_Stripe();
return array_filter( $gateway->supports, array( $gateway, 'supports' ) );
}
}