Skip to content

Commit

Permalink
Payment extendable (#2838)
Browse files Browse the repository at this point in the history
* #2817: Add Payment Targets

* #2816: Review changes

* Prettier changes.

Co-authored-by: Devagouda <40405790+dpatil-magento@users.noreply.github.com>
Co-authored-by: Revanth Kumar <revanth0212@gmail.com>
  • Loading branch information
3 people authored Oct 28, 2020
1 parent a196b5e commit 7a60ec0
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import React from 'react';
import createTestInstance from '@magento/peregrine/lib/util/createTestInstance';

import PaymentMethods from '../paymentMethods';
import CreditCard from '../creditCard';

import { usePaymentMethods } from '@magento/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods';

jest.mock('../../../../classify');

jest.mock('../creditCard', () => props => <mock-CreditCard {...props} />);

jest.mock(
'@magento/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods'
);

jest.mock('../paymentMethodCollection', () => ({
braintree: props => <mock-Braintree id={'BraintreeMockId'} {...props} />
}));

const defaultTalonProps = {
availablePaymentMethods: [{ code: 'braintree' }],
currentSelectedPaymentMethod: 'braintree',
Expand Down Expand Up @@ -41,6 +43,7 @@ test('renders null when loading', () => {

expect(tree.toJSON()).toMatchSnapshot();
});

test('should render no method if not selected', () => {
usePaymentMethods.mockReturnValueOnce({
...defaultTalonProps,
Expand All @@ -50,8 +53,8 @@ test('should render no method if not selected', () => {
const tree = createTestInstance(<PaymentMethods {...defaultProps} />);

expect(() => {
tree.root.findByType(CreditCard);
}).toThrow();
tree.root.findByProps({ id: 'BraintreeMockId' });
}).toThrow('No instances found with props: {"id":"BraintreeMockId"}');
});

test('should render CreditCard component if "braintree" is selected', () => {
Expand All @@ -63,6 +66,6 @@ test('should render CreditCard component if "braintree" is selected', () => {
const tree = createTestInstance(<PaymentMethods {...defaultProps} />);

expect(() => {
tree.root.findByType(CreditCard);
tree.root.findByProps({ id: 'BraintreeMockId' });
}).not.toThrow();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* This file is augmented at build time using the @magento/venia-ui build
* target "payments", which allows third-party modules to add new payment component mappings.
*
* @see [Payment definition object]{@link PaymentDefinition}
*/
export default {};

/**
* A payment definition object that describes a payment in your storefront.
*
* @typedef {Object} PaymentDefinition
* @property {string} paymentCode is use to map your payment
* @property {string} importPath Resolvable path to the component the
* Route component will render
*
* @example <caption>A custom payment method</caption>
* const myCustomPayment = {
* paymentCode: 'cc',
* importPath: '@partner/module/path_to_your_component'
* }
*/
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ import { usePaymentMethods } from '@magento/peregrine/lib/talons/CheckoutPage/Pa

import { mergeClasses } from '../../../classify';
import Radio from '../../RadioGroup/radio';
import CreditCard from './creditCard';
import paymentMethodOperations from './paymentMethods.gql';
import defaultClasses from './paymentMethods.css';

const PAYMENT_METHOD_COMPONENTS_BY_CODE = {
braintree: CreditCard
// checkmo: CheckMo,
// etc
};
import payments from './paymentMethodCollection';

const PaymentMethods = props => {
const {
Expand Down Expand Up @@ -44,12 +38,12 @@ const PaymentMethods = props => {

const radios = availablePaymentMethods.map(({ code, title }) => {
// If we don't have an implementation for a method type, ignore it.
if (!Object.keys(PAYMENT_METHOD_COMPONENTS_BY_CODE).includes(code)) {
if (!Object.keys(payments).includes(code)) {
return;
}

const isSelected = currentSelectedPaymentMethod === code;
const PaymentMethodComponent = PAYMENT_METHOD_COMPONENTS_BY_CODE[code];
const PaymentMethodComponent = payments[code];
const renderedComponent = isSelected ? (
<PaymentMethodComponent
onPaymentSuccess={onPaymentSuccess}
Expand Down
29 changes: 29 additions & 0 deletions packages/venia-ui/lib/targets/PaymentMethodList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Implementation of our 'payments' target. This will gather
* PaymentMethod declarations { paymentCode, importPath } from all
* interceptors, and then tap `builtins.transformModules` to inject a module
* transform into the build which is configured to generate an object of modules
* to be imported and then exported.
*
* An instance of this class is made available when you use VeniaUI's
* `payments` target.
*/
class PaymentMethodList {
/** @hideconstructor */
constructor(venia) {
const registry = this;
this._methods = venia.esModuleObject({
module:
'@magento/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethodCollection.js',
publish(targets) {
targets.payments.call(registry);
}
});
}

add({ paymentCode, importPath }) {
this._methods.add(`import ${paymentCode} from '${importPath}'`);
}
}

module.exports = PaymentMethodList;
60 changes: 60 additions & 0 deletions packages/venia-ui/lib/targets/__tests__/venia-ui-targets.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,63 @@ test('uses routes to inject client-routed pages', async () => {
expect(built.bundle).toContain('DynamicCreateAccount');
expect(built.bundle).toContain('DynamicCheckout');
});

test('declares payments target', async () => {
const bus = mockBuildBus({
context: __dirname,
dependencies: [thisDep]
});
bus.runPhase('declare');
const { payments } = bus.getTargetsOf('@magento/venia-ui');

const interceptor = jest.fn();
// no implementation testing in declare phase
payments.tap('test', interceptor);
payments.call('woah');
expect(interceptor).toHaveBeenCalledWith('woah');
});

test('uses RichContentRenderers to default strategy Payment Methode', async () => {
jest.setTimeout(15000);

const built = await buildModuleWith(
'../../components/CheckoutPage/PaymentInformation/paymentMethodCollection.js',
{
context: __dirname,
dependencies: ['@magento/peregrine', thisDep]
}
);

const payments = built.run();
expect(payments).toHaveProperty('braintree');
});

test('declares payments target', async () => {
const bus = mockBuildBus({
context: __dirname,
dependencies: [thisDep]
});
bus.runPhase('declare');
const { payments } = bus.getTargetsOf('@magento/venia-ui');

const interceptor = jest.fn();
// no implementation testing in declare phase
payments.tap('test', interceptor);
payments.call('woah');
expect(interceptor).toHaveBeenCalledWith('woah');
});

test('uses RichContentRenderers to default strategy Payment Methode', async () => {
jest.setTimeout(15000);

const built = await buildModuleWith(
'../../components/CheckoutPage/PaymentInformation/paymentMethodCollection.js',
{
context: __dirname,
dependencies: ['@magento/peregrine', thisDep]
}
);

const payments = built.run();
expect(payments).toHaveProperty('braintree');
});
81 changes: 80 additions & 1 deletion packages/venia-ui/lib/targets/venia-ui-declare.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,28 @@ module.exports = targets => {
* return routesArray;
* })
*/
routes: new targets.types.AsyncSeriesWaterfall(['routes'])
routes: new targets.types.AsyncSeriesWaterfall(['routes']),

/**
* Provides access to Venia's payment methods
*
* This target lets you add new payment to your storefronts.
*
* @member {tapable.SyncHook}
*
* @see [Intercept function signature]{@link paymentInterceptFunction}
* @see [PaymentMethodList]{@link #PaymentMethodList}
* @see [Payment definition object]{@link PaymentDefinition}
*
* @example <caption>Add a payment</caption>
* targets.of('@magento/venia-ui').payments.tap(
* payments => payments.add({
* paymentCode: 'braintree',
* importPath: '@magento/braintree_payment'
* })
* );
*/
payments: new targets.types.Sync(['payments'])
});
};

Expand Down Expand Up @@ -159,3 +180,61 @@ module.exports = targets => {
* path: '@my-components/my-route-component'
* }
*/

/** Type definition related to: payments */

/**
* Intercept function signature for the `payments` target.
*
* Interceptors of `payments` should call `.add` on the provided [payment list]{@link #PaymentMethodList}.
*
* @callback paymentInterceptFunction
*
* @param {PaymentMethodList} renderers The list of payments registered
* so far in the build.
*
*/

/**
* A payment definition object that describes a payment in your storefront.
*
* @typedef {Object} PaymentDefinition
* @property {string} paymentCode is use to map your payment
* @property {string} importPath Resolvable path to the component the
* Route component will render
*
* @example <caption>A custom payment method</caption>
* const myCustomPayment = {
* paymentCode: 'cc',
* importPath: '@partner/module/path_to_your_component'
* }
*/

/** Type definition related to: payments */

/**
* Intercept function signature for the `payments` target.
*
* Interceptors of `payments` should call `.add` on the provided [payment list]{@link #PaymentMethodList}.
*
* @callback paymentInterceptFunction
*
* @param {PaymentMethodList} renderers The list of payments registered
* so far in the build.
*
*/

/**
* A payment definition object that describes a payment in your storefront.
*
* @typedef {Object} PaymentDefinition
* @property {string} paymentCode is use to map your payment
* @property {string} importPath Resolvable path to the component the
* Route component will render
*
* @example <caption>A custom payment method</caption>
* const myCustomPayment = {
* paymentCode: 'cc',
* importPath: '@partner/module/path_to_your_component'
* }
*/
8 changes: 8 additions & 0 deletions packages/venia-ui/lib/targets/venia-ui-intercept.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const { Targetables } = require('@magento/pwa-buildpack');
const RichContentRendererList = require('./RichContentRendererList');
const makeRoutesTarget = require('./makeRoutesTarget');
const PaymentMethodList = require('./PaymentMethodList');

module.exports = veniaTargets => {
const venia = Targetables.using(veniaTargets);
Expand All @@ -25,4 +26,11 @@ module.exports = veniaTargets => {
componentName: 'PlainHtmlRenderer',
importPath: './plainHtmlRenderer'
});

const paymentMethodList = new PaymentMethodList(venia);
paymentMethodList.add({
paymentCode: 'braintree',
importPath:
'@magento/venia-ui/lib/components/CheckoutPage/PaymentInformation/creditCard'
});
};

0 comments on commit 7a60ec0

Please sign in to comment.