Hook up payments gateway data store (https://github.com/woocommerce/woocommerce-admin/pull/7038)
* Hook up payment gateway data store * Fix deprecated onSubmitCallback in dynamic form * Throw catchable errors in data store * Provide a way to get errors from the data store * Hook up payment connection update with data store * Remove redundant requesting state on selectors * Add changelog entry * Handle PR feedback * Fix linting errors
This commit is contained in:
parent
71d34c4c21
commit
dc175824c9
|
@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
|
|||
import { Button } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { DynamicForm, WooRemotePaymentForm } from '@woocommerce/components';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { PAYMENT_GATEWAYS_STORE_NAME } from '@woocommerce/data';
|
||||
import { useSlot } from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ export const PaymentConnect = ( {
|
|||
recordConnectStartEvent,
|
||||
} ) => {
|
||||
const {
|
||||
key,
|
||||
id,
|
||||
oauth_connection_url: oAuthConnectionUrl,
|
||||
setup_help_text: setupHelpText,
|
||||
required_settings_keys: settingKeys,
|
||||
|
@ -28,9 +28,9 @@ export const PaymentConnect = ( {
|
|||
title,
|
||||
} = paymentGateway;
|
||||
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const { createNotice } = useDispatch( 'core/notices' );
|
||||
const slot = useSlot( `woocommerce_remote_payment_form_${ key }` );
|
||||
const { updatePaymentGateway } = useDispatch( PAYMENT_GATEWAYS_STORE_NAME );
|
||||
const slot = useSlot( `woocommerce_remote_payment_form_${ id }` );
|
||||
const hasFills = Boolean( slot?.fills?.length );
|
||||
const fields = settingKeys
|
||||
? settingKeys
|
||||
|
@ -38,37 +38,34 @@ export const PaymentConnect = ( {
|
|||
.filter( Boolean )
|
||||
: [];
|
||||
|
||||
const isOptionsRequesting = useSelect( ( select ) => {
|
||||
const { isOptionsUpdating } = select( OPTIONS_STORE_NAME );
|
||||
const { isUpdating } = useSelect( ( select ) => {
|
||||
const { isPaymentGatewayUpdating } = select(
|
||||
PAYMENT_GATEWAYS_STORE_NAME
|
||||
);
|
||||
|
||||
return isOptionsUpdating();
|
||||
return {
|
||||
isUpdating: isPaymentGatewayUpdating(),
|
||||
};
|
||||
} );
|
||||
|
||||
const updateSettings = async ( values ) => {
|
||||
recordConnectStartEvent( key );
|
||||
const handleSubmit = ( values ) => {
|
||||
recordConnectStartEvent( id );
|
||||
|
||||
const options = {};
|
||||
|
||||
fields.forEach( ( field ) => {
|
||||
const optionName = field.option || field.name;
|
||||
options[ optionName ] = values[ field.name ];
|
||||
} );
|
||||
|
||||
if ( ! Object.keys( options ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = await updateOptions( {
|
||||
...options,
|
||||
} );
|
||||
|
||||
if ( update.success ) {
|
||||
markConfigured( key );
|
||||
updatePaymentGateway( id, {
|
||||
enabled: true,
|
||||
settings: values,
|
||||
} )
|
||||
.then( ( result ) => {
|
||||
if ( result && result.id === id ) {
|
||||
markConfigured( id );
|
||||
createNotice(
|
||||
'success',
|
||||
title + __( ' connected successfully', 'woocommerce-admin' )
|
||||
title +
|
||||
__( ' connected successfully', 'woocommerce-admin' )
|
||||
);
|
||||
} else {
|
||||
}
|
||||
} )
|
||||
.catch( () => {
|
||||
createNotice(
|
||||
'error',
|
||||
__(
|
||||
|
@ -76,7 +73,7 @@ export const PaymentConnect = ( {
|
|||
'woocommerce-admin'
|
||||
)
|
||||
);
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
const validate = ( values ) => {
|
||||
|
@ -105,8 +102,8 @@ export const PaymentConnect = ( {
|
|||
const DefaultForm = ( props ) => (
|
||||
<DynamicForm
|
||||
fields={ fields }
|
||||
isBusy={ isOptionsRequesting }
|
||||
onSubmit={ updateSettings }
|
||||
isBusy={ isUpdating }
|
||||
onSubmit={ handleSubmit }
|
||||
submitLabel={ __( 'Proceed', 'woocommerce-admin' ) }
|
||||
validate={ validate }
|
||||
{ ...props }
|
||||
|
@ -118,11 +115,11 @@ export const PaymentConnect = ( {
|
|||
<WooRemotePaymentForm.Slot
|
||||
fillProps={ {
|
||||
defaultForm: DefaultForm,
|
||||
defaultSubmit: updateSettings,
|
||||
defaultSubmit: handleSubmit,
|
||||
defaultFields: fields,
|
||||
markConfigured: () => markConfigured( key ),
|
||||
markConfigured: () => markConfigured( id ),
|
||||
} }
|
||||
id={ key }
|
||||
id={ id }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { Card, CardBody } from '@wordpress/components';
|
||||
import { enqueueScript } from '@woocommerce/wc-admin-settings';
|
||||
import {
|
||||
OPTIONS_STORE_NAME,
|
||||
PAYMENT_GATEWAYS_STORE_NAME,
|
||||
PLUGINS_STORE_NAME,
|
||||
pluginNames,
|
||||
} from '@woocommerce/data';
|
||||
|
@ -30,10 +30,7 @@ export const PaymentMethod = ( {
|
|||
const { key, plugins, title } = method;
|
||||
const slot = useSlot( `woocommerce_remote_payment_${ key }` );
|
||||
const hasFills = Boolean( slot?.fills?.length );
|
||||
const [ isFetchingPaymentGateway, setIsFetchingPaymentGateway ] = useState(
|
||||
false
|
||||
);
|
||||
const [ paymentGateway, setPaymentGateway ] = useState( null );
|
||||
const [ isPluginLoaded, setIsPluginLoaded ] = useState( false );
|
||||
|
||||
useEffect( () => {
|
||||
recordEvent( 'payments_task_stepper_view', {
|
||||
|
@ -41,52 +38,51 @@ export const PaymentMethod = ( {
|
|||
} );
|
||||
}, [] );
|
||||
|
||||
const { activePlugins } = useSelect( ( select ) => {
|
||||
const { getActivePlugins } = select( PLUGINS_STORE_NAME );
|
||||
|
||||
return {
|
||||
activePlugins: getActivePlugins(),
|
||||
};
|
||||
} );
|
||||
|
||||
const isOptionsRequesting = useSelect( ( select ) => {
|
||||
const {
|
||||
isOptionUpdating,
|
||||
isPaymentGatewayResolving,
|
||||
needsPluginInstall,
|
||||
paymentGateway,
|
||||
} = useSelect( ( select ) => {
|
||||
const { isOptionsUpdating } = select( OPTIONS_STORE_NAME );
|
||||
|
||||
return isOptionsUpdating();
|
||||
} );
|
||||
|
||||
const { getPaymentGateway, isResolving } = select(
|
||||
PAYMENT_GATEWAYS_STORE_NAME
|
||||
);
|
||||
const activePlugins = select( PLUGINS_STORE_NAME ).getActivePlugins();
|
||||
const pluginsToInstall = plugins.filter(
|
||||
( m ) => ! activePlugins.includes( m )
|
||||
);
|
||||
|
||||
return {
|
||||
isOptionUpdating: isOptionsUpdating(),
|
||||
isPaymentGatewayResolving: isResolving( 'getPaymentGateway', [
|
||||
key,
|
||||
] ),
|
||||
paymentGateway: ! pluginsToInstall.length
|
||||
? getPaymentGateway( key )
|
||||
: null,
|
||||
needsPluginInstall: !! pluginsToInstall.length,
|
||||
};
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
pluginsToInstall.length ||
|
||||
paymentGateway ||
|
||||
isFetchingPaymentGateway
|
||||
) {
|
||||
if ( ! paymentGateway ) {
|
||||
return;
|
||||
}
|
||||
fetchGateway();
|
||||
}, [ pluginsToInstall ] );
|
||||
|
||||
// @todo This should updated to use the data store in https://github.com/woocommerce/woocommerce-admin/pull/6918
|
||||
const fetchGateway = () => {
|
||||
setIsFetchingPaymentGateway( true );
|
||||
apiFetch( {
|
||||
path: 'wc/v3/payment_gateways/' + key,
|
||||
} ).then( async ( results ) => {
|
||||
const { post_install_scripts: postInstallScripts } = results;
|
||||
if ( postInstallScripts ) {
|
||||
const { post_install_scripts: postInstallScripts } = paymentGateway;
|
||||
if ( postInstallScripts && postInstallScripts.length ) {
|
||||
const scriptPromises = postInstallScripts.map( ( script ) =>
|
||||
enqueueScript( script )
|
||||
);
|
||||
await Promise.all( scriptPromises );
|
||||
}
|
||||
setPaymentGateway( results );
|
||||
setIsFetchingPaymentGateway( false );
|
||||
Promise.all( scriptPromises ).then( () => {
|
||||
setIsPluginLoaded( true );
|
||||
} );
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
setIsPluginLoaded( true );
|
||||
}, [ paymentGateway ] );
|
||||
|
||||
const pluginNamesString = plugins
|
||||
.map( ( pluginSlug ) => pluginNames[ pluginSlug ] )
|
||||
|
@ -119,10 +115,10 @@ export const PaymentMethod = ( {
|
|||
pluginSlugs={ plugins }
|
||||
/>
|
||||
),
|
||||
isComplete: ! pluginsToInstall.length,
|
||||
isComplete: ! needsPluginInstall,
|
||||
}
|
||||
: null;
|
||||
}, [ pluginsToInstall.length ] );
|
||||
}, [ needsPluginInstall ] );
|
||||
|
||||
const connectStep = {
|
||||
key: 'connect',
|
||||
|
@ -143,8 +139,9 @@ export const PaymentMethod = ( {
|
|||
|
||||
const stepperPending =
|
||||
! installStep.isComplete ||
|
||||
isOptionsRequesting ||
|
||||
isFetchingPaymentGateway;
|
||||
isOptionUpdating ||
|
||||
isPaymentGatewayResolving ||
|
||||
! isPluginLoaded;
|
||||
|
||||
const DefaultStepper = useCallback(
|
||||
( props ) => (
|
||||
|
|
|
@ -65,8 +65,8 @@ export const DynamicForm: React.FC< DynamicFormProps > = ( {
|
|||
return (
|
||||
<Form
|
||||
initialValues={ getInitialConfigValues() }
|
||||
onChangeCallback={ onChange }
|
||||
onSubmitCallback={ onSubmit }
|
||||
onChange={ onChange }
|
||||
onSubmit={ onSubmit }
|
||||
validate={ validate }
|
||||
>
|
||||
{ ( {
|
||||
|
|
|
@ -9,7 +9,7 @@ A form component to handle form state and provide input helper props.
|
|||
const initialValues = { firstName: '' };
|
||||
|
||||
<Form
|
||||
onSubmitCallback={ ( values ) => {} }
|
||||
onSubmit={ ( values ) => {} }
|
||||
initialValues={ initialValues }
|
||||
>
|
||||
{ ( {
|
||||
|
@ -42,7 +42,7 @@ Name | Type | Default | Description
|
|||
`children` | * | `null` | A renderable component in which to pass this component's state and helpers. Generally a number of input or other form elements
|
||||
`errors` | Object | `{}` | Object of all initial errors to store in state
|
||||
`initialValues` | Object | `{}` | Object key:value pair list of all initial field values
|
||||
`onSubmitCallback` | Function | `noop` | Function to call when a form is submitted with valid fields
|
||||
`onSubmit` | Function | `noop` | Function to call when a form is submitted with valid fields
|
||||
`validate` | Function | `noop` | A function that is passed a list of all values and should return an `errors` object with error response
|
||||
`touched` | Object | `{}` | This prop helps determine whether or not a field has received focus
|
||||
`onChange` | Function | `null` | A function that receives the value of the input; called when selected items change, whether added, edited, or removed
|
|
@ -124,6 +124,7 @@ export function* updatePaymentGateway(
|
|||
}
|
||||
} catch ( e ) {
|
||||
yield updatePaymentGatewayError( e );
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES } from './action-types';
|
||||
import { PluginsState, SelectorKeysWithActions, PaymentGateway } from './types';
|
||||
import { PluginsState, PaymentGateway } from './types';
|
||||
import { Actions } from './actions';
|
||||
|
||||
function updatePaymentGatewayList(
|
||||
state: PluginsState,
|
||||
paymentGateway: PaymentGateway,
|
||||
selector: SelectorKeysWithActions
|
||||
paymentGateway: PaymentGateway
|
||||
): PluginsState {
|
||||
const targetIndex = state.paymentGateways.findIndex(
|
||||
( gateway ) => gateway.id === paymentGateway.id
|
||||
|
@ -18,10 +17,7 @@ function updatePaymentGatewayList(
|
|||
return {
|
||||
...state,
|
||||
paymentGateways: [ ...state.paymentGateways, paymentGateway ],
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
[ selector ]: false,
|
||||
},
|
||||
isUpdating: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,17 +28,14 @@ function updatePaymentGatewayList(
|
|||
paymentGateway,
|
||||
...state.paymentGateways.slice( targetIndex + 1 ),
|
||||
],
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
[ selector ]: false,
|
||||
},
|
||||
isUpdating: false,
|
||||
};
|
||||
}
|
||||
|
||||
const reducer = (
|
||||
state: PluginsState = {
|
||||
paymentGateways: [],
|
||||
requesting: {},
|
||||
isUpdating: false,
|
||||
errors: {},
|
||||
},
|
||||
payload?: Actions
|
||||
|
@ -50,29 +43,12 @@ const reducer = (
|
|||
if ( payload && 'type' in payload ) {
|
||||
switch ( payload.type ) {
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAYS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
getPaymentGateways: true,
|
||||
},
|
||||
};
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
getPaymentGateway: true,
|
||||
},
|
||||
};
|
||||
return state;
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAYS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
paymentGateways: payload.paymentGateways,
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
getPaymentGateways: false,
|
||||
},
|
||||
};
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAYS_ERROR:
|
||||
return {
|
||||
|
@ -81,10 +57,6 @@ const reducer = (
|
|||
...state.errors,
|
||||
getPaymentGateways: payload.error,
|
||||
},
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
getPaymentGateways: false,
|
||||
},
|
||||
};
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAY_ERROR:
|
||||
return {
|
||||
|
@ -93,30 +65,21 @@ const reducer = (
|
|||
...state.errors,
|
||||
getPaymentGateway: payload.error,
|
||||
},
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
getPaymentGateway: false,
|
||||
},
|
||||
};
|
||||
case ACTION_TYPES.UPDATE_PAYMENT_GATEWAY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
updatePaymentGateway: true,
|
||||
},
|
||||
isUpdating: true,
|
||||
};
|
||||
case ACTION_TYPES.UPDATE_PAYMENT_GATEWAY_SUCCESS:
|
||||
return updatePaymentGatewayList(
|
||||
state,
|
||||
payload.paymentGateway,
|
||||
'updatePaymentGateway'
|
||||
payload.paymentGateway
|
||||
);
|
||||
case ACTION_TYPES.GET_PAYMENT_GATEWAY_SUCCESS:
|
||||
return updatePaymentGatewayList(
|
||||
state,
|
||||
payload.paymentGateway,
|
||||
'getPaymentGateway'
|
||||
payload.paymentGateway
|
||||
);
|
||||
|
||||
case ACTION_TYPES.UPDATE_PAYMENT_GATEWAY_ERROR:
|
||||
|
@ -126,10 +89,7 @@ const reducer = (
|
|||
...state.errors,
|
||||
updatePaymentGateway: payload.error,
|
||||
},
|
||||
requesting: {
|
||||
...state.requesting,
|
||||
updatePaymentGateway: false,
|
||||
},
|
||||
isUpdating: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import {
|
||||
PaymentGateway,
|
||||
PluginsState,
|
||||
RestApiError,
|
||||
WPDataSelector,
|
||||
WPDataSelectors,
|
||||
} from './types';
|
||||
|
@ -23,17 +24,19 @@ export function getPaymentGateways(
|
|||
return state.paymentGateways;
|
||||
}
|
||||
|
||||
export function isPaymentGatewayRequesting(
|
||||
export function getPaymentGatewayError(
|
||||
state: PluginsState,
|
||||
selector: string
|
||||
): boolean {
|
||||
return state.requesting[ selector ] || false;
|
||||
): RestApiError | null {
|
||||
return state.errors[ selector ] || null;
|
||||
}
|
||||
|
||||
export function isPaymentGatewayUpdating( state: PluginsState ): boolean {
|
||||
return state.isUpdating || false;
|
||||
}
|
||||
|
||||
export type PaymentSelectors = {
|
||||
getPaymentGateway: WPDataSelector< typeof getPaymentGateway >;
|
||||
getPaymentGateways: WPDataSelector< typeof getPaymentGateways >;
|
||||
isPaymentGatewayRequesting: WPDataSelector<
|
||||
typeof isPaymentGatewayRequesting
|
||||
>;
|
||||
isPaymentGatewayUpdating: WPDataSelector< typeof isPaymentGatewayUpdating >;
|
||||
} & WPDataSelectors;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { paymentGatewaysStub } from '../test-helpers/stub';
|
|||
|
||||
const defaultState: PluginsState = {
|
||||
paymentGateways: [],
|
||||
requesting: {},
|
||||
isUpdating: false,
|
||||
errors: {},
|
||||
};
|
||||
|
||||
|
@ -31,28 +31,12 @@ describe( 'plugins reducer', () => {
|
|||
expect( state ).not.toBe( defaultState );
|
||||
} );
|
||||
|
||||
it( 'should handle GET_PAYMENT_GATEWAY_REQUEST', () => {
|
||||
const state = reducer( defaultState, {
|
||||
type: ACTION_TYPES.GET_PAYMENT_GATEWAY_REQUEST,
|
||||
} );
|
||||
|
||||
expect( state.requesting.getPaymentGateway ).toBe( true );
|
||||
} );
|
||||
|
||||
it( 'should handle GET_PAYMENT_GATEWAYS_REQUEST', () => {
|
||||
const state = reducer( defaultState, {
|
||||
type: ACTION_TYPES.GET_PAYMENT_GATEWAYS_REQUEST,
|
||||
} );
|
||||
|
||||
expect( state.requesting.getPaymentGateways ).toBe( true );
|
||||
} );
|
||||
|
||||
it( 'should handle UPDATE_PAYMENT_GATEWAY_REQUEST', () => {
|
||||
const state = reducer( defaultState, {
|
||||
type: ACTION_TYPES.UPDATE_PAYMENT_GATEWAY_REQUEST,
|
||||
} );
|
||||
|
||||
expect( state.requesting.updatePaymentGateway ).toBe( true );
|
||||
expect( state.isUpdating ).toBe( true );
|
||||
} );
|
||||
|
||||
it( 'should handle GET_PAYMENT_GATEWAYS_ERROR', () => {
|
||||
|
@ -62,7 +46,6 @@ describe( 'plugins reducer', () => {
|
|||
} );
|
||||
|
||||
expect( state.errors.getPaymentGateways ).toBe( restApiError );
|
||||
expect( state.requesting.getPaymentGateways ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'should handle GET_PAYMENT_GATEWAY_ERROR', () => {
|
||||
|
@ -72,7 +55,6 @@ describe( 'plugins reducer', () => {
|
|||
} );
|
||||
|
||||
expect( state.errors.getPaymentGateway ).toBe( restApiError );
|
||||
expect( state.requesting.getPaymentGateway ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'should handle UPDATE_PAYMENT_GATEWAY_ERROR', () => {
|
||||
|
@ -82,7 +64,7 @@ describe( 'plugins reducer', () => {
|
|||
} );
|
||||
|
||||
expect( state.errors.updatePaymentGateway ).toBe( restApiError );
|
||||
expect( state.requesting.updatePaymentGateway ).toBe( false );
|
||||
expect( state.isUpdating ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'should handle GET_PAYMENT_GATEWAYS_SUCCESS', () => {
|
||||
|
|
|
@ -23,7 +23,7 @@ export type PaymentGateway = {
|
|||
|
||||
export type PluginsState = {
|
||||
paymentGateways: PaymentGateway[];
|
||||
requesting: Record< string, boolean >;
|
||||
isUpdating: boolean;
|
||||
errors: Record< string, RestApiError >;
|
||||
};
|
||||
|
||||
|
@ -37,11 +37,6 @@ export type RestApiError = {
|
|||
message: string;
|
||||
};
|
||||
|
||||
export type SelectorKeysWithActions =
|
||||
| 'getPaymentGateways'
|
||||
| 'getPaymentGateway'
|
||||
| 'updatePaymentGateway';
|
||||
|
||||
// Type for the basic selectors built into @wordpress/data, note these
|
||||
// types define the interface for the public selectors, so state is not an
|
||||
// argument.
|
||||
|
|
|
@ -125,6 +125,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
|
|||
- Update: Task list component with new Experimental Task list. #6849
|
||||
- Update: Experimental task list import to the experimental package. #6950
|
||||
- Update: Redirect to WC Home after setting up a payment method #6891
|
||||
- Update: Hook up payments gateway data store #7038
|
||||
|
||||
== 2.3.1 5/24/2021 ==
|
||||
- Tweak: Store profiler - Changed MailPoet's title and description #6990
|
||||
|
|
Loading…
Reference in New Issue