Skip to content

Commit

Permalink
feat(payment): INT-1997 Add BlueSnap V2 payment method
Browse files Browse the repository at this point in the history
  • Loading branch information
mauricio-sg committed Feb 24, 2020
1 parent ac58d35 commit 1c7884e
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
152 changes: 152 additions & 0 deletions src/app/payment/paymentMethod/BlueSnapV2PaymentMethod.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { createCheckoutService, CheckoutSelectors, CheckoutService } from '@bigcommerce/checkout-sdk';
import { mount, ReactWrapper } from 'enzyme';
import { Formik } from 'formik';
import { noop } from 'lodash';
import React, { FunctionComponent } from 'react';
import { act } from 'react-dom/test-utils';

import { getCart } from '../../cart/carts.mock';
import { CheckoutProvider } from '../../checkout';
import { getStoreConfig } from '../../config/config.mock';
import { getCustomer } from '../../customer/customers.mock';
import { createLocaleContext, LocaleContext, LocaleContextType } from '../../locale';
import { Modal, ModalProps } from '../../ui/modal';
import { getPaymentMethod } from '../payment-methods.mock';
import PaymentContext, { PaymentContextProps } from '../PaymentContext';

import BlueSnapV2PaymentMethod, { BlueSnapV2PaymentMethodProps } from './BlueSnapV2PaymentMethod';
import HostedPaymentMethod from './HostedPaymentMethod';
import PaymentMethodId from './PaymentMethodId';

describe('when using BlueSnapV2 payment', () => {
let defaultProps: BlueSnapV2PaymentMethodProps;
let checkoutService: CheckoutService;
let checkoutState: CheckoutSelectors;
let localeContext: LocaleContextType;
let paymentContext: PaymentContextProps;
let BlueSnapV2PaymentMethodTest: FunctionComponent;

beforeEach(() => {
defaultProps = {
initializePayment: jest.fn(),
deinitializePayment: jest.fn(),
method: {
...getPaymentMethod(),
id: 'mastercard',
gateway: PaymentMethodId.BlueSnapV2,
},
};

checkoutService = createCheckoutService();
checkoutState = checkoutService.getState();
localeContext = createLocaleContext(getStoreConfig());

paymentContext = {
disableSubmit: jest.fn(),
setSubmit: jest.fn(),
setValidationSchema: jest.fn(),
};

jest.spyOn(checkoutState.data, 'getCart')
.mockReturnValue(getCart());

jest.spyOn(checkoutState.data, 'getConfig')
.mockReturnValue(getStoreConfig());

jest.spyOn(checkoutState.data, 'getCustomer')
.mockReturnValue(getCustomer());

BlueSnapV2PaymentMethodTest = props => (
<CheckoutProvider checkoutService={ checkoutService }>
<PaymentContext.Provider value={ paymentContext }>
<LocaleContext.Provider value={ localeContext }>
<Formik
initialValues={ {} }
onSubmit={ noop }
>
<BlueSnapV2PaymentMethod { ...defaultProps } { ...props } />
</Formik>
</LocaleContext.Provider>
</PaymentContext.Provider>
</CheckoutProvider>
);
});

it('renders as hosted payment method', () => {
const container = mount(<BlueSnapV2PaymentMethodTest />);

expect(container.find(HostedPaymentMethod).length)
.toEqual(1);
});

it('initializes method with required config', () => {
mount(<BlueSnapV2PaymentMethodTest />);

expect(defaultProps.initializePayment)
.toHaveBeenCalledWith(expect.objectContaining({
methodId: 'mastercard',
gatewayId: 'bluesnapv2',
bluesnapv2: {
onLoad: expect.any(Function),
style: {
border: expect.any(String),
height: expect.any(String),
width: expect.any(String),
},
},
}));
});

it('renders modal that hosts bluesnap payment page', async () => {
const component = mount(<BlueSnapV2PaymentMethodTest />);
const initializeOptions = (defaultProps.initializePayment as jest.Mock).mock.calls[0][0];

act(() => {
initializeOptions.bluesnapv2.onLoad(
document.createElement('iframe'),
jest.fn()
);
});

await new Promise(resolve => process.nextTick(resolve));

act(() => {
component.update();
});

expect(component.find(Modal).prop('isOpen'))
.toEqual(true);

expect(component.find(Modal).render().find('iframe'))
.toHaveLength(1);
});

it('cancels payment flow if user chooses to close modal', async () => {
const cancelBlueSnapV2Payment = jest.fn();
const component = mount(<BlueSnapV2PaymentMethodTest />);
const initializeOptions = (defaultProps.initializePayment as jest.Mock).mock.calls[0][0];

act(() => {
initializeOptions.bluesnapv2.onLoad(
document.createElement('iframe'),
cancelBlueSnapV2Payment
);
});

await new Promise(resolve => process.nextTick(resolve));

act(() => {
component.update();
});

const modal: ReactWrapper<ModalProps> = component.find(Modal);

act(() => {
// tslint:disable-next-line:no-non-null-assertion
modal.prop('onRequestClose')!(new MouseEvent('click') as any);
});

expect(cancelBlueSnapV2Payment)
.toHaveBeenCalled();
});
});
74 changes: 74 additions & 0 deletions src/app/payment/paymentMethod/BlueSnapV2PaymentMethod.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk';
import React, { createRef, useCallback, useRef, useState, FunctionComponent, RefObject } from 'react';

import { Modal } from '../../ui/modal';

import HostedPaymentMethod, { HostedPaymentMethodProps } from './HostedPaymentMethod';

export type BlueSnapV2PaymentMethodProps = HostedPaymentMethodProps;

interface BlueSnapV2PaymentMethodRef {
paymentPageContentRef: RefObject<HTMLDivElement>;
cancelBlueSnapV2Payment?(): void;
}

const BlueSnapV2PaymentMethod: FunctionComponent<BlueSnapV2PaymentMethodProps> = ({
initializePayment,
...rest
}) => {
const [paymentPageContent, setPaymentPageContent] = useState<HTMLElement>();
const ref = useRef<BlueSnapV2PaymentMethodRef>({
paymentPageContentRef: createRef(),
});

const cancelBlueSnapV2ModalFlow = useCallback(() => {
setPaymentPageContent(undefined);

if (ref.current.cancelBlueSnapV2Payment) {
ref.current.cancelBlueSnapV2Payment();
ref.current.cancelBlueSnapV2Payment = undefined;
}
}, []);

const initializeBlueSnapV2Payment = useCallback((options: PaymentInitializeOptions) => {
return initializePayment({
...options,
bluesnapv2: {
onLoad(content: HTMLIFrameElement, cancel: () => void) {
setPaymentPageContent(content);
ref.current.cancelBlueSnapV2Payment = cancel;
},
style: {
border: '1px solid lightgray',
height: '60vh',
width: '100%',
},
},
});
}, [initializePayment]);

const appendPaymentPageContent = useCallback(() => {
if (ref.current.paymentPageContentRef.current && paymentPageContent) {
ref.current.paymentPageContentRef.current.appendChild(paymentPageContent);
}
}, [paymentPageContent]);

return (
<>
<HostedPaymentMethod
{ ...rest }
initializePayment={ initializeBlueSnapV2Payment }
/>
<Modal
isOpen={ !!paymentPageContent }
onAfterOpen={ appendPaymentPageContent }
onRequestClose={ cancelBlueSnapV2ModalFlow }
shouldShowCloseButton={ true }
>
<div ref={ ref.current.paymentPageContentRef } />
</Modal>
</>
);
};

export default BlueSnapV2PaymentMethod;
5 changes: 5 additions & 0 deletions src/app/payment/paymentMethod/PaymentMethod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AdyenV2PaymentMethod from './AdyenV2PaymentMethod';
import AffirmPaymentMethod from './AffirmPaymentMethod';
import AmazonPaymentMethod from './AmazonPaymentMethod';
import BarclaycardPaymentMethod from './BarclaycardPaymentMethod';
import BlueSnapV2PaymentMethod from './BlueSnapV2PaymentMethod';
import BraintreeCreditCardPaymentMethod from './BraintreeCreditCardPaymentMethod';
import ChasePayPaymentMethod from './ChasePayPaymentMethod';
import CreditCardPaymentMethod from './CreditCardPaymentMethod';
Expand Down Expand Up @@ -73,6 +74,10 @@ const PaymentMethodComponent: FunctionComponent<PaymentMethodProps & WithCheckou
return <AffirmPaymentMethod { ...props } />;
}

if (method.gateway === PaymentMethodId.BlueSnapV2) {
return <BlueSnapV2PaymentMethod { ...props } />;
}

if (method.id === PaymentMethodId.Klarna) {
return <KlarnaPaymentMethod { ...props } />;
}
Expand Down
1 change: 1 addition & 0 deletions src/app/payment/paymentMethod/PaymentMethodId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum PaymentMethodId {
Afterpay = 'afterpay',
Amazon = 'amazon',
Barclaycard = 'barclaycard',
BlueSnapV2 = 'bluesnapv2',
Braintree = 'braintree',
BraintreeGooglePay = 'googlepaybraintree',
BraintreeVisaCheckout = 'braintreevisacheckout',
Expand Down

0 comments on commit 1c7884e

Please sign in to comment.