rebase from live enviornment
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
export enum ACTION_TYPES {
|
||||
SET_PAYMENT_IDLE = 'SET_PAYMENT_IDLE',
|
||||
SET_EXPRESS_PAYMENT_STARTED = 'SET_EXPRESS_PAYMENT_STARTED',
|
||||
SET_PAYMENT_READY = 'SET_PAYMENT_READY',
|
||||
SET_PAYMENT_PROCESSING = 'SET_PAYMENT_PROCESSING',
|
||||
SET_PAYMENT_ERROR = 'SET_PAYMENT_ERROR',
|
||||
SET_PAYMENT_METHODS_INITIALIZED = 'SET_PAYMENT_METHODS_INITIALIZED',
|
||||
SET_EXPRESS_PAYMENT_METHODS_INITIALIZED = 'SET_EXPRESS_PAYMENT_METHODS_INITIALIZED',
|
||||
SET_ACTIVE_PAYMENT_METHOD = 'SET_ACTIVE_PAYMENT_METHOD',
|
||||
SET_SHOULD_SAVE_PAYMENT_METHOD = 'SET_SHOULD_SAVE_PAYMENT_METHOD',
|
||||
SET_AVAILABLE_PAYMENT_METHODS = 'SET_AVAILABLE_PAYMENT_METHODS',
|
||||
SET_AVAILABLE_EXPRESS_PAYMENT_METHODS = 'SET_AVAILABLE_EXPRESS_PAYMENT_METHODS',
|
||||
REMOVE_AVAILABLE_PAYMENT_METHOD = 'REMOVE_AVAILABLE_PAYMENT_METHOD',
|
||||
REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD = 'REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD',
|
||||
INITIALIZE_PAYMENT_METHODS = 'INITIALIZE_PAYMENT_METHODS',
|
||||
SET_PAYMENT_METHOD_DATA = 'SET_PAYMENT_METHOD_DATA',
|
||||
SET_PAYMENT_RESULT = 'SET_PAYMENT_RESULT',
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
PlainPaymentMethods,
|
||||
PlainExpressPaymentMethods,
|
||||
} from '@woocommerce/types';
|
||||
import type { PaymentResult } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES } from './action-types';
|
||||
import { checkPaymentMethodsCanPay } from './utils/check-payment-methods';
|
||||
import { setDefaultPaymentMethod } from './utils/set-default-payment-method';
|
||||
|
||||
// `Thunks are functions that can be dispatched, similar to actions creators
|
||||
export * from './thunks';
|
||||
|
||||
export const __internalSetPaymentIdle = () => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_IDLE,
|
||||
} );
|
||||
|
||||
export const __internalSetExpressPaymentStarted = () => ( {
|
||||
type: ACTION_TYPES.SET_EXPRESS_PAYMENT_STARTED,
|
||||
} );
|
||||
|
||||
export const __internalSetPaymentProcessing = () => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_PROCESSING,
|
||||
} );
|
||||
|
||||
export const __internalSetPaymentError = () => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_ERROR,
|
||||
} );
|
||||
|
||||
export const __internalSetPaymentReady = () => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_READY,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Set whether the payment methods have been initialised or not
|
||||
*
|
||||
* @param initialized True if the `checkCanPay` methods have been run on all available payment methods
|
||||
*/
|
||||
export const __internalSetPaymentMethodsInitialized = (
|
||||
initialized: boolean
|
||||
) => {
|
||||
return async ( { select, dispatch } ) => {
|
||||
// If the currently selected method is not in this new list, then we need to select a new one, or select a default.
|
||||
const methods = select.getAvailablePaymentMethods();
|
||||
if ( initialized ) {
|
||||
await setDefaultPaymentMethod( methods );
|
||||
}
|
||||
dispatch( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_METHODS_INITIALIZED,
|
||||
initialized,
|
||||
} );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the express payment methods have been initialised or not
|
||||
*
|
||||
* @param initialized True if the `checkCanPay` methods have been run on all express available payment methods
|
||||
*/
|
||||
export const __internalSetExpressPaymentMethodsInitialized = (
|
||||
initialized: boolean
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_EXPRESS_PAYMENT_METHODS_INITIALIZED,
|
||||
initialized,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Set a flag for whether to save the current payment method for next time
|
||||
*
|
||||
* @param shouldSavePaymentMethod Whether to save the current payment method for next time
|
||||
*/
|
||||
export const __internalSetShouldSavePaymentMethod = (
|
||||
shouldSavePaymentMethod: boolean
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_SHOULD_SAVE_PAYMENT_METHOD,
|
||||
shouldSavePaymentMethod,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Set the payment method the user has chosen. This should change every time the user selects a new payment method
|
||||
*
|
||||
* @param activePaymentMethod The name of the payment method selected by the user
|
||||
* @param paymentMethodData The extra data associated with a payment
|
||||
*/
|
||||
export const __internalSetActivePaymentMethod = (
|
||||
activePaymentMethod: string,
|
||||
paymentMethodData: Record< string, unknown > = {}
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_ACTIVE_PAYMENT_METHOD,
|
||||
activePaymentMethod,
|
||||
paymentMethodData,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Set the extra data for the chosen payment method
|
||||
*
|
||||
* @param paymentMethodData The extra data associated with a payment
|
||||
*/
|
||||
export const __internalSetPaymentMethodData = (
|
||||
paymentMethodData: Record< string, unknown > = {}
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_METHOD_DATA,
|
||||
paymentMethodData,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Store the result of the payment attempt from the /checkout StoreApi call
|
||||
*
|
||||
* @param data The result of the payment attempt through the StoreApi /checkout endpoints
|
||||
*/
|
||||
export const __internalSetPaymentResult = ( data: PaymentResult ) => ( {
|
||||
type: ACTION_TYPES.SET_PAYMENT_RESULT,
|
||||
data,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Set the available payment methods.
|
||||
* An available payment method is one that has been validated and can make a payment.
|
||||
*/
|
||||
export const __internalSetAvailablePaymentMethods = (
|
||||
paymentMethods: PlainPaymentMethods
|
||||
) => {
|
||||
return async ( { dispatch, select } ) => {
|
||||
// If the currently selected method is not in this new list, then we need to select a new one, or select a default.
|
||||
const activePaymentMethod = select.getActivePaymentMethod();
|
||||
if ( ! ( activePaymentMethod in paymentMethods ) ) {
|
||||
await setDefaultPaymentMethod( paymentMethods );
|
||||
}
|
||||
dispatch( {
|
||||
type: ACTION_TYPES.SET_AVAILABLE_PAYMENT_METHODS,
|
||||
paymentMethods,
|
||||
} );
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the available express payment methods.
|
||||
* An available payment method is one that has been validated and can make a payment.
|
||||
*/
|
||||
export const __internalSetAvailableExpressPaymentMethods = (
|
||||
paymentMethods: PlainExpressPaymentMethods
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_AVAILABLE_EXPRESS_PAYMENT_METHODS,
|
||||
paymentMethods,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Remove a payment method name from the available payment methods.
|
||||
* This is called when a payment method is removed from the registry.
|
||||
*/
|
||||
export const __internalRemoveAvailablePaymentMethod = ( name: string ) => ( {
|
||||
type: ACTION_TYPES.REMOVE_AVAILABLE_PAYMENT_METHOD,
|
||||
name,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Remove an express payment method name from the available payment methods.
|
||||
* This is called when an express payment method is removed from the registry.
|
||||
*/
|
||||
export const __internalRemoveAvailableExpressPaymentMethod = (
|
||||
name: string
|
||||
) => ( {
|
||||
type: ACTION_TYPES.REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD,
|
||||
name,
|
||||
} );
|
||||
|
||||
/**
|
||||
* The store is initialised once we have checked whether the payment methods registered can pay or not
|
||||
*/
|
||||
export function __internalUpdateAvailablePaymentMethods() {
|
||||
return async ( { select, dispatch } ) => {
|
||||
const expressRegistered = await checkPaymentMethodsCanPay( true );
|
||||
const registered = await checkPaymentMethodsCanPay( false );
|
||||
const { paymentMethodsInitialized, expressPaymentMethodsInitialized } =
|
||||
select;
|
||||
if ( registered && ! paymentMethodsInitialized() ) {
|
||||
dispatch( __internalSetPaymentMethodsInitialized( true ) );
|
||||
}
|
||||
if ( expressRegistered && ! expressPaymentMethodsInitialized() ) {
|
||||
dispatch( __internalSetExpressPaymentMethodsInitialized( true ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const STORE_KEY = 'wc/store/payment';
|
||||
|
||||
export enum STATUS {
|
||||
IDLE = 'idle',
|
||||
EXPRESS_STARTED = 'express_started',
|
||||
PROCESSING = 'processing',
|
||||
READY = 'ready',
|
||||
ERROR = 'has_error',
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { EmptyObjectType, PaymentResult } from '@woocommerce/types';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
PlainPaymentMethods,
|
||||
PlainExpressPaymentMethods,
|
||||
} from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SavedPaymentMethod } from './types';
|
||||
import { STATUS as PAYMENT_STATUS } from './constants';
|
||||
|
||||
export interface PaymentState {
|
||||
status: string;
|
||||
activePaymentMethod: string;
|
||||
activeSavedToken: string;
|
||||
// Available payment methods are payment methods which have been validated and can make payment.
|
||||
availablePaymentMethods: PlainPaymentMethods;
|
||||
availableExpressPaymentMethods: PlainExpressPaymentMethods;
|
||||
savedPaymentMethods:
|
||||
| Record< string, SavedPaymentMethod[] >
|
||||
| EmptyObjectType;
|
||||
paymentMethodData: Record< string, unknown >;
|
||||
paymentResult: PaymentResult | null;
|
||||
paymentMethodsInitialized: boolean;
|
||||
expressPaymentMethodsInitialized: boolean;
|
||||
shouldSavePaymentMethod: boolean;
|
||||
}
|
||||
|
||||
export const defaultPaymentState: PaymentState = {
|
||||
status: PAYMENT_STATUS.IDLE,
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: {},
|
||||
savedPaymentMethods: getSetting<
|
||||
Record< string, SavedPaymentMethod[] > | EmptyObjectType
|
||||
>( 'customerPaymentMethods', {} ),
|
||||
paymentMethodData: {},
|
||||
paymentResult: null,
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createReduxStore, register } from '@wordpress/data';
|
||||
import { controls as dataControls } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from './reducers';
|
||||
import { STORE_KEY } from './constants';
|
||||
import * as actions from './actions';
|
||||
import { controls as sharedControls } from '../shared-controls';
|
||||
import * as selectors from './selectors';
|
||||
import { DispatchFromMap, SelectFromMap } from '../mapped-types';
|
||||
|
||||
export const config = {
|
||||
reducer,
|
||||
selectors,
|
||||
actions,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
controls: { ...dataControls, ...sharedControls } as any,
|
||||
__experimentalUseThunks: true,
|
||||
};
|
||||
|
||||
const store = createReduxStore( STORE_KEY, config );
|
||||
register( store );
|
||||
|
||||
declare module '@wordpress/data' {
|
||||
function dispatch(
|
||||
key: typeof STORE_KEY
|
||||
): DispatchFromMap< typeof actions >;
|
||||
function select( key: typeof STORE_KEY ): SelectFromMap<
|
||||
typeof selectors
|
||||
> & {
|
||||
hasFinishedResolution: ( selector: string ) => boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const PAYMENT_STORE_KEY = STORE_KEY;
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { Reducer } from 'redux';
|
||||
import { objectHasProp, PaymentResult } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { defaultPaymentState, PaymentState } from './default-state';
|
||||
import { ACTION_TYPES } from './action-types';
|
||||
import { STATUS } from './constants';
|
||||
|
||||
const reducer: Reducer< PaymentState > = (
|
||||
state = defaultPaymentState,
|
||||
action
|
||||
) => {
|
||||
let newState = state;
|
||||
switch ( action.type ) {
|
||||
case ACTION_TYPES.SET_PAYMENT_IDLE:
|
||||
newState = {
|
||||
...state,
|
||||
status: STATUS.IDLE,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_EXPRESS_PAYMENT_STARTED:
|
||||
newState = {
|
||||
...state,
|
||||
status: STATUS.EXPRESS_STARTED,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_PROCESSING:
|
||||
newState = {
|
||||
...state,
|
||||
status: STATUS.PROCESSING,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_READY:
|
||||
newState = {
|
||||
...state,
|
||||
status: STATUS.READY,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_ERROR:
|
||||
newState = {
|
||||
...state,
|
||||
status: STATUS.ERROR,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_SHOULD_SAVE_PAYMENT_METHOD:
|
||||
newState = {
|
||||
...state,
|
||||
shouldSavePaymentMethod: action.shouldSavePaymentMethod,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_METHOD_DATA:
|
||||
newState = {
|
||||
...state,
|
||||
paymentMethodData: action.paymentMethodData,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_RESULT:
|
||||
newState = {
|
||||
...state,
|
||||
paymentResult: action.data as PaymentResult,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.REMOVE_AVAILABLE_PAYMENT_METHOD:
|
||||
const previousAvailablePaymentMethods = {
|
||||
...state.availablePaymentMethods,
|
||||
};
|
||||
delete previousAvailablePaymentMethods[ action.name ];
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
availablePaymentMethods: {
|
||||
...previousAvailablePaymentMethods,
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD:
|
||||
const previousAvailableExpressPaymentMethods = {
|
||||
...state.availablePaymentMethods,
|
||||
};
|
||||
delete previousAvailableExpressPaymentMethods[ action.name ];
|
||||
newState = {
|
||||
...state,
|
||||
availableExpressPaymentMethods: {
|
||||
...previousAvailableExpressPaymentMethods,
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_PAYMENT_METHODS_INITIALIZED:
|
||||
newState = {
|
||||
...state,
|
||||
paymentMethodsInitialized: action.initialized,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_EXPRESS_PAYMENT_METHODS_INITIALIZED:
|
||||
newState = {
|
||||
...state,
|
||||
expressPaymentMethodsInitialized: action.initialized,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_AVAILABLE_PAYMENT_METHODS:
|
||||
newState = {
|
||||
...state,
|
||||
availablePaymentMethods: action.paymentMethods,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_AVAILABLE_EXPRESS_PAYMENT_METHODS:
|
||||
newState = {
|
||||
...state,
|
||||
availableExpressPaymentMethods: action.paymentMethods,
|
||||
};
|
||||
break;
|
||||
|
||||
case ACTION_TYPES.SET_ACTIVE_PAYMENT_METHOD:
|
||||
const activeSavedToken =
|
||||
typeof state.paymentMethodData === 'object' &&
|
||||
objectHasProp( action.paymentMethodData, 'token' )
|
||||
? action.paymentMethodData.token + ''
|
||||
: '';
|
||||
newState = {
|
||||
...state,
|
||||
activeSavedToken,
|
||||
activePaymentMethod: action.activePaymentMethod,
|
||||
paymentMethodData:
|
||||
action.paymentMethodData || state.paymentMethodData,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
return newState;
|
||||
}
|
||||
return newState;
|
||||
};
|
||||
export type State = ReturnType< typeof reducer >;
|
||||
|
||||
export default reducer;
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { objectHasProp } from '@woocommerce/types';
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import type { GlobalPaymentMethod } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PaymentState } from './default-state';
|
||||
import { filterActiveSavedPaymentMethods } from './utils/filter-active-saved-payment-methods';
|
||||
import { STATUS as PAYMENT_STATUS } from './constants';
|
||||
|
||||
const globalPaymentMethods: Record< string, string > = {};
|
||||
|
||||
if ( getSetting( 'globalPaymentMethods' ) ) {
|
||||
getSetting< GlobalPaymentMethod[] >( 'globalPaymentMethods' ).forEach(
|
||||
( method ) => {
|
||||
globalPaymentMethods[ method.id ] = method.title;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const isPaymentPristine = ( state: PaymentState ) => {
|
||||
deprecated( 'isPaymentPristine', {
|
||||
since: '9.6.0',
|
||||
alternative: 'isPaymentIdle',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
|
||||
return state.status === PAYMENT_STATUS.IDLE;
|
||||
};
|
||||
|
||||
export const isPaymentIdle = ( state: PaymentState ) =>
|
||||
state.status === PAYMENT_STATUS.IDLE;
|
||||
|
||||
export const isPaymentStarted = ( state: PaymentState ) => {
|
||||
deprecated( 'isPaymentStarted', {
|
||||
since: '9.6.0',
|
||||
alternative: 'isExpressPaymentStarted',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
return state.status === PAYMENT_STATUS.EXPRESS_STARTED;
|
||||
};
|
||||
|
||||
export const isExpressPaymentStarted = ( state: PaymentState ) => {
|
||||
return state.status === PAYMENT_STATUS.EXPRESS_STARTED;
|
||||
};
|
||||
|
||||
export const isPaymentProcessing = ( state: PaymentState ) =>
|
||||
state.status === PAYMENT_STATUS.PROCESSING;
|
||||
|
||||
export const isPaymentReady = ( state: PaymentState ) =>
|
||||
state.status === PAYMENT_STATUS.READY;
|
||||
|
||||
export const isPaymentSuccess = ( state: PaymentState ) => {
|
||||
deprecated( 'isPaymentSuccess', {
|
||||
since: '9.6.0',
|
||||
alternative: 'isPaymentReady',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
|
||||
return state.status === PAYMENT_STATUS.READY;
|
||||
};
|
||||
|
||||
export const hasPaymentError = ( state: PaymentState ) =>
|
||||
state.status === PAYMENT_STATUS.ERROR;
|
||||
|
||||
export const isPaymentFailed = ( state: PaymentState ) => {
|
||||
deprecated( 'isPaymentFailed', {
|
||||
since: '9.6.0',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
|
||||
return state.status === PAYMENT_STATUS.ERROR;
|
||||
};
|
||||
|
||||
export const isExpressPaymentMethodActive = ( state: PaymentState ) => {
|
||||
return Object.keys( state.availableExpressPaymentMethods ).includes(
|
||||
state.activePaymentMethod
|
||||
);
|
||||
};
|
||||
|
||||
export const getActiveSavedToken = ( state: PaymentState ) => {
|
||||
return typeof state.paymentMethodData === 'object' &&
|
||||
objectHasProp( state.paymentMethodData, 'token' )
|
||||
? state.paymentMethodData.token + ''
|
||||
: '';
|
||||
};
|
||||
|
||||
export const getActivePaymentMethod = ( state: PaymentState ) => {
|
||||
return state.activePaymentMethod;
|
||||
};
|
||||
|
||||
export const getAvailablePaymentMethods = ( state: PaymentState ) => {
|
||||
return state.availablePaymentMethods;
|
||||
};
|
||||
|
||||
export const getAvailableExpressPaymentMethods = ( state: PaymentState ) => {
|
||||
return state.availableExpressPaymentMethods;
|
||||
};
|
||||
|
||||
export const getPaymentMethodData = ( state: PaymentState ) => {
|
||||
return state.paymentMethodData;
|
||||
};
|
||||
|
||||
export const getIncompatiblePaymentMethods = ( state: PaymentState ) => {
|
||||
const {
|
||||
availablePaymentMethods,
|
||||
availableExpressPaymentMethods,
|
||||
paymentMethodsInitialized,
|
||||
expressPaymentMethodsInitialized,
|
||||
} = state;
|
||||
|
||||
if ( ! paymentMethodsInitialized || ! expressPaymentMethodsInitialized ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries( globalPaymentMethods ).filter( ( [ k ] ) => {
|
||||
return ! (
|
||||
k in
|
||||
{
|
||||
...availablePaymentMethods,
|
||||
...availableExpressPaymentMethods,
|
||||
}
|
||||
);
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
export const getSavedPaymentMethods = ( state: PaymentState ) => {
|
||||
return state.savedPaymentMethods;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the list of saved payment methods and returns only the ones which
|
||||
* are active and supported by the payment gateway
|
||||
*/
|
||||
export const getActiveSavedPaymentMethods = ( state: PaymentState ) => {
|
||||
const availablePaymentMethodKeys = Object.keys(
|
||||
state.availablePaymentMethods
|
||||
);
|
||||
|
||||
return filterActiveSavedPaymentMethods(
|
||||
availablePaymentMethodKeys,
|
||||
state.savedPaymentMethods
|
||||
);
|
||||
};
|
||||
|
||||
export const paymentMethodsInitialized = ( state: PaymentState ) => {
|
||||
return state.paymentMethodsInitialized;
|
||||
};
|
||||
|
||||
export const expressPaymentMethodsInitialized = ( state: PaymentState ) => {
|
||||
return state.expressPaymentMethodsInitialized;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated - Use these selectors instead: isPaymentIdle, isPaymentProcessing,
|
||||
* hasPaymentError
|
||||
*/
|
||||
export const getCurrentStatus = ( state: PaymentState ) => {
|
||||
deprecated( 'getCurrentStatus', {
|
||||
since: '8.9.0',
|
||||
alternative: 'isPaymentIdle, isPaymentProcessing, hasPaymentError',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/7666',
|
||||
} );
|
||||
|
||||
return {
|
||||
get isPristine() {
|
||||
deprecated( 'isPristine', {
|
||||
since: '9.6.0',
|
||||
alternative: 'isIdle',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
} );
|
||||
return isPaymentIdle( state );
|
||||
}, // isPristine is the same as isIdle.
|
||||
isIdle: isPaymentIdle( state ),
|
||||
isStarted: isExpressPaymentStarted( state ),
|
||||
isProcessing: isPaymentProcessing( state ),
|
||||
get isFinished() {
|
||||
deprecated( 'isFinished', {
|
||||
since: '9.6.0',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
return hasPaymentError( state ) || isPaymentReady( state );
|
||||
},
|
||||
hasError: hasPaymentError( state ),
|
||||
get hasFailed() {
|
||||
deprecated( 'hasFailed', {
|
||||
since: '9.6.0',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
return hasPaymentError( state );
|
||||
},
|
||||
get isSuccessful() {
|
||||
deprecated( 'isSuccessful', {
|
||||
since: '9.6.0',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8110',
|
||||
} );
|
||||
return isPaymentReady( state );
|
||||
},
|
||||
isDoingExpressPayment: isExpressPaymentMethodActive( state ),
|
||||
};
|
||||
};
|
||||
|
||||
export const getShouldSavePaymentMethod = ( state: PaymentState ) => {
|
||||
return state.shouldSavePaymentMethod;
|
||||
};
|
||||
|
||||
export const getPaymentResult = ( state: PaymentState ) => {
|
||||
return state.paymentResult;
|
||||
};
|
||||
|
||||
// We should avoid using this selector and instead use the focused selectors
|
||||
// We're keeping it because it's used in our unit test: assets/js/blocks/cart-checkout-shared/payment-methods/test/payment-methods.js
|
||||
// to mock the selectors.
|
||||
export const getState = ( state: PaymentState ) => {
|
||||
return state;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { setDefaultPaymentMethod as setDefaultPaymentMethodOriginal } from '../utils/set-default-payment-method';
|
||||
import { PAYMENT_STORE_KEY } from '..';
|
||||
import { PlainPaymentMethods } from '../../../types';
|
||||
|
||||
const originalDispatch = jest.requireActual( '@wordpress/data' ).dispatch;
|
||||
|
||||
jest.mock( '../utils/set-default-payment-method', () => ( {
|
||||
setDefaultPaymentMethod: jest.fn(),
|
||||
} ) );
|
||||
|
||||
describe( 'payment data store actions', () => {
|
||||
const paymentMethods: PlainPaymentMethods = {
|
||||
'wc-payment-gateway-1': {
|
||||
name: 'wc-payment-gateway-1',
|
||||
},
|
||||
'wc-payment-gateway-2': {
|
||||
name: 'wc-payment-gateway-2',
|
||||
},
|
||||
};
|
||||
|
||||
describe( 'setAvailablePaymentMethods', () => {
|
||||
it( 'Does not call setDefaultPaymentGateway if the current method is still available', () => {
|
||||
const actions = originalDispatch( PAYMENT_STORE_KEY );
|
||||
actions.__internalSetActivePaymentMethod(
|
||||
Object.keys( paymentMethods )[ 0 ]
|
||||
);
|
||||
actions.__internalSetAvailablePaymentMethods( paymentMethods );
|
||||
expect( setDefaultPaymentMethodOriginal ).not.toBeCalled();
|
||||
} );
|
||||
|
||||
it( 'Resets the default gateway if the current method is no longer available', () => {
|
||||
const actions = originalDispatch( PAYMENT_STORE_KEY );
|
||||
actions.__internalSetActivePaymentMethod(
|
||||
Object.keys( paymentMethods )[ 0 ]
|
||||
);
|
||||
actions.__internalSetAvailablePaymentMethods( [
|
||||
paymentMethods[ Object.keys( paymentMethods )[ 0 ] ],
|
||||
] );
|
||||
expect( setDefaultPaymentMethodOriginal ).toBeCalled();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import * as wpDataFunctions from '@wordpress/data';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import { PAYMENT_STORE_KEY, CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
import {
|
||||
registerPaymentMethod,
|
||||
registerExpressPaymentMethod,
|
||||
__experimentalDeRegisterPaymentMethod,
|
||||
__experimentalDeRegisterExpressPaymentMethod,
|
||||
} from '@woocommerce/blocks-registry';
|
||||
import { CanMakePaymentArgument } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { checkPaymentMethodsCanPay } from '../utils/check-payment-methods';
|
||||
|
||||
const requiredKeyCheck = ( args: CanMakePaymentArgument ) => {
|
||||
const requiredKeys = [
|
||||
'billingData',
|
||||
'billingAddress',
|
||||
'cart',
|
||||
'cartNeedsShipping',
|
||||
'cartTotals',
|
||||
'paymentMethods',
|
||||
'paymentRequirements',
|
||||
'selectedShippingMethods',
|
||||
'shippingAddress',
|
||||
];
|
||||
const argKeys = Object.keys( args );
|
||||
|
||||
const requiredCartKeys = [
|
||||
'cartCoupons',
|
||||
'cartItems',
|
||||
'crossSellsProducts',
|
||||
'cartFees',
|
||||
'cartItemsCount',
|
||||
'cartItemsWeight',
|
||||
'cartNeedsPayment',
|
||||
'cartNeedsShipping',
|
||||
'cartItemErrors',
|
||||
'cartTotals',
|
||||
'cartIsLoading',
|
||||
'cartErrors',
|
||||
'billingData',
|
||||
'billingAddress',
|
||||
'shippingAddress',
|
||||
'extensions',
|
||||
'shippingRates',
|
||||
'isLoadingRates',
|
||||
'cartHasCalculatedShipping',
|
||||
'paymentRequirements',
|
||||
'receiveCart',
|
||||
];
|
||||
const cartKeys = Object.keys( args.cart );
|
||||
const requiredTotalsKeys = [
|
||||
'total_items',
|
||||
'total_items_tax',
|
||||
'total_fees',
|
||||
'total_fees_tax',
|
||||
'total_discount',
|
||||
'total_discount_tax',
|
||||
'total_shipping',
|
||||
'total_shipping_tax',
|
||||
'total_price',
|
||||
'total_tax',
|
||||
'tax_lines',
|
||||
'currency_code',
|
||||
'currency_symbol',
|
||||
'currency_minor_unit',
|
||||
'currency_decimal_separator',
|
||||
'currency_thousand_separator',
|
||||
'currency_prefix',
|
||||
'currency_suffix',
|
||||
];
|
||||
const totalsKeys = Object.keys( args.cartTotals );
|
||||
return (
|
||||
requiredKeys.every( ( key ) => argKeys.includes( key ) ) &&
|
||||
requiredTotalsKeys.every( ( key ) => totalsKeys.includes( key ) ) &&
|
||||
requiredCartKeys.every( ( key ) => cartKeys.includes( key ) )
|
||||
);
|
||||
};
|
||||
|
||||
const mockedCanMakePayment = jest.fn().mockImplementation( requiredKeyCheck );
|
||||
const mockedExpressCanMakePayment = jest
|
||||
.fn()
|
||||
.mockImplementation( requiredKeyCheck );
|
||||
|
||||
const registerMockPaymentMethods = ( savedCards = true ) => {
|
||||
[ 'credit-card' ].forEach( ( name ) => {
|
||||
registerPaymentMethod( {
|
||||
name,
|
||||
label: name,
|
||||
content: <div>A payment method</div>,
|
||||
edit: <div>A payment method</div>,
|
||||
icons: null,
|
||||
canMakePayment: mockedCanMakePayment,
|
||||
supports: {
|
||||
showSavedCards: savedCards,
|
||||
showSaveOption: true,
|
||||
features: [ 'products' ],
|
||||
},
|
||||
ariaLabel: name,
|
||||
} );
|
||||
} );
|
||||
[ 'express-payment' ].forEach( ( name ) => {
|
||||
const Content = ( {
|
||||
onClose = () => void null,
|
||||
onClick = () => void null,
|
||||
} ) => {
|
||||
return (
|
||||
<>
|
||||
<button onClick={ onClick }>
|
||||
{ name + ' express payment method' }
|
||||
</button>
|
||||
<button onClick={ onClose }>
|
||||
{ name + ' express payment method close' }
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
registerExpressPaymentMethod( {
|
||||
name,
|
||||
content: <Content />,
|
||||
edit: <div>An express payment method</div>,
|
||||
canMakePayment: mockedExpressCanMakePayment,
|
||||
paymentMethodId: name,
|
||||
supports: {
|
||||
features: [ 'products' ],
|
||||
},
|
||||
} );
|
||||
} );
|
||||
wpDataFunctions
|
||||
.dispatch( PAYMENT_STORE_KEY )
|
||||
.__internalUpdateAvailablePaymentMethods();
|
||||
wpDataFunctions.dispatch( CART_STORE_KEY ).receiveCart( {
|
||||
...previewCart,
|
||||
payment_methods: [ 'cheque', 'bacs', 'credit-card' ],
|
||||
} );
|
||||
};
|
||||
|
||||
const resetMockPaymentMethods = () => {
|
||||
[ 'cheque', 'bacs', 'credit-card' ].forEach( ( name ) => {
|
||||
__experimentalDeRegisterPaymentMethod( name );
|
||||
} );
|
||||
[ 'express-payment' ].forEach( ( name ) => {
|
||||
__experimentalDeRegisterExpressPaymentMethod( name );
|
||||
} );
|
||||
};
|
||||
|
||||
describe( 'checkPaymentMethods', () => {
|
||||
beforeEach( registerMockPaymentMethods );
|
||||
afterEach( resetMockPaymentMethods );
|
||||
|
||||
it( `Sends correct arguments to regular payment methods' canMakePayment functions`, async () => {
|
||||
await checkPaymentMethodsCanPay();
|
||||
expect( mockedCanMakePayment ).toHaveReturnedWith( true );
|
||||
} );
|
||||
|
||||
it( `Sends correct arguments to express payment methods' canMakePayment functions`, async () => {
|
||||
await checkPaymentMethodsCanPay( true );
|
||||
expect( mockedExpressCanMakePayment ).toHaveReturnedWith( true );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from '../reducers';
|
||||
import { ACTION_TYPES } from '../action-types';
|
||||
|
||||
describe( 'paymentMethodDataReducer', () => {
|
||||
const originalState = deepFreeze( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: {},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
|
||||
it( 'sets state as expected when adding a payment method', () => {
|
||||
const nextState = reducer( originalState, {
|
||||
type: ACTION_TYPES.SET_AVAILABLE_PAYMENT_METHODS,
|
||||
paymentMethods: { 'my-new-method': { express: false } },
|
||||
} );
|
||||
expect( nextState ).toEqual( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: { 'my-new-method': { express: false } },
|
||||
availableExpressPaymentMethods: {},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'sets state as expected when removing a payment method', () => {
|
||||
const stateWithRegisteredMethod = deepFreeze( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: { 'my-new-method': { express: false } },
|
||||
availableExpressPaymentMethods: {},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
const nextState = reducer( stateWithRegisteredMethod, {
|
||||
type: ACTION_TYPES.REMOVE_AVAILABLE_PAYMENT_METHOD,
|
||||
name: 'my-new-method',
|
||||
} );
|
||||
expect( nextState ).toEqual( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: {},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'sets state as expected when adding an express payment method', () => {
|
||||
const nextState = reducer( originalState, {
|
||||
type: ACTION_TYPES.SET_AVAILABLE_EXPRESS_PAYMENT_METHODS,
|
||||
paymentMethods: { 'my-new-method': { express: true } },
|
||||
} );
|
||||
expect( nextState ).toEqual( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: {
|
||||
'my-new-method': { express: true },
|
||||
},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'sets state as expected when removing an express payment method', () => {
|
||||
const stateWithRegisteredMethod = deepFreeze( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: [ 'my-new-method' ],
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
const nextState = reducer( stateWithRegisteredMethod, {
|
||||
type: ACTION_TYPES.REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD,
|
||||
name: 'my-new-method',
|
||||
} );
|
||||
expect( nextState ).toEqual( {
|
||||
currentStatus: {
|
||||
isPristine: true,
|
||||
isStarted: false,
|
||||
isProcessing: false,
|
||||
isFinished: false,
|
||||
hasError: false,
|
||||
hasFailed: false,
|
||||
isSuccessful: false,
|
||||
},
|
||||
availablePaymentMethods: {},
|
||||
availableExpressPaymentMethods: {},
|
||||
paymentMethodData: {},
|
||||
paymentMethodsInitialized: false,
|
||||
expressPaymentMethodsInitialized: false,
|
||||
shouldSavePaymentMethod: false,
|
||||
errorMessage: '',
|
||||
activePaymentMethod: '',
|
||||
activeSavedToken: '',
|
||||
incompatiblePaymentMethods: {},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_PAYMENT_RESULT', () => {
|
||||
const mockResponse = {
|
||||
message: 'success',
|
||||
redirectUrl: 'https://example.com',
|
||||
paymentStatus: 'not set',
|
||||
paymentDetails: {},
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
...originalState,
|
||||
paymentResult: mockResponse,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer( originalState, {
|
||||
type: ACTION_TYPES.SET_PAYMENT_RESULT,
|
||||
data: mockResponse,
|
||||
} )
|
||||
).toEqual( expectedState );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import * as wpDataFunctions from '@wordpress/data';
|
||||
import {
|
||||
CART_STORE_KEY as storeKey,
|
||||
PAYMENT_STORE_KEY,
|
||||
} from '@woocommerce/block-data';
|
||||
import {
|
||||
registerPaymentMethod,
|
||||
registerExpressPaymentMethod,
|
||||
__experimentalDeRegisterPaymentMethod,
|
||||
__experimentalDeRegisterExpressPaymentMethod,
|
||||
} from '@woocommerce/blocks-registry';
|
||||
import { default as fetchMock } from 'jest-fetch-mock';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
CheckoutExpressPayment,
|
||||
SavedPaymentMethodOptions,
|
||||
} from '../../../blocks/cart-checkout-shared/payment-methods';
|
||||
import { defaultCartState } from '../../cart/default-state';
|
||||
|
||||
const originalSelect = jest.requireActual( '@wordpress/data' ).select;
|
||||
jest.spyOn( wpDataFunctions, 'select' ).mockImplementation( ( storeName ) => {
|
||||
const originalStore = originalSelect( storeName );
|
||||
if ( storeName === storeKey ) {
|
||||
return {
|
||||
...originalStore,
|
||||
hasFinishedResolution: jest
|
||||
.fn()
|
||||
.mockImplementation( ( selectorName ) => {
|
||||
if ( selectorName === 'getCartTotals' ) {
|
||||
return true;
|
||||
}
|
||||
return originalStore.hasFinishedResolution( selectorName );
|
||||
} ),
|
||||
};
|
||||
}
|
||||
return originalStore;
|
||||
} );
|
||||
|
||||
jest.mock( '@woocommerce/settings', () => {
|
||||
const originalModule = jest.requireActual( '@woocommerce/settings' );
|
||||
|
||||
return {
|
||||
// @ts-ignore We know @woocommerce/settings is an object.
|
||||
...originalModule,
|
||||
getSetting: ( setting, ...rest ) => {
|
||||
if ( setting === 'customerPaymentMethods' ) {
|
||||
return {
|
||||
cc: [
|
||||
{
|
||||
method: {
|
||||
gateway: 'credit-card',
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
},
|
||||
expires: '12/22',
|
||||
is_default: true,
|
||||
tokenId: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return originalModule.getSetting( setting, ...rest );
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
||||
const registerMockPaymentMethods = ( savedCards = true ) => {
|
||||
[ 'cheque', 'bacs' ].forEach( ( name ) => {
|
||||
registerPaymentMethod( {
|
||||
name,
|
||||
label: name,
|
||||
content: <div>A payment method</div>,
|
||||
edit: <div>A payment method</div>,
|
||||
icons: null,
|
||||
canMakePayment: () => true,
|
||||
supports: {
|
||||
features: [ 'products' ],
|
||||
},
|
||||
ariaLabel: name,
|
||||
} );
|
||||
} );
|
||||
[ 'credit-card' ].forEach( ( name ) => {
|
||||
registerPaymentMethod( {
|
||||
name,
|
||||
label: name,
|
||||
content: <div>A payment method</div>,
|
||||
edit: <div>A payment method</div>,
|
||||
icons: null,
|
||||
canMakePayment: () => true,
|
||||
supports: {
|
||||
showSavedCards: savedCards,
|
||||
showSaveOption: true,
|
||||
features: [ 'products' ],
|
||||
},
|
||||
ariaLabel: name,
|
||||
} );
|
||||
} );
|
||||
[ 'express-payment' ].forEach( ( name ) => {
|
||||
const Content = ( {
|
||||
onClose = () => void null,
|
||||
onClick = () => void null,
|
||||
} ) => {
|
||||
return (
|
||||
<>
|
||||
<button onClick={ onClick }>
|
||||
{ name + ' express payment method' }
|
||||
</button>
|
||||
<button onClick={ onClose }>
|
||||
{ name + ' express payment method close' }
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
registerExpressPaymentMethod( {
|
||||
name,
|
||||
content: <Content />,
|
||||
edit: <div>An express payment method</div>,
|
||||
canMakePayment: () => true,
|
||||
paymentMethodId: name,
|
||||
supports: {
|
||||
features: [ 'products' ],
|
||||
},
|
||||
} );
|
||||
} );
|
||||
wpDataFunctions
|
||||
.dispatch( PAYMENT_STORE_KEY )
|
||||
.__internalUpdateAvailablePaymentMethods();
|
||||
};
|
||||
|
||||
const resetMockPaymentMethods = () => {
|
||||
[ 'cheque', 'bacs', 'credit-card' ].forEach( ( name ) => {
|
||||
__experimentalDeRegisterPaymentMethod( name );
|
||||
} );
|
||||
[ 'express-payment' ].forEach( ( name ) => {
|
||||
__experimentalDeRegisterExpressPaymentMethod( name );
|
||||
} );
|
||||
};
|
||||
|
||||
describe( 'Payment method data store selectors/thunks', () => {
|
||||
beforeEach( () => {
|
||||
act( () => {
|
||||
registerMockPaymentMethods( false );
|
||||
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/v1\/cart/ ) ) {
|
||||
return Promise.resolve( JSON.stringify( previewCart ) );
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
|
||||
// need to clear the store resolution state between tests.
|
||||
wpDataFunctions.dispatch( storeKey ).invalidateResolutionForStore();
|
||||
wpDataFunctions
|
||||
.dispatch( storeKey )
|
||||
.receiveCart( defaultCartState.cartData );
|
||||
} );
|
||||
} );
|
||||
|
||||
afterEach( async () => {
|
||||
act( () => {
|
||||
resetMockPaymentMethods();
|
||||
fetchMock.resetMocks();
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'toggles active payment method correctly for express payment activation and close', async () => {
|
||||
const TriggerActiveExpressPaymentMethod = () => {
|
||||
const activePaymentMethod = wpDataFunctions.useSelect(
|
||||
( select ) => {
|
||||
return select( PAYMENT_STORE_KEY ).getActivePaymentMethod();
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CheckoutExpressPayment />
|
||||
{ 'Active Payment Method: ' + activePaymentMethod }
|
||||
</>
|
||||
);
|
||||
};
|
||||
const TestComponent = () => {
|
||||
return <TriggerActiveExpressPaymentMethod />;
|
||||
};
|
||||
|
||||
render( <TestComponent /> );
|
||||
|
||||
// should initialize by default the first payment method.
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: credit-card/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
|
||||
// Express payment method clicked.
|
||||
userEvent.click(
|
||||
screen.getByText( 'express-payment express payment method' )
|
||||
);
|
||||
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: express-payment/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
|
||||
// Express payment method closed.
|
||||
userEvent.click(
|
||||
screen.getByText( 'express-payment express payment method close' )
|
||||
);
|
||||
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: credit-card/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'Testing Payment Methods work correctly with saved cards turned on', () => {
|
||||
beforeEach( () => {
|
||||
act( () => {
|
||||
registerMockPaymentMethods( true );
|
||||
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/v1\/cart/ ) ) {
|
||||
return Promise.resolve( JSON.stringify( previewCart ) );
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
|
||||
// need to clear the store resolution state between tests.
|
||||
wpDataFunctions.dispatch( storeKey ).invalidateResolutionForStore();
|
||||
wpDataFunctions
|
||||
.dispatch( storeKey )
|
||||
.receiveCart( defaultCartState.cartData );
|
||||
} );
|
||||
} );
|
||||
|
||||
afterEach( async () => {
|
||||
act( () => {
|
||||
resetMockPaymentMethods();
|
||||
fetchMock.resetMocks();
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'resets saved payment method data after starting and closing an express payment method', async () => {
|
||||
const TriggerActiveExpressPaymentMethod = () => {
|
||||
const { activePaymentMethod, paymentMethodData } =
|
||||
wpDataFunctions.useSelect( ( select ) => {
|
||||
const store = select( PAYMENT_STORE_KEY );
|
||||
return {
|
||||
activePaymentMethod: store.getActivePaymentMethod(),
|
||||
paymentMethodData: store.getPaymentMethodData(),
|
||||
};
|
||||
} );
|
||||
return (
|
||||
<>
|
||||
<CheckoutExpressPayment />
|
||||
<SavedPaymentMethodOptions onChange={ () => void null } />
|
||||
{ 'Active Payment Method: ' + activePaymentMethod }
|
||||
{ paymentMethodData[ 'wc-credit-card-payment-token' ] && (
|
||||
<span>credit-card token</span>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
const TestComponent = () => {
|
||||
return <TriggerActiveExpressPaymentMethod />;
|
||||
};
|
||||
|
||||
render( <TestComponent /> );
|
||||
|
||||
// Should initialize by default the default saved payment method.
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: credit-card/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
|
||||
await waitFor( () => {
|
||||
const creditCardToken = screen.queryByText( /credit-card token/ );
|
||||
expect( creditCardToken ).not.toBeNull();
|
||||
} );
|
||||
|
||||
// Express payment method clicked.
|
||||
userEvent.click(
|
||||
screen.getByText( 'express-payment express payment method' )
|
||||
);
|
||||
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: express-payment/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
|
||||
await waitFor( () => {
|
||||
const creditCardToken = screen.queryByText( /credit-card token/ );
|
||||
expect( creditCardToken ).toBeNull();
|
||||
} );
|
||||
|
||||
// Express payment method closed.
|
||||
userEvent.click(
|
||||
screen.getByText( 'express-payment express payment method close' )
|
||||
);
|
||||
|
||||
await waitFor( () => {
|
||||
const activePaymentMethod = screen.queryByText(
|
||||
/Active Payment Method: credit-card/
|
||||
);
|
||||
expect( activePaymentMethod ).not.toBeNull();
|
||||
} );
|
||||
|
||||
await waitFor( () => {
|
||||
const creditCardToken = screen.queryByText( /credit-card token/ );
|
||||
expect( creditCardToken ).not.toBeNull();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,149 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import * as wpDataFunctions from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { setDefaultPaymentMethod } from '../utils/set-default-payment-method';
|
||||
import { PlainPaymentMethods } from '../../../types';
|
||||
import { PAYMENT_STORE_KEY } from '..';
|
||||
|
||||
const originalSelect = jest.requireActual( '@wordpress/data' ).select;
|
||||
|
||||
describe( 'setDefaultPaymentMethod', () => {
|
||||
afterEach( () => {
|
||||
jest.resetAllMocks();
|
||||
jest.resetModules();
|
||||
} );
|
||||
|
||||
const paymentMethods: PlainPaymentMethods = {
|
||||
'wc-payment-gateway-1': {
|
||||
name: 'wc-payment-gateway-1',
|
||||
},
|
||||
'wc-payment-gateway-2': {
|
||||
name: 'wc-payment-gateway-2',
|
||||
},
|
||||
};
|
||||
|
||||
it( 'correctly sets the first payment method in the list of available payment methods', async () => {
|
||||
jest.spyOn( wpDataFunctions, 'select' ).mockImplementation(
|
||||
( storeName ) => {
|
||||
const originalStore = originalSelect( storeName );
|
||||
if ( storeName === PAYMENT_STORE_KEY ) {
|
||||
return {
|
||||
...originalStore,
|
||||
getAvailableExpressPaymentMethods: () => {
|
||||
return {
|
||||
express_payment_1: {
|
||||
name: 'express_payment_1',
|
||||
},
|
||||
};
|
||||
},
|
||||
getSavedPaymentMethods: () => {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
}
|
||||
return originalStore;
|
||||
}
|
||||
);
|
||||
|
||||
const originalDispatch =
|
||||
jest.requireActual( '@wordpress/data' ).dispatch;
|
||||
const setActivePaymentMethodMock = jest.fn();
|
||||
jest.spyOn( wpDataFunctions, 'dispatch' ).mockImplementation(
|
||||
( storeName ) => {
|
||||
const originalStore = originalDispatch( storeName );
|
||||
if ( storeName === PAYMENT_STORE_KEY ) {
|
||||
return {
|
||||
...originalStore,
|
||||
__internalSetActivePaymentMethod:
|
||||
setActivePaymentMethodMock,
|
||||
};
|
||||
}
|
||||
return originalStore;
|
||||
}
|
||||
);
|
||||
await setDefaultPaymentMethod( paymentMethods );
|
||||
expect( setActivePaymentMethodMock ).toHaveBeenCalledWith(
|
||||
'wc-payment-gateway-1'
|
||||
);
|
||||
} );
|
||||
it( 'correctly sets the saved payment method if one is available', async () => {
|
||||
jest.spyOn( wpDataFunctions, 'select' ).mockImplementation(
|
||||
( storeName ) => {
|
||||
const originalStore = originalSelect( storeName );
|
||||
if ( storeName === PAYMENT_STORE_KEY ) {
|
||||
return {
|
||||
...originalStore,
|
||||
getAvailableExpressPaymentMethods: () => {
|
||||
return {
|
||||
express_payment_1: {
|
||||
name: 'express_payment_1',
|
||||
},
|
||||
};
|
||||
},
|
||||
getSavedPaymentMethods: () => {
|
||||
return {
|
||||
cc: [
|
||||
{
|
||||
method: {
|
||||
gateway: 'saved-method',
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
},
|
||||
expires: '04/44',
|
||||
is_default: true,
|
||||
actions: {
|
||||
delete: {
|
||||
url: 'https://example.com/delete',
|
||||
name: 'Delete',
|
||||
},
|
||||
},
|
||||
tokenId: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
return originalStore;
|
||||
}
|
||||
);
|
||||
|
||||
const originalDispatch =
|
||||
jest.requireActual( '@wordpress/data' ).dispatch;
|
||||
const setActivePaymentMethodMock = jest.fn();
|
||||
jest.spyOn( wpDataFunctions, 'dispatch' ).mockImplementation(
|
||||
( storeName ) => {
|
||||
const originalStore = originalDispatch( storeName );
|
||||
if ( storeName === PAYMENT_STORE_KEY ) {
|
||||
return {
|
||||
...originalStore,
|
||||
__internalSetActivePaymentMethod:
|
||||
setActivePaymentMethodMock,
|
||||
__internalSetPaymentError: () => void 0,
|
||||
__internalSetPaymentIdle: () => void 0,
|
||||
__internalSetExpressPaymentStarted: () => void 0,
|
||||
__internalSetPaymentProcessing: () => void 0,
|
||||
__internalSetPaymentReady: () => void 0,
|
||||
};
|
||||
}
|
||||
return originalStore;
|
||||
}
|
||||
);
|
||||
await setDefaultPaymentMethod( paymentMethods );
|
||||
expect( setActivePaymentMethodMock ).toHaveBeenCalledWith(
|
||||
'saved-method',
|
||||
{
|
||||
isSavedToken: true,
|
||||
payment_method: 'saved-method',
|
||||
token: '2',
|
||||
'wc-saved-method-payment-token': '2',
|
||||
}
|
||||
);
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import * as wpDataFunctions from '@wordpress/data';
|
||||
import { EventObserversType } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PAYMENT_STORE_KEY } from '../index';
|
||||
import { __internalEmitPaymentProcessingEvent } from '../thunks';
|
||||
|
||||
/**
|
||||
* If an observer returns billingAddress, shippingAddress, or paymentData, then the values of these
|
||||
* should be updated in the data stores.
|
||||
*/
|
||||
const testShippingAddress = {
|
||||
first_name: 'test',
|
||||
last_name: 'test',
|
||||
company: 'test',
|
||||
address_1: 'test',
|
||||
address_2: 'test',
|
||||
city: 'test',
|
||||
state: 'test',
|
||||
postcode: 'test',
|
||||
country: 'test',
|
||||
phone: 'test',
|
||||
};
|
||||
const testBillingAddress = {
|
||||
...testShippingAddress,
|
||||
email: 'test@test.com',
|
||||
};
|
||||
const testPaymentMethodData = {
|
||||
payment_method: 'test',
|
||||
};
|
||||
|
||||
describe( 'wc/store/payment thunks', () => {
|
||||
const testPaymentProcessingCallback = jest.fn();
|
||||
const testPaymentProcessingCallback2 = jest.fn();
|
||||
const currentObservers: EventObserversType = {
|
||||
payment_setup: new Map(),
|
||||
};
|
||||
currentObservers.payment_setup.set( 'test', {
|
||||
callback: testPaymentProcessingCallback,
|
||||
priority: 10,
|
||||
} );
|
||||
currentObservers.payment_setup.set( 'test2', {
|
||||
callback: testPaymentProcessingCallback2,
|
||||
priority: 10,
|
||||
} );
|
||||
|
||||
describe( '__internalEmitPaymentProcessingEvent', () => {
|
||||
beforeEach( () => {
|
||||
jest.resetAllMocks();
|
||||
} );
|
||||
it( 'calls all registered observers', async () => {
|
||||
const {
|
||||
__internalEmitPaymentProcessingEvent:
|
||||
__internalEmitPaymentProcessingEventFromStore,
|
||||
} = wpDataFunctions.dispatch( PAYMENT_STORE_KEY );
|
||||
await __internalEmitPaymentProcessingEventFromStore(
|
||||
currentObservers,
|
||||
jest.fn()
|
||||
);
|
||||
expect( testPaymentProcessingCallback ).toHaveBeenCalled();
|
||||
expect( testPaymentProcessingCallback2 ).toHaveBeenCalled();
|
||||
} );
|
||||
|
||||
it( 'sets metadata if successful observers return it', async () => {
|
||||
const testSuccessCallbackWithMetadata = jest.fn().mockReturnValue( {
|
||||
type: 'success',
|
||||
meta: {
|
||||
billingAddress: testBillingAddress,
|
||||
shippingAddress: testShippingAddress,
|
||||
paymentMethodData: testPaymentMethodData,
|
||||
},
|
||||
} );
|
||||
|
||||
currentObservers.payment_setup.set( 'test3', {
|
||||
callback: testSuccessCallbackWithMetadata,
|
||||
priority: 10,
|
||||
} );
|
||||
|
||||
const setBillingAddressMock = jest.fn();
|
||||
const setShippingAddressMock = jest.fn();
|
||||
const setPaymentMethodDataMock = jest.fn();
|
||||
const registryMock = {
|
||||
dispatch: jest.fn().mockImplementation( ( store: string ) => {
|
||||
return {
|
||||
...wpDataFunctions.dispatch( store ),
|
||||
setBillingAddress: setBillingAddressMock,
|
||||
setShippingAddress: setShippingAddressMock,
|
||||
};
|
||||
} ),
|
||||
};
|
||||
|
||||
// Await here because the function returned by the __internalEmitPaymentProcessingEvent action creator
|
||||
// (a thunk) returns a Promise.
|
||||
await __internalEmitPaymentProcessingEvent(
|
||||
currentObservers,
|
||||
jest.fn()
|
||||
)( {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - it would be too much work to mock the entire registry, so we only mock dispatch on it,
|
||||
// which is all we need to test this thunk.
|
||||
registry: registryMock,
|
||||
dispatch: {
|
||||
...wpDataFunctions.dispatch( PAYMENT_STORE_KEY ),
|
||||
__internalSetPaymentMethodData: setPaymentMethodDataMock,
|
||||
},
|
||||
} );
|
||||
|
||||
expect( setBillingAddressMock ).toHaveBeenCalledWith(
|
||||
testBillingAddress
|
||||
);
|
||||
expect( setShippingAddressMock ).toHaveBeenCalledWith(
|
||||
testShippingAddress
|
||||
);
|
||||
expect( setPaymentMethodDataMock ).toHaveBeenCalledWith(
|
||||
testPaymentMethodData
|
||||
);
|
||||
} );
|
||||
it( 'sets metadata if failed observers return it', async () => {
|
||||
const testFailingCallbackWithMetadata = jest.fn().mockReturnValue( {
|
||||
type: 'failure',
|
||||
meta: {
|
||||
billingAddress: testBillingAddress,
|
||||
paymentMethodData: testPaymentMethodData,
|
||||
},
|
||||
} );
|
||||
|
||||
currentObservers.payment_setup.set( 'test4', {
|
||||
callback: testFailingCallbackWithMetadata,
|
||||
priority: 10,
|
||||
} );
|
||||
|
||||
const setBillingAddressMock = jest.fn();
|
||||
const setPaymentMethodDataMock = jest.fn();
|
||||
const registryMock = {
|
||||
dispatch: jest.fn().mockImplementation( ( store: string ) => {
|
||||
return {
|
||||
...wpDataFunctions.dispatch( store ),
|
||||
setBillingAddress: setBillingAddressMock,
|
||||
};
|
||||
} ),
|
||||
};
|
||||
|
||||
// Await here because the function returned by the __internalEmitPaymentProcessingEvent action creator
|
||||
// (a thunk) returns a Promise.
|
||||
await __internalEmitPaymentProcessingEvent(
|
||||
currentObservers,
|
||||
jest.fn()
|
||||
)( {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - it would be too much work to mock the entire registry, so we only mock dispatch on it,
|
||||
// which is all we need to test this thunk.
|
||||
registry: registryMock,
|
||||
dispatch: {
|
||||
...wpDataFunctions.dispatch( PAYMENT_STORE_KEY ),
|
||||
__internalSetPaymentMethodData: setPaymentMethodDataMock,
|
||||
},
|
||||
} );
|
||||
|
||||
expect( setBillingAddressMock ).toHaveBeenCalledWith(
|
||||
testBillingAddress
|
||||
);
|
||||
expect( setPaymentMethodDataMock ).toHaveBeenCalledWith(
|
||||
testPaymentMethodData
|
||||
);
|
||||
} );
|
||||
it( 'sets payment status to error if one observer is successful, but another errors', async () => {
|
||||
const testErrorCallbackWithMetadata = jest
|
||||
.fn()
|
||||
.mockImplementation( () => {
|
||||
return {
|
||||
type: 'error',
|
||||
};
|
||||
} );
|
||||
|
||||
const testSuccessCallback = jest.fn().mockReturnValue( {
|
||||
type: 'success',
|
||||
} );
|
||||
|
||||
currentObservers.payment_setup.set( 'test5', {
|
||||
callback: testErrorCallbackWithMetadata,
|
||||
priority: 10,
|
||||
} );
|
||||
currentObservers.payment_setup.set( 'test6', {
|
||||
callback: testSuccessCallback,
|
||||
priority: 9,
|
||||
} );
|
||||
|
||||
const setPaymentErrorMock = jest.fn();
|
||||
const setPaymentReadyMock = jest.fn();
|
||||
const registryMock = {
|
||||
dispatch: jest
|
||||
.fn()
|
||||
.mockImplementation( wpDataFunctions.dispatch ),
|
||||
};
|
||||
|
||||
// Await here because the function returned by the __internalEmitPaymentProcessingEvent action creator
|
||||
// (a thunk) returns a Promise.
|
||||
await __internalEmitPaymentProcessingEvent(
|
||||
currentObservers,
|
||||
jest.fn()
|
||||
)( {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - it would be too much work to mock the entire registry, so we only mock dispatch on it,
|
||||
// which is all we need to test this thunk.
|
||||
registry: registryMock,
|
||||
dispatch: {
|
||||
...wpDataFunctions.dispatch( PAYMENT_STORE_KEY ),
|
||||
__internalSetPaymentError: setPaymentErrorMock,
|
||||
__internalSetPaymentReady: setPaymentReadyMock,
|
||||
},
|
||||
} );
|
||||
|
||||
// The observer throwing will cause this.
|
||||
//expect( console ).toHaveErroredWith( new Error( 'test error' ) );
|
||||
expect( setPaymentErrorMock ).toHaveBeenCalled();
|
||||
expect( setPaymentReadyMock ).not.toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
import type { BillingAddress, ShippingAddress } from '@woocommerce/settings';
|
||||
import { isObject, isString, objectHasProp } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
emitEventWithAbort,
|
||||
isErrorResponse,
|
||||
isFailResponse,
|
||||
isSuccessResponse,
|
||||
noticeContexts,
|
||||
ObserverResponse,
|
||||
} from '../../base/context/event-emit';
|
||||
import { EMIT_TYPES } from '../../base/context/providers/cart-checkout/payment-events/event-emit';
|
||||
import type { emitProcessingEventType } from './types';
|
||||
import { CART_STORE_KEY } from '../cart';
|
||||
import {
|
||||
isBillingAddress,
|
||||
isShippingAddress,
|
||||
} from '../../types/type-guards/address';
|
||||
import { isObserverResponse } from '../../types/type-guards/observers';
|
||||
import { isValidValidationErrorsObject } from '../../types/type-guards/validation';
|
||||
|
||||
export const __internalSetExpressPaymentError = ( message?: string ) => {
|
||||
return ( { registry } ) => {
|
||||
const { createErrorNotice, removeNotice } =
|
||||
registry.dispatch( noticesStore );
|
||||
if ( message ) {
|
||||
createErrorNotice( message, {
|
||||
id: 'wc-express-payment-error',
|
||||
context: noticeContexts.EXPRESS_PAYMENTS,
|
||||
} );
|
||||
} else {
|
||||
removeNotice(
|
||||
'wc-express-payment-error',
|
||||
noticeContexts.EXPRESS_PAYMENTS
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit the payment_processing event
|
||||
*/
|
||||
export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = (
|
||||
currentObserver,
|
||||
setValidationErrors
|
||||
) => {
|
||||
return ( { dispatch, registry } ) => {
|
||||
const { createErrorNotice, removeNotice } =
|
||||
registry.dispatch( 'core/notices' );
|
||||
removeNotice( 'wc-payment-error', noticeContexts.PAYMENTS );
|
||||
return emitEventWithAbort(
|
||||
currentObserver,
|
||||
EMIT_TYPES.PAYMENT_SETUP,
|
||||
{}
|
||||
).then( ( observerResponses ) => {
|
||||
let successResponse: ObserverResponse | undefined,
|
||||
errorResponse: ObserverResponse | undefined,
|
||||
billingAddress: BillingAddress | undefined,
|
||||
shippingAddress: ShippingAddress | undefined;
|
||||
observerResponses.forEach( ( response ) => {
|
||||
if ( isSuccessResponse( response ) ) {
|
||||
// The last observer response always "wins" for success.
|
||||
successResponse = response;
|
||||
}
|
||||
|
||||
// We consider both failed and error responses as an error.
|
||||
if (
|
||||
isErrorResponse( response ) ||
|
||||
isFailResponse( response )
|
||||
) {
|
||||
errorResponse = response;
|
||||
}
|
||||
// Extensions may return shippingData, shippingAddress, billingData, and billingAddress in the response,
|
||||
// so we need to check for all. If we detect either shippingData or billingData we need to show a
|
||||
// deprecated warning for it, but also apply the changes to the wc/store/cart store.
|
||||
const {
|
||||
billingAddress: billingAddressFromResponse,
|
||||
|
||||
// Deprecated, but keeping it for now, for compatibility with extensions returning it.
|
||||
billingData: billingDataFromResponse,
|
||||
shippingAddress: shippingAddressFromResponse,
|
||||
|
||||
// Deprecated, but keeping it for now, for compatibility with extensions returning it.
|
||||
shippingData: shippingDataFromResponse,
|
||||
} = response?.meta || {};
|
||||
|
||||
billingAddress = billingAddressFromResponse as BillingAddress;
|
||||
shippingAddress =
|
||||
shippingAddressFromResponse as ShippingAddress;
|
||||
|
||||
if ( billingDataFromResponse ) {
|
||||
// Set this here so that old extensions still using billingData can set the billingAddress.
|
||||
billingAddress = billingDataFromResponse as BillingAddress;
|
||||
deprecated(
|
||||
'returning billingData from an onPaymentProcessing observer in WooCommerce Blocks',
|
||||
{
|
||||
version: '9.5.0',
|
||||
alternative: 'billingAddress',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/6369',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
objectHasProp( shippingDataFromResponse, 'address' ) &&
|
||||
shippingDataFromResponse.address
|
||||
) {
|
||||
// Set this here so that old extensions still using shippingData can set the shippingAddress.
|
||||
shippingAddress =
|
||||
shippingDataFromResponse.address as ShippingAddress;
|
||||
deprecated(
|
||||
'returning shippingData from an onPaymentProcessing observer in WooCommerce Blocks',
|
||||
{
|
||||
version: '9.5.0',
|
||||
alternative: 'shippingAddress',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/8163',
|
||||
}
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
const { setBillingAddress, setShippingAddress } =
|
||||
registry.dispatch( CART_STORE_KEY );
|
||||
|
||||
// Observer returned success, we sync the payment method data and billing address.
|
||||
if ( isObserverResponse( successResponse ) && ! errorResponse ) {
|
||||
const { paymentMethodData } = successResponse?.meta || {};
|
||||
|
||||
if ( isBillingAddress( billingAddress ) ) {
|
||||
setBillingAddress( billingAddress );
|
||||
}
|
||||
if ( isShippingAddress( shippingAddress ) ) {
|
||||
setShippingAddress( shippingAddress );
|
||||
}
|
||||
|
||||
dispatch.__internalSetPaymentMethodData(
|
||||
isObject( paymentMethodData ) ? paymentMethodData : {}
|
||||
);
|
||||
dispatch.__internalSetPaymentReady();
|
||||
} else if ( isFailResponse( errorResponse ) ) {
|
||||
const { paymentMethodData } = errorResponse?.meta || {};
|
||||
|
||||
if (
|
||||
objectHasProp( errorResponse, 'message' ) &&
|
||||
isString( errorResponse.message ) &&
|
||||
errorResponse.message.length
|
||||
) {
|
||||
let context: string = noticeContexts.PAYMENTS;
|
||||
if (
|
||||
objectHasProp( errorResponse, 'messageContext' ) &&
|
||||
isString( errorResponse.messageContext ) &&
|
||||
errorResponse.messageContext.length
|
||||
) {
|
||||
context = errorResponse.messageContext;
|
||||
}
|
||||
createErrorNotice( errorResponse.message, {
|
||||
id: 'wc-payment-error',
|
||||
isDismissible: false,
|
||||
context,
|
||||
} );
|
||||
}
|
||||
|
||||
if ( isBillingAddress( billingAddress ) ) {
|
||||
setBillingAddress( billingAddress );
|
||||
}
|
||||
|
||||
dispatch.__internalSetPaymentMethodData(
|
||||
isObject( paymentMethodData ) ? paymentMethodData : {}
|
||||
);
|
||||
dispatch.__internalSetPaymentError();
|
||||
} else if ( isErrorResponse( errorResponse ) ) {
|
||||
if (
|
||||
objectHasProp( errorResponse, 'message' ) &&
|
||||
isString( errorResponse.message ) &&
|
||||
errorResponse.message.length
|
||||
) {
|
||||
let context: string = noticeContexts.PAYMENTS;
|
||||
if (
|
||||
objectHasProp( errorResponse, 'messageContext' ) &&
|
||||
isString( errorResponse.messageContext ) &&
|
||||
errorResponse.messageContext.length
|
||||
) {
|
||||
context = errorResponse.messageContext;
|
||||
}
|
||||
createErrorNotice( errorResponse.message, {
|
||||
id: 'wc-payment-error',
|
||||
isDismissible: false,
|
||||
context,
|
||||
} );
|
||||
}
|
||||
|
||||
dispatch.__internalSetPaymentError();
|
||||
|
||||
if (
|
||||
isValidValidationErrorsObject(
|
||||
errorResponse.validationErrors
|
||||
)
|
||||
) {
|
||||
setValidationErrors( errorResponse.validationErrors );
|
||||
}
|
||||
} else {
|
||||
// Otherwise there are no payment methods doing anything so just assume payment method is ready.
|
||||
dispatch.__internalSetPaymentReady();
|
||||
}
|
||||
} );
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
PlainPaymentMethods,
|
||||
PlainExpressPaymentMethods,
|
||||
} from '@woocommerce/types';
|
||||
import type {
|
||||
EmptyObjectType,
|
||||
ObjectType,
|
||||
FieldValidationStatus,
|
||||
} from '@woocommerce/types';
|
||||
import { DataRegistry } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { EventObserversType } from '../../base/context/event-emit';
|
||||
import type { DispatchFromMap } from '../mapped-types';
|
||||
import * as actions from './actions';
|
||||
|
||||
export interface CustomerPaymentMethodConfiguration {
|
||||
gateway: string;
|
||||
brand: string;
|
||||
last4: string;
|
||||
}
|
||||
export interface SavedPaymentMethod {
|
||||
method: CustomerPaymentMethodConfiguration;
|
||||
expires: string;
|
||||
is_default: boolean;
|
||||
tokenId: number;
|
||||
actions: ObjectType;
|
||||
}
|
||||
export type SavedPaymentMethods =
|
||||
| Record< string, SavedPaymentMethod[] >
|
||||
| EmptyObjectType;
|
||||
|
||||
export interface PaymentMethodDispatchers {
|
||||
setRegisteredPaymentMethods: (
|
||||
paymentMethods: PlainPaymentMethods
|
||||
) => void;
|
||||
setRegisteredExpressPaymentMethods: (
|
||||
paymentMethods: PlainExpressPaymentMethods
|
||||
) => void;
|
||||
setActivePaymentMethod: (
|
||||
paymentMethod: string,
|
||||
paymentMethodData?: ObjectType | EmptyObjectType
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface PaymentStatusDispatchers {
|
||||
pristine: () => void;
|
||||
started: () => void;
|
||||
processing: () => void;
|
||||
error: ( error: string ) => void;
|
||||
failed: (
|
||||
error?: string,
|
||||
paymentMethodData?: ObjectType | EmptyObjectType,
|
||||
billingAddress?: ObjectType | EmptyObjectType
|
||||
) => void;
|
||||
success: (
|
||||
paymentMethodData?: ObjectType | EmptyObjectType,
|
||||
billingAddress?: ObjectType | EmptyObjectType,
|
||||
shippingData?: ObjectType | EmptyObjectType
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type PaymentMethodsDispatcherType = (
|
||||
paymentMethods: PlainPaymentMethods
|
||||
) => undefined | void;
|
||||
|
||||
/**
|
||||
* Type for emitProcessingEventType() thunk
|
||||
*/
|
||||
export type emitProcessingEventType = (
|
||||
observers: EventObserversType,
|
||||
setValidationErrors: (
|
||||
errors: Record< string, FieldValidationStatus >
|
||||
) => void
|
||||
) => ( {
|
||||
dispatch,
|
||||
registry,
|
||||
}: {
|
||||
dispatch: DispatchFromMap< typeof actions >;
|
||||
registry: DataRegistry;
|
||||
} ) => void;
|
||||
|
||||
export interface PaymentStatus {
|
||||
isPristine?: boolean;
|
||||
isStarted?: boolean;
|
||||
isProcessing?: boolean;
|
||||
isFinished?: boolean;
|
||||
hasError?: boolean;
|
||||
hasFailed?: boolean;
|
||||
isSuccessful?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
CanMakePaymentArgument,
|
||||
ExpressPaymentMethodConfigInstance,
|
||||
PaymentMethodConfigInstance,
|
||||
} from '@woocommerce/types';
|
||||
import { CURRENT_USER_IS_ADMIN, getSetting } from '@woocommerce/settings';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
import {
|
||||
deriveSelectedShippingRates,
|
||||
emptyHiddenAddressFields,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
getExpressPaymentMethods,
|
||||
getPaymentMethods,
|
||||
} from '@woocommerce/blocks-registry';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY as CART_STORE_KEY } from '../../cart/constants';
|
||||
import { STORE_KEY as PAYMENT_STORE_KEY } from '../constants';
|
||||
import { noticeContexts } from '../../../base/context/event-emit';
|
||||
import {
|
||||
EMPTY_CART_ERRORS,
|
||||
EMPTY_CART_ITEM_ERRORS,
|
||||
EMPTY_EXTENSIONS,
|
||||
} from '../../../data/constants';
|
||||
import { defaultCartState } from '../../../data/cart/default-state';
|
||||
|
||||
/**
|
||||
* Get the argument that will be passed to a payment method's `canMakePayment` method.
|
||||
*/
|
||||
export const getCanMakePaymentArg = (): CanMakePaymentArgument => {
|
||||
const isEditor = !! select( 'core/editor' );
|
||||
let canPayArgument: CanMakePaymentArgument;
|
||||
|
||||
if ( ! isEditor ) {
|
||||
const store = select( CART_STORE_KEY );
|
||||
const cart = store.getCartData();
|
||||
const cartErrors = store.getCartErrors();
|
||||
const cartTotals = store.getCartTotals();
|
||||
const cartIsLoading = ! store.hasFinishedResolution( 'getCartData' );
|
||||
const isLoadingRates = store.isCustomerDataUpdating();
|
||||
const selectedShippingMethods = deriveSelectedShippingRates(
|
||||
cart.shippingRates
|
||||
);
|
||||
|
||||
const cartForCanPayArgument = {
|
||||
cartCoupons: cart.coupons,
|
||||
cartItems: cart.items,
|
||||
crossSellsProducts: cart.crossSells,
|
||||
cartFees: cart.fees,
|
||||
cartItemsCount: cart.itemsCount,
|
||||
cartItemsWeight: cart.itemsWeight,
|
||||
cartNeedsPayment: cart.needsPayment,
|
||||
cartNeedsShipping: cart.needsShipping,
|
||||
cartItemErrors: cart.errors,
|
||||
cartTotals,
|
||||
cartIsLoading,
|
||||
cartErrors,
|
||||
billingData: emptyHiddenAddressFields( cart.billingAddress ),
|
||||
billingAddress: emptyHiddenAddressFields( cart.billingAddress ),
|
||||
shippingAddress: emptyHiddenAddressFields( cart.shippingAddress ),
|
||||
extensions: cart.extensions,
|
||||
shippingRates: cart.shippingRates,
|
||||
isLoadingRates,
|
||||
cartHasCalculatedShipping: cart.hasCalculatedShipping,
|
||||
paymentRequirements: cart.paymentRequirements,
|
||||
receiveCart: dispatch( CART_STORE_KEY ).receiveCart,
|
||||
};
|
||||
canPayArgument = {
|
||||
cart: cartForCanPayArgument,
|
||||
cartTotals: cart.totals,
|
||||
cartNeedsShipping: cart.needsShipping,
|
||||
billingData: cart.billingAddress,
|
||||
billingAddress: cart.billingAddress,
|
||||
shippingAddress: cart.shippingAddress,
|
||||
selectedShippingMethods,
|
||||
paymentMethods: cart.paymentMethods,
|
||||
paymentRequirements: cart.paymentRequirements,
|
||||
};
|
||||
} else {
|
||||
const cartForCanPayArgument = {
|
||||
cartCoupons: previewCart.coupons,
|
||||
cartItems: previewCart.items,
|
||||
crossSellsProducts: previewCart.cross_sells,
|
||||
cartFees: previewCart.fees,
|
||||
cartItemsCount: previewCart.items_count,
|
||||
cartItemsWeight: previewCart.items_weight,
|
||||
cartNeedsPayment: previewCart.needs_payment,
|
||||
cartNeedsShipping: previewCart.needs_shipping,
|
||||
cartItemErrors: EMPTY_CART_ITEM_ERRORS,
|
||||
cartTotals: previewCart.totals,
|
||||
cartIsLoading: false,
|
||||
cartErrors: EMPTY_CART_ERRORS,
|
||||
billingData: defaultCartState.cartData.billingAddress,
|
||||
billingAddress: defaultCartState.cartData.billingAddress,
|
||||
shippingAddress: defaultCartState.cartData.shippingAddress,
|
||||
extensions: EMPTY_EXTENSIONS,
|
||||
shippingRates: previewCart.shipping_rates,
|
||||
isLoadingRates: false,
|
||||
cartHasCalculatedShipping: previewCart.has_calculated_shipping,
|
||||
paymentRequirements: previewCart.payment_requirements,
|
||||
receiveCart: () => undefined,
|
||||
};
|
||||
canPayArgument = {
|
||||
cart: cartForCanPayArgument,
|
||||
cartTotals: cartForCanPayArgument.cartTotals,
|
||||
cartNeedsShipping: cartForCanPayArgument.cartNeedsShipping,
|
||||
billingData: cartForCanPayArgument.billingAddress,
|
||||
billingAddress: cartForCanPayArgument.billingAddress,
|
||||
shippingAddress: cartForCanPayArgument.shippingAddress,
|
||||
selectedShippingMethods: deriveSelectedShippingRates(
|
||||
cartForCanPayArgument.shippingRates
|
||||
),
|
||||
paymentMethods: previewCart.payment_methods,
|
||||
paymentRequirements: cartForCanPayArgument.paymentRequirements,
|
||||
};
|
||||
}
|
||||
|
||||
return canPayArgument;
|
||||
};
|
||||
|
||||
const registrationErrorNotice = (
|
||||
paymentMethod:
|
||||
| ExpressPaymentMethodConfigInstance
|
||||
| PaymentMethodConfigInstance,
|
||||
errorMessage: string,
|
||||
express = false
|
||||
) => {
|
||||
const { createErrorNotice } = dispatch( 'core/notices' );
|
||||
const noticeContext = express
|
||||
? noticeContexts.EXPRESS_PAYMENTS
|
||||
: noticeContexts.PAYMENTS;
|
||||
const errorText = sprintf(
|
||||
/* translators: %s the id of the payment method being registered (bank transfer, cheque...) */
|
||||
__(
|
||||
`There was an error registering the payment method with id '%s': `,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
paymentMethod.paymentMethodId
|
||||
);
|
||||
createErrorNotice( `${ errorText } ${ errorMessage }`, {
|
||||
context: noticeContext,
|
||||
id: `wc-${ paymentMethod.paymentMethodId }-registration-error`,
|
||||
} );
|
||||
};
|
||||
|
||||
export const checkPaymentMethodsCanPay = async ( express = false ) => {
|
||||
let availablePaymentMethods = {};
|
||||
|
||||
const paymentMethods = express
|
||||
? getExpressPaymentMethods()
|
||||
: getPaymentMethods();
|
||||
|
||||
const addAvailablePaymentMethod = (
|
||||
paymentMethod:
|
||||
| PaymentMethodConfigInstance
|
||||
| ExpressPaymentMethodConfigInstance
|
||||
) => {
|
||||
const { name } = paymentMethod;
|
||||
availablePaymentMethods = {
|
||||
...availablePaymentMethods,
|
||||
[ paymentMethod.name ]: { name },
|
||||
};
|
||||
};
|
||||
|
||||
// Order payment methods.
|
||||
const paymentMethodsOrder = express
|
||||
? Object.keys( paymentMethods )
|
||||
: Array.from(
|
||||
new Set( [
|
||||
...( getSetting( 'paymentMethodSortOrder', [] ) as [] ),
|
||||
...Object.keys( paymentMethods ),
|
||||
] )
|
||||
);
|
||||
const canPayArgument = getCanMakePaymentArg();
|
||||
const cartPaymentMethods = canPayArgument.paymentMethods as string[];
|
||||
const isEditor = !! select( 'core/editor' );
|
||||
|
||||
for ( let i = 0; i < paymentMethodsOrder.length; i++ ) {
|
||||
const paymentMethodName = paymentMethodsOrder[ i ];
|
||||
const paymentMethod = paymentMethods[ paymentMethodName ];
|
||||
|
||||
if ( ! paymentMethod ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if payment method should be available. This always evaluates to true in the editor context.
|
||||
try {
|
||||
const validForCart =
|
||||
isEditor || express
|
||||
? true
|
||||
: cartPaymentMethods.includes( paymentMethodName );
|
||||
const canPay = isEditor
|
||||
? true
|
||||
: validForCart &&
|
||||
( await Promise.resolve(
|
||||
paymentMethod.canMakePayment( canPayArgument )
|
||||
) );
|
||||
|
||||
if ( canPay ) {
|
||||
if ( typeof canPay === 'object' && canPay.error ) {
|
||||
throw new Error( canPay.error.message );
|
||||
}
|
||||
addAvailablePaymentMethod( paymentMethod );
|
||||
}
|
||||
} catch ( e ) {
|
||||
if ( CURRENT_USER_IS_ADMIN || isEditor ) {
|
||||
registrationErrorNotice( paymentMethod, e as string, express );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const availablePaymentMethodNames = Object.keys( availablePaymentMethods );
|
||||
const currentlyAvailablePaymentMethods = express
|
||||
? select( PAYMENT_STORE_KEY ).getAvailableExpressPaymentMethods()
|
||||
: select( PAYMENT_STORE_KEY ).getAvailablePaymentMethods();
|
||||
|
||||
if (
|
||||
Object.keys( currentlyAvailablePaymentMethods ).length ===
|
||||
availablePaymentMethodNames.length &&
|
||||
Object.keys( currentlyAvailablePaymentMethods ).every( ( current ) =>
|
||||
availablePaymentMethodNames.includes( current )
|
||||
)
|
||||
) {
|
||||
// All the names are the same, no need to dispatch more actions.
|
||||
return true;
|
||||
}
|
||||
|
||||
const {
|
||||
__internalSetAvailablePaymentMethods,
|
||||
__internalSetAvailableExpressPaymentMethods,
|
||||
} = dispatch( PAYMENT_STORE_KEY );
|
||||
|
||||
const setCallback = express
|
||||
? __internalSetAvailableExpressPaymentMethods
|
||||
: __internalSetAvailablePaymentMethods;
|
||||
|
||||
setCallback( availablePaymentMethods );
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getPaymentMethods } from '@woocommerce/blocks-registry';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { SavedPaymentMethods } from '../types';
|
||||
|
||||
/**
|
||||
* Gets the payment methods saved for the current user after filtering out disabled ones.
|
||||
*/
|
||||
export const filterActiveSavedPaymentMethods = (
|
||||
availablePaymentMethods: string[] = [],
|
||||
savedPaymentMethods: SavedPaymentMethods
|
||||
): SavedPaymentMethods => {
|
||||
if ( availablePaymentMethods.length === 0 ) {
|
||||
return {};
|
||||
}
|
||||
const registeredPaymentMethods = getPaymentMethods();
|
||||
const availablePaymentMethodsWithConfig = Object.fromEntries(
|
||||
availablePaymentMethods.map( ( name ) => [
|
||||
name,
|
||||
registeredPaymentMethods[ name ],
|
||||
] )
|
||||
);
|
||||
|
||||
const paymentMethodKeys = Object.keys( savedPaymentMethods );
|
||||
const activeSavedPaymentMethods = {} as SavedPaymentMethods;
|
||||
paymentMethodKeys.forEach( ( type ) => {
|
||||
const methods = savedPaymentMethods[ type ].filter(
|
||||
( {
|
||||
method: { gateway },
|
||||
}: {
|
||||
method: {
|
||||
gateway: string;
|
||||
};
|
||||
} ) =>
|
||||
gateway in availablePaymentMethodsWithConfig &&
|
||||
availablePaymentMethodsWithConfig[ gateway ].supports
|
||||
?.showSavedCards
|
||||
);
|
||||
if ( methods.length ) {
|
||||
activeSavedPaymentMethods[ type ] = methods;
|
||||
}
|
||||
} );
|
||||
return activeSavedPaymentMethods;
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select, dispatch } from '@wordpress/data';
|
||||
import { PlainPaymentMethods } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY as PAYMENT_STORE_KEY } from '../constants';
|
||||
|
||||
export const setDefaultPaymentMethod = async (
|
||||
paymentMethods: PlainPaymentMethods
|
||||
) => {
|
||||
const paymentMethodKeys = Object.keys( paymentMethods );
|
||||
|
||||
const expressPaymentMethodKeys = Object.keys(
|
||||
select( PAYMENT_STORE_KEY ).getAvailableExpressPaymentMethods()
|
||||
);
|
||||
|
||||
const allPaymentMethodKeys = [
|
||||
...paymentMethodKeys,
|
||||
...expressPaymentMethodKeys,
|
||||
];
|
||||
|
||||
const savedPaymentMethods =
|
||||
select( PAYMENT_STORE_KEY ).getSavedPaymentMethods();
|
||||
|
||||
const savedPaymentMethod =
|
||||
Object.keys( savedPaymentMethods ).flatMap(
|
||||
( type ) => savedPaymentMethods[ type ]
|
||||
)[ 0 ] || undefined;
|
||||
|
||||
if ( savedPaymentMethod ) {
|
||||
const token = savedPaymentMethod.tokenId.toString();
|
||||
const paymentMethodSlug = savedPaymentMethod.method.gateway;
|
||||
|
||||
const savedTokenKey = `wc-${ paymentMethodSlug }-payment-token`;
|
||||
|
||||
dispatch( PAYMENT_STORE_KEY ).__internalSetActivePaymentMethod(
|
||||
paymentMethodSlug,
|
||||
{
|
||||
token,
|
||||
payment_method: paymentMethodSlug,
|
||||
[ savedTokenKey ]: token,
|
||||
isSavedToken: true,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const activePaymentMethod =
|
||||
select( PAYMENT_STORE_KEY ).getActivePaymentMethod();
|
||||
|
||||
// Return if current method is valid.
|
||||
if (
|
||||
activePaymentMethod &&
|
||||
allPaymentMethodKeys.includes( activePaymentMethod )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch( PAYMENT_STORE_KEY ).__internalSetPaymentIdle();
|
||||
|
||||
dispatch( PAYMENT_STORE_KEY ).__internalSetActivePaymentMethod(
|
||||
paymentMethodKeys[ 0 ]
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user