diff --git a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-element-field.tsx b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-element-field.tsx index ea1ef15d9d693..2c6d1c3c1af18 100644 --- a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-element-field.tsx +++ b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-element-field.tsx @@ -3,8 +3,8 @@ import { CardElement } from '@stripe/react-stripe-js'; import { useSelect } from '@wordpress/data'; import { useI18n } from '@wordpress/react-i18n'; import classnames from 'classnames'; +import { creditCardStore } from 'calypso/state/partner-portal/credit-card-form'; import type { StripeElementChangeEvent, StripeElementStyle } from '@stripe/stripe-js'; -import type { CreditCardSelectors } from 'calypso/state/partner-portal/types'; export default function CreditCardElementField( { setIsStripeFullyLoaded, @@ -19,7 +19,7 @@ export default function CreditCardElementField( { const { formStatus } = useFormStatus(); const isDisabled = formStatus !== FormStatus.READY; const { card: cardError } = useSelect( - ( select ) => ( select( 'credit-card' ) as CreditCardSelectors ).getCardDataErrors(), + ( select ) => select( creditCardStore ).getCardDataErrors(), [] ); diff --git a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-submit-button.tsx b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-submit-button.tsx index db222c4133062..46e598b01c0ce 100644 --- a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-submit-button.tsx +++ b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/credit-card-submit-button.tsx @@ -1,41 +1,40 @@ import { Button, FormStatus, useFormStatus } from '@automattic/composite-checkout'; import { useElements, CardElement } from '@stripe/react-stripe-js'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useI18n } from '@wordpress/react-i18n'; import debugFactory from 'debug'; import { useMemo } from 'react'; +import { creditCardStore } from 'calypso/state/partner-portal/credit-card-form'; import type { StripeConfiguration } from '@automattic/calypso-stripe'; import type { ProcessPayment } from '@automattic/composite-checkout'; -import type { StoreState } from '@automattic/wpcom-checkout'; import type { Stripe } from '@stripe/stripe-js'; -import type { I18n } from '@wordpress/i18n'; -import type { State } from 'calypso/state/partner-portal/credit-card-form/reducer'; -import type { CreditCardSelectors } from 'calypso/state/partner-portal/types'; const debug = debugFactory( 'calypso:partner-portal:credit-card' ); export default function CreditCardSubmitButton( { disabled, onClick, - store, stripe, stripeConfiguration, activeButtonText, }: { disabled?: boolean; onClick?: ProcessPayment; - store: State; stripe: Stripe | null; stripeConfiguration: StripeConfiguration | null; activeButtonText: string | undefined; } ) { const { __ } = useI18n(); - const fields: StoreState< string > = useSelect( - ( select ) => ( select( 'credit-card' ) as CreditCardSelectors ).getFields(), - [] - ); - const useAsPrimaryPaymentMethod: boolean = useSelect( - ( select ) => ( select( 'credit-card' ) as CreditCardSelectors ).useAsPrimaryPaymentMethod(), + const { fields, useAsPrimaryPaymentMethod, errors, incompleteFieldKeys } = useSelect( + ( select ) => { + const store = select( creditCardStore ); + return { + fields: store.getFields(), + useAsPrimaryPaymentMethod: store.useAsPrimaryPaymentMethod(), + errors: store.getCardDataErrors(), + incompleteFieldKeys: store.getIncompleteFieldKeys(), + }; + }, [] ); const cardholderName = fields.cardholderName; @@ -54,30 +53,53 @@ export default function CreditCardSubmitButton( { return __( 'Please wait…' ); }, [ formStatus, activeButtonText, __ ] ); + const { setCardDataError, setFieldValue, setFieldError } = useDispatch( creditCardStore ); + + const handleButtonClick = () => { + debug( 'validating credit card fields' ); + + if ( ! cardholderName?.value.length ) { + // Touch the field so it displays a validation error + setFieldValue( 'cardholderName', '' ); + setFieldError( 'cardholderName', __( 'This field is required' ) ); + } + const areThereErrors = Object.keys( errors ).some( ( errorKey ) => errors[ errorKey ] ); + + if ( incompleteFieldKeys.length > 0 ) { + // Show "this field is required" for each incomplete field + incompleteFieldKeys.map( ( key: string ) => + setCardDataError( key, __( 'This field is required' ) ) + ); + } + + if ( areThereErrors || ! cardholderName?.value.length || incompleteFieldKeys.length > 0 ) { + // credit card is invalid + return false; + } + + debug( 'submitting stripe payment' ); + + if ( ! onClick ) { + throw new Error( + 'Missing onClick prop; CreditCardSubmitButton must be used as a payment button in CheckoutSubmitButton' + ); + } + + onClick( { + stripe, + name: cardholderName?.value, + stripeConfiguration, + cardElement, + useAsPrimaryPaymentMethod, + } ); + return; + }; + return ( ); } - -function isCreditCardFormValid( store: State, __: I18n[ '__' ] ) { - debug( 'validating credit card fields' ); - - const fields = store.selectors.getFields( store.getState() ); - const cardholderName = fields.cardholderName; - if ( ! cardholderName?.value.length ) { - // Touch the field so it displays a validation error - store.dispatch( store.actions.setFieldValue( 'cardholderName', '' ) ); - store.dispatch( - store.actions.setFieldError( 'cardholderName', __( 'This field is required' ) ) - ); - } - const errors = store.selectors.getCardDataErrors( store.getState() ); - const incompleteFieldKeys = store.selectors.getIncompleteFieldKeys( store.getState() ); - const areThereErrors = Object.keys( errors ).some( ( errorKey ) => errors[ errorKey ] ); - - if ( incompleteFieldKeys.length > 0 ) { - // Show "this field is required" for each incomplete field - incompleteFieldKeys.map( ( key: string ) => - store.dispatch( store.actions.setCardDataError( key, __( 'This field is required' ) ) ) - ); - } - if ( areThereErrors || ! cardholderName?.value.length || incompleteFieldKeys.length > 0 ) { - return false; - } - return true; -} diff --git a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/index.tsx b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/index.tsx index ad520178ee92d..7de8574aefa1d 100644 --- a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/index.tsx +++ b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/index.tsx @@ -7,23 +7,23 @@ import { useState } from 'react'; import { useDispatch as useReduxDispatch } from 'react-redux'; import { useRecentPaymentMethodsQuery } from 'calypso/jetpack-cloud/sections/partner-portal/hooks'; import { recordTracksEvent } from 'calypso/state/analytics/actions'; +import { creditCardStore } from 'calypso/state/partner-portal/credit-card-form'; import CreditCardElementField from './credit-card-element-field'; import CreditCardLoading from './credit-card-loading'; import SetAsPrimaryPaymentMethod from './set-as-primary-payment-method'; import type { StoreState } from '@automattic/wpcom-checkout'; import type { StripeElementChangeEvent, StripeElementStyle } from '@stripe/stripe-js'; -import type { CreditCardSelectors } from 'calypso/state/partner-portal/types'; import './style.scss'; export default function CreditCardFields() { const { __ } = useI18n(); const [ isStripeFullyLoaded, setIsStripeFullyLoaded ] = useState( false ); const fields: StoreState< string > = useSelect( - ( select ) => ( select( 'credit-card' ) as CreditCardSelectors ).getFields(), + ( select ) => select( creditCardStore ).getFields(), [] ); const useAsPrimaryPaymentMethod: boolean = useSelect( - ( select ) => ( select( 'credit-card' ) as CreditCardSelectors ).useAsPrimaryPaymentMethod(), + ( select ) => select( creditCardStore ).useAsPrimaryPaymentMethod(), [] ); const getField = ( key: string | number ) => fields[ key ] || {}; diff --git a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/test/credit-card-submit-button.jsx b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/test/credit-card-submit-button.jsx index bfabe38501737..08833f8494a69 100644 --- a/client/jetpack-cloud/sections/partner-portal/credit-card-fields/test/credit-card-submit-button.jsx +++ b/client/jetpack-cloud/sections/partner-portal/credit-card-fields/test/credit-card-submit-button.jsx @@ -7,20 +7,16 @@ import { Elements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useSelect } from '@wordpress/data'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import * as actions from 'calypso/state/partner-portal/credit-card-form/actions'; import * as selectors from 'calypso/state/partner-portal/credit-card-form/selectors'; import CreditCardSubmitButton from '../credit-card-submit-button'; -const mockUseSelector = () => () => null; - jest.mock( '@stripe/stripe-js', () => ( { loadStripe: () => null, } ) ); -jest.mock( '@wordpress/data' ); jest.mock( 'calypso/state/partner-portal/credit-card-form/selectors', () => { const items = jest.requireActual( 'calypso/state/partner-portal/credit-card-form/selectors' ); return { @@ -71,7 +67,6 @@ describe( '', () => { beforeEach( () => { // Re-mock dependencies jest.clearAllMocks(); - useSelect.mockImplementation( mockUseSelector ); useFormStatus.mockImplementation( () => { return { formStatus: 'ready', @@ -81,7 +76,6 @@ describe( '', () => { } ); afterEach( () => { - useSelect.mockClear(); useFormStatus.mockClear(); } ); @@ -106,7 +100,6 @@ describe( '', () => { const buttonText = 'Save payment method'; const props = { - store: newStore, stripe, stripeConfiguration, disabled: false, diff --git a/client/jetpack-cloud/sections/partner-portal/payment-methods/hooks/use-create-stored-credit-card.ts b/client/jetpack-cloud/sections/partner-portal/payment-methods/hooks/use-create-stored-credit-card.ts index 540a3aa9baf91..27149b956b543 100644 --- a/client/jetpack-cloud/sections/partner-portal/payment-methods/hooks/use-create-stored-credit-card.ts +++ b/client/jetpack-cloud/sections/partner-portal/payment-methods/hooks/use-create-stored-credit-card.ts @@ -1,6 +1,5 @@ import { useMemo } from 'react'; import { createStoredCreditCardMethod } from 'calypso/jetpack-cloud/sections/partner-portal/payment-methods/stored-credit-card-method'; -import { createStoredCreditCardPaymentMethodStore } from 'calypso/state/partner-portal/credit-card-form'; import type { StripeConfiguration, StripeLoadingError } from '@automattic/calypso-stripe'; import type { PaymentMethod } from '@automattic/composite-checkout'; import type { Stripe } from '@stripe/stripe-js'; @@ -20,18 +19,15 @@ export function useCreateStoredCreditCardMethod( { } ): PaymentMethod | null { const shouldLoadStripeMethod = ! isStripeLoading && ! stripeLoadingError; - const store = useMemo( () => createStoredCreditCardPaymentMethodStore(), [] ); - return useMemo( () => shouldLoadStripeMethod ? createStoredCreditCardMethod( { - store, stripe, stripeConfiguration, activePayButtonText, } ) : null, - [ shouldLoadStripeMethod, store, stripe, stripeConfiguration, activePayButtonText ] + [ shouldLoadStripeMethod, stripe, stripeConfiguration, activePayButtonText ] ); } diff --git a/client/jetpack-cloud/sections/partner-portal/payment-methods/stored-credit-card-method.tsx b/client/jetpack-cloud/sections/partner-portal/payment-methods/stored-credit-card-method.tsx index b6235b8a147d2..dd24da0b70d51 100644 --- a/client/jetpack-cloud/sections/partner-portal/payment-methods/stored-credit-card-method.tsx +++ b/client/jetpack-cloud/sections/partner-portal/payment-methods/stored-credit-card-method.tsx @@ -3,15 +3,12 @@ import CreditCardSubmitButton from 'calypso/jetpack-cloud/sections/partner-porta import type { StripeConfiguration } from '@automattic/calypso-stripe'; import type { PaymentMethod } from '@automattic/composite-checkout'; import type { Stripe } from '@stripe/stripe-js'; -import type { State } from 'calypso/state/partner-portal/credit-card-form/reducer'; export function createStoredCreditCardMethod( { - store, stripe, stripeConfiguration, activePayButtonText = undefined, }: { - store: State; stripe: Stripe | null; stripeConfiguration: StripeConfiguration | null; activePayButtonText?: string | undefined; @@ -23,7 +20,6 @@ export function createStoredCreditCardMethod( { activeContent: , submitButton: ( ( select( 'credit-card' ) as CreditCardSelectors ).useAsPrimaryPaymentMethod(), + ( select ) => select( creditCardStore ).useAsPrimaryPaymentMethod(), [] ); diff --git a/client/state/partner-portal/credit-card-form/index.ts b/client/state/partner-portal/credit-card-form/index.ts index 76d6df2f387f4..661069397b8e6 100644 --- a/client/state/partner-portal/credit-card-form/index.ts +++ b/client/state/partner-portal/credit-card-form/index.ts @@ -1,14 +1,12 @@ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; import * as actions from 'calypso/state/partner-portal/credit-card-form/actions'; import reducer from 'calypso/state/partner-portal/credit-card-form/reducer'; import * as selectors from 'calypso/state/partner-portal/credit-card-form/selectors'; -export function createStoredCreditCardPaymentMethodStore(): Record< string, unknown > { - const store = registerStore( 'credit-card', { - reducer, - actions, - selectors, - } ); +export const creditCardStore = createReduxStore( 'credit-card', { + reducer, + actions, + selectors, +} ); - return { ...store, actions, selectors }; -} +register( creditCardStore ); diff --git a/client/state/partner-portal/types.ts b/client/state/partner-portal/types.ts index 783220e74e4d4..2288a8a434b61 100644 --- a/client/state/partner-portal/types.ts +++ b/client/state/partner-portal/types.ts @@ -5,10 +5,6 @@ import { LicenseSortDirection, LicenseSortField, } from 'calypso/jetpack-cloud/sections/partner-portal/types'; -import * as creditCardSelectors from './credit-card-form/selectors'; -import type { SelectFromMap } from '@automattic/data-stores'; - -export type CreditCardSelectors = SelectFromMap< typeof creditCardSelectors >; /** * Utility.