Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Add support for extensions to filter express payment methods (#4774)
Browse files Browse the repository at this point in the history
* Support express payment methods filtering by extensions

* Add tests for getCanMakePayment and fix payment tests' TS errors

* Add comments for payment-method-config-helper test
  • Loading branch information
ralucaStan authored Sep 23, 2021
1 parent 410eb43 commit e84577d
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
/**
* Internal dependencies
*/
import { canMakePaymentWithFeaturesCheck } from './payment-method-config-helper';
import { getCanMakePayment } from './payment-method-config-helper';
import { assertConfigHasProperties, assertValidElement } from './assertions';

export default class ExpressPaymentMethodConfig
Expand All @@ -22,7 +22,7 @@ export default class ExpressPaymentMethodConfig
public edit: ReactNode;
public paymentMethodId?: string;
public supports: Supports;
public canMakePayment: CanMakePaymentCallback;
public canMakePaymentFromConfig: CanMakePaymentCallback;

constructor( config: ExpressPaymentMethodConfiguration ) {
// validate config
Expand All @@ -34,9 +34,15 @@ export default class ExpressPaymentMethodConfig
this.supports = {
features: config?.supports?.features || [ 'products' ],
};
this.canMakePayment = canMakePaymentWithFeaturesCheck(
config.canMakePayment,
this.supports.features
this.canMakePaymentFromConfig = config.canMakePayment;
}

// canMakePayment is calculated each time based on data that modifies outside of the class (eg: cart data).
get canMakePayment(): CanMakePaymentCallback {
return getCanMakePayment(
this.canMakePaymentFromConfig,
this.supports.features,
this.name
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import type { CanMakePaymentCallback } from '@woocommerce/type-defs/payments';
/**
* Internal dependencies
*/
import type {
import {
NamespacedCanMakePaymentExtensionsCallbacks,
PaymentMethodName,
ExtensionNamespace,
extensionsConfig,
} from './extensions-config';

// Filter out payment methods by supported features and cart requirement.
export const canMakePaymentWithFeaturesCheck = (
canMakePayment: CanMakePaymentCallback,
features: string[]
): CanMakePaymentCallback => ( canPayArgument ) => {
const requirements = canPayArgument.paymentRequirements || [];
const requirements = canPayArgument?.paymentRequirements || [];
const featuresSupportRequirements = requirements.every( ( requirement ) =>
features.includes( requirement )
);
Expand Down Expand Up @@ -65,3 +66,22 @@ export const canMakePaymentWithExtensions = (

return canPay;
};

export const getCanMakePayment = (
canMakePayment: CanMakePaymentCallback,
features: string[],
paymentMethodName: string
): CanMakePaymentCallback => {
const canPay = canMakePaymentWithFeaturesCheck( canMakePayment, features );
// Loop through all callbacks to check if there are any registered for this payment method.
return ( Object.values( extensionsConfig.canMakePayment ) as Record<
PaymentMethodName,
CanMakePaymentCallback
>[] ).some( ( callbacks ) => paymentMethodName in callbacks )
? canMakePaymentWithExtensions(
canPay,
extensionsConfig.canMakePayment,
paymentMethodName
)
: canPay;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import type {
/**
* Internal dependencies
*/
import {
canMakePaymentWithFeaturesCheck,
canMakePaymentWithExtensions,
} from './payment-method-config-helper';
import { extensionsConfig, PaymentMethodName } from './extensions-config';
import { getCanMakePayment } from './payment-method-config-helper';
import {
assertConfigHasProperties,
assertValidElement,
Expand Down Expand Up @@ -68,22 +64,11 @@ export default class PaymentMethodConfig

// canMakePayment is calculated each time based on data that modifies outside of the class (eg: cart data).
get canMakePayment(): CanMakePaymentCallback {
const canPay = canMakePaymentWithFeaturesCheck(
return getCanMakePayment(
this.canMakePaymentFromConfig,
this.supports.features
this.supports.features,
this.name
);

// Loop through all callbacks to check if there are any registered for this payment method.
return ( Object.values( extensionsConfig.canMakePayment ) as Record<
PaymentMethodName,
CanMakePaymentCallback
>[] ).some( ( callbacks ) => this.name in callbacks )
? canMakePaymentWithExtensions(
canPay,
extensionsConfig.canMakePayment,
this.name
)
: canPay;
}

static assertValidConfig = ( config: PaymentMethodConfiguration ): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,88 @@
* External dependencies
*/
import { registerPaymentMethodExtensionCallbacks } from '@woocommerce/blocks-registry';

/**
* Internal dependencies
*/
import * as helpers from '../payment-method-config-helper';
import { canMakePaymentExtensionsCallbacks } from '../extensions-config';
import { canMakePaymentWithExtensions } from '../payment-method-config-helper';

describe( 'canMakePaymentWithExtensions', () => {
const canMakePaymentArgument = {
cartTotals: {
total_items: '1488',
total_items_tax: '312',
total_fees: '0',
total_fees_tax: '0',
total_discount: '0',
total_discount_tax: '0',
total_shipping: '0',
total_shipping_tax: '0',
total_price: '1800',
total_tax: '312',
tax_lines: [
{
name: 'BTW',
price: '312',
rate: '21%',
},
],
currency_code: 'EUR',
currency_symbol: '€',
currency_minor_unit: 2,
currency_decimal_separator: ',',
currency_thousand_separator: '.',
currency_prefix: '€',
currency_suffix: '',
},
cartNeedsShipping: true,
billingData: {
first_name: 'name',
last_name: 'Name',
company: '',
address_1: 'fdsfdsfdsf',
address_2: '',
city: 'Berlin',
state: '',
postcode: 'xxxxx',
country: 'DE',
email: '[email protected]',
phone: '1234',
},
shippingAddress: {
first_name: 'name',
last_name: 'Name',
company: '',
address_1: 'fdsfdsfdsf',
address_2: '',
city: 'Berlin',
state: '',
postcode: 'xxxxx',
country: 'DE',
phone: '1234',
},
selectedShippingMethods: {
'0': 'free_shipping:1',
},
paymentRequirements: [ 'products' ],
};
describe( 'payment-method-config-helper', () => {
const trueCallback = jest.fn().mockReturnValue( true );
const falseCallback = jest.fn().mockReturnValue( false );
const bacsCallback = jest.fn().mockReturnValue( false );
const throwsCallback = jest.fn().mockImplementation( () => {
throw new Error();
} );
beforeAll( () => {
// Register extension callbacks for two payment methods.
registerPaymentMethodExtensionCallbacks(
'woocommerce-marketplace-extension',
{
// cod: one extension returns true, the other returns false.
cod: () => trueCallback,
cod: trueCallback,
// cheque: returns true only if arg.billingData.postcode is 12345.
cheque: ( arg ) => arg.billingData.postcode === '12345',
// bacs: both extensions return false.
bacs: falseCallback,
bacs: bacsCallback,
// woopay: both extensions return true.
woopay: trueCallback,
// testpay: one callback errors, one returns true
Expand All @@ -35,9 +94,9 @@ describe( 'canMakePaymentWithExtensions', () => {
'other-woocommerce-marketplace-extension',
{
cod: falseCallback,
bacs: falseCallback,
woopay: trueCallback,
testpay: trueCallback,
bacs: bacsCallback,
}
);
} );
Expand All @@ -46,54 +105,101 @@ describe( 'canMakePaymentWithExtensions', () => {
trueCallback.mockClear();
throwsCallback.mockClear();
falseCallback.mockClear();
bacsCallback.mockClear();
} );
describe( 'getCanMakePayment', () => {
it( 'returns callback canMakePaymentWithFeaturesCheck if no extension callback is detected', () => {
// Define arguments from a payment method ('missing-payment-method') with no registered extension callbacks.
const args = {
canMakePayment: jest.fn().mockImplementation( () => true ),
features: [ 'products' ],
paymentMethodName: 'missing-payment-method',
};

it( "Returns false without executing the registered callbacks, if the payment method's canMakePayment callback returns false.", () => {
const canMakePayment = () => false;
const canMakePaymentWithExtensionsResult = canMakePaymentWithExtensions(
canMakePayment,
canMakePaymentExtensionsCallbacks,
'cod'
)();
expect( canMakePaymentWithExtensionsResult ).toBe( false );
expect( trueCallback ).not.toHaveBeenCalled();
} );
const canMakePayment = helpers.getCanMakePayment(
args.canMakePayment,
args.features,
args.paymentMethodName
)( canMakePaymentArgument );

it( 'Returns early when a registered callback returns false, without executing all the registered callbacks', () => {
canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'bacs'
)();
expect( falseCallback ).toHaveBeenCalledTimes( 1 );
} );
// Expect that the result of getCanMakePayment is the result of
// the payment method's own canMakePayment, as no extension callbacks are called.
expect( canMakePayment ).toEqual( args.canMakePayment() );
} );

it( 'Returns true if all extension callbacks return true', () => {
const result = canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'woopay'
)();
expect( result ).toBe( true );
} );
it( 'returns callbacks from the extensions when they are defined', () => {
// Define arguments from a payment method (bacs) with registered extension callbacks.
const args = {
canMakePaymentConfiguration: jest
.fn()
.mockImplementation( () => true ),
features: [ 'products' ],
paymentMethodName: 'bacs',
};

it( 'Passes canPayArg to the callback', () => {
canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'woopay'
)( 'canPayArg' );
expect( trueCallback ).toHaveBeenCalledWith( 'canPayArg' );
const canMakePayment = helpers.getCanMakePayment(
args.canMakePaymentConfiguration,
args.features,
args.paymentMethodName
)( canMakePaymentArgument );

// Expect that the result of getCanMakePayment is not the result of
// the payment method's own canMakePayment (args.canMakePaymentConfiguration),
// but of the registered bacsCallback.
expect( canMakePayment ).toBe( bacsCallback() );
} );
} );

it( 'Allows all valid callbacks to run, even if one causes an error', () => {
canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'testpay'
)();
expect( console ).toHaveErrored();
expect( throwsCallback ).toHaveBeenCalledTimes( 1 );
expect( trueCallback ).toHaveBeenCalledTimes( 1 );
describe( 'canMakePaymentWithExtensions', () => {
it( "Returns false without executing the registered callbacks, if the payment method's canMakePayment callback returns false.", () => {
const canMakePayment = () => false;
const canMakePaymentWithExtensionsResult = helpers.canMakePaymentWithExtensions(
canMakePayment,
canMakePaymentExtensionsCallbacks,
'cod'
)( canMakePaymentArgument );
expect( canMakePaymentWithExtensionsResult ).toBe( false );
expect( trueCallback ).not.toHaveBeenCalled();
} );

it( 'Returns early when a registered callback returns false, without executing all the registered callbacks', () => {
helpers.canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'bacs'
)( canMakePaymentArgument );
expect( bacsCallback ).toHaveBeenCalledTimes( 1 );
} );

it( 'Returns true if all extension callbacks return true', () => {
const result = helpers.canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'woopay'
)( canMakePaymentArgument );
expect( result ).toBe( true );
} );

it( 'Passes canPayArg to the callback', () => {
helpers.canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'woopay'
)( canMakePaymentArgument );
expect( trueCallback ).toHaveBeenCalledWith(
canMakePaymentArgument
);
} );

it( 'Allows all valid callbacks to run, even if one causes an error', () => {
helpers.canMakePaymentWithExtensions(
() => true,
canMakePaymentExtensionsCallbacks,
'testpay'
)( canMakePaymentArgument );
expect( console ).toHaveErrored();
expect( throwsCallback ).toHaveBeenCalledTimes( 1 );
expect( trueCallback ).toHaveBeenCalledTimes( 1 );
} );
} );
} );
Loading

0 comments on commit e84577d

Please sign in to comment.