Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payment extendable #2838

Merged
merged 8 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
* }
*/
7 changes: 7 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,10 @@ module.exports = veniaTargets => {
componentName: 'PlainHtmlRenderer',
importPath: './plainHtmlRenderer'
});

const paymentMethodList = new PaymentMethodList(venia);
paymentMethodList.add({
paymentCode: 'braintree',
importPath: './creditCard'
larsroettig marked this conversation as resolved.
Show resolved Hide resolved
});
};