Skip to content

Commit f9a9fc0

Browse files
authoredMar 26, 2024
feat(payment): PI-1793 [Klarna] Refactor the existing Klarna compon… (bigcommerce#1762)
* feat(payment): PI-1793 [Klarna] Refactor the existing `Klarna` components in Checkout JS to use the new checkout payment integration JS API * feat(payment): PI-1793 [Klarna] Refactor the existing `Klarna` components in Checkout JS to use the new checkout payment integration JS API * feat(payment): PI-1793 [Klarna] Refactor the existing `Klarna` components in Checkout JS to use the new checkout payment integration JS API
1 parent 21475c5 commit f9a9fc0

File tree

8 files changed

+236
-142
lines changed

8 files changed

+236
-142
lines changed
 

‎package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"prettier": "@bigcommerce/eslint-config/prettier",
4141
"homepage": "https://github.com/bigcommerce/checkout-js#readme",
4242
"dependencies": {
43-
"@bigcommerce/checkout-sdk": "^1.567.0",
43+
"@bigcommerce/checkout-sdk": "^1.568.0",
4444
"@bigcommerce/citadel": "^2.15.1",
4545
"@bigcommerce/form-poster": "^1.2.2",
4646
"@bigcommerce/memoize": "^1.0.0",

‎packages/core/src/app/payment/paymentMethod/KlarnaPaymentMethod.spec.tsx

-95
This file was deleted.

‎packages/core/src/app/payment/paymentMethod/KlarnaPaymentMethod.tsx

-34
This file was deleted.

‎packages/core/src/app/payment/paymentMethod/PaymentMethod.tsx

-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import DigitalRiverPaymentMethod from './DigitalRiverPaymentMethod';
2323
import GooglePayPaymentMethod from './GooglePayPaymentMethod';
2424
import HostedCreditCardPaymentMethod from './HostedCreditCardPaymentMethod';
2525
import HostedPaymentMethod from './HostedPaymentMethod';
26-
import KlarnaPaymentMethod from './KlarnaPaymentMethod';
2726
import MasterpassPaymentMethod from './MasterpassPaymentMethod';
2827
import MolliePaymentMethod from './MolliePaymentMethod';
2928
import MonerisPaymentMethod from './MonerisPaymentMethod';
@@ -97,10 +96,6 @@ const PaymentMethodComponent: FunctionComponent<
9796
return <DigitalRiverPaymentMethod {...props} />;
9897
}
9998

100-
if (method.id === PaymentMethodId.Klarna && method.gateway !== PaymentMethodId.Mollie) {
101-
return <KlarnaPaymentMethod {...props} />;
102-
}
103-
10499
if (method.id === PaymentMethodId.CCAvenueMars) {
105100
return <CCAvenueMarsPaymentMethod {...props} />;
106101
}
+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { default as KlarnaPaymentMethod } from './klarna/KlarnaPaymentMethod';
12
export { default as KlarnaV2PaymentMethod } from './klarnav2/KlarnaV2PaymentMethod';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import {
2+
CheckoutSelectors,
3+
CheckoutService,
4+
createCheckoutService,
5+
createLanguageService,
6+
PaymentMethod,
7+
} from '@bigcommerce/checkout-sdk';
8+
import { mount } from 'enzyme';
9+
import { Formik } from 'formik';
10+
import { noop } from 'lodash';
11+
import React, { FunctionComponent } from 'react';
12+
13+
import { HostedWidgetPaymentComponent } from '@bigcommerce/checkout/hosted-widget-integration';
14+
import {
15+
createLocaleContext,
16+
LocaleContext,
17+
LocaleContextType,
18+
} from '@bigcommerce/checkout/locale';
19+
import {
20+
CheckoutProvider,
21+
PaymentMethodId,
22+
PaymentMethodProps,
23+
} from '@bigcommerce/checkout/payment-integration-api';
24+
import {
25+
getCheckout,
26+
getCustomer,
27+
getPaymentFormServiceMock,
28+
getPaymentMethod,
29+
getStoreConfig,
30+
} from '@bigcommerce/checkout/test-mocks';
31+
32+
import KlarnaPaymentMethod from './KlarnaPaymentMethod';
33+
34+
describe('when using Klarna payment', () => {
35+
let method: PaymentMethod;
36+
let checkoutService: CheckoutService;
37+
let checkoutState: CheckoutSelectors;
38+
let defaultProps: PaymentMethodProps;
39+
let localeContext: LocaleContextType;
40+
41+
let PaymentMethodTest: FunctionComponent<PaymentMethodProps>;
42+
43+
beforeEach(() => {
44+
checkoutService = createCheckoutService();
45+
checkoutState = checkoutService.getState();
46+
localeContext = createLocaleContext(getStoreConfig());
47+
method = { ...getPaymentMethod(), id: PaymentMethodId.Klarna };
48+
49+
jest.spyOn(checkoutState.data, 'getConfig').mockReturnValue(getStoreConfig());
50+
51+
jest.spyOn(checkoutService, 'deinitializePayment').mockResolvedValue(checkoutState);
52+
53+
jest.spyOn(checkoutService, 'initializePayment').mockResolvedValue(checkoutState);
54+
55+
jest.spyOn(checkoutState.data, 'getCheckout').mockReturnValue(getCheckout());
56+
57+
jest.spyOn(checkoutState.data, 'isPaymentDataRequired').mockReturnValue(true);
58+
59+
defaultProps = {
60+
method,
61+
checkoutService,
62+
checkoutState,
63+
paymentForm: getPaymentFormServiceMock(),
64+
language: createLanguageService(),
65+
onUnhandledError: jest.fn(),
66+
};
67+
68+
PaymentMethodTest = (props) => (
69+
<CheckoutProvider checkoutService={checkoutService}>
70+
<LocaleContext.Provider value={localeContext}>
71+
<Formik initialValues={{}} onSubmit={noop}>
72+
<KlarnaPaymentMethod {...props} />
73+
</Formik>
74+
</LocaleContext.Provider>
75+
</CheckoutProvider>
76+
);
77+
});
78+
79+
afterEach(() => {
80+
jest.clearAllMocks();
81+
});
82+
83+
it('renders as hosted widget component', () => {
84+
const container = mount(<PaymentMethodTest {...defaultProps} method={method} />);
85+
const component = container.find(HostedWidgetPaymentComponent);
86+
87+
expect(component.props()).toEqual(
88+
expect.objectContaining({
89+
containerId: `${method.id}Widget`,
90+
deinitializePayment: expect.any(Function),
91+
initializePayment: expect.any(Function),
92+
method,
93+
}),
94+
);
95+
});
96+
97+
it('initializes method with required config when no instruments', () => {
98+
jest.spyOn(checkoutState.data, 'getInstruments').mockReturnValue(undefined);
99+
100+
const container = mount(<PaymentMethodTest {...defaultProps} method={method} />);
101+
const component = container.find(HostedWidgetPaymentComponent);
102+
103+
expect(component.props()).toEqual(
104+
expect.objectContaining({
105+
containerId: `${method.id}Widget`,
106+
deinitializePayment: expect.any(Function),
107+
initializePayment: expect.any(Function),
108+
method,
109+
}),
110+
);
111+
});
112+
113+
it('initializes method with required config when customer is defined', () => {
114+
jest.spyOn(checkoutState.data, 'getCustomer').mockReturnValue(getCustomer());
115+
116+
const container = mount(<PaymentMethodTest {...defaultProps} method={method} />);
117+
const component = container.find(HostedWidgetPaymentComponent);
118+
119+
expect(component.props()).toEqual(
120+
expect.objectContaining({
121+
containerId: `${method.id}Widget`,
122+
deinitializePayment: expect.any(Function),
123+
initializePayment: expect.any(Function),
124+
method,
125+
}),
126+
);
127+
});
128+
129+
it('initializes method with required config', () => {
130+
mount(<PaymentMethodTest {...defaultProps} method={method} />);
131+
132+
expect(checkoutService.initializePayment).toHaveBeenCalledWith(
133+
expect.objectContaining({
134+
methodId: method.id,
135+
gatewayId: method.gateway,
136+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
137+
[`${method.id}`]: {
138+
container: `#${method.id}Widget`,
139+
},
140+
}),
141+
);
142+
});
143+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { some } from 'lodash';
2+
import React, { FunctionComponent, useCallback } from 'react';
3+
4+
import { HostedWidgetPaymentComponent } from '@bigcommerce/checkout/hosted-widget-integration';
5+
import {
6+
isInstrumentCardCodeRequiredSelector,
7+
isInstrumentCardNumberRequiredSelector,
8+
} from '@bigcommerce/checkout/instrument-utils';
9+
import {
10+
PaymentMethodProps,
11+
PaymentMethodResolveId,
12+
toResolvableComponent,
13+
} from '@bigcommerce/checkout/payment-integration-api';
14+
15+
const KlarnaPaymentMethod: FunctionComponent<PaymentMethodProps> = ({
16+
checkoutService,
17+
checkoutState,
18+
method,
19+
paymentForm,
20+
...rest
21+
}) => {
22+
const initializeKlarnaPayment = useCallback(
23+
(options) =>
24+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
25+
checkoutService.initializePayment({
26+
...options,
27+
klarna: {
28+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
29+
container: `#${options.methodId}Widget`,
30+
},
31+
}),
32+
[checkoutService],
33+
);
34+
35+
const {
36+
hidePaymentSubmitButton,
37+
disableSubmit,
38+
setFieldValue,
39+
setSubmit,
40+
setValidationSchema,
41+
} = paymentForm;
42+
43+
const instruments = checkoutState.data.getInstruments(method) || [];
44+
45+
const {
46+
data: { getCheckout, isPaymentDataRequired },
47+
statuses: { isLoadingInstruments },
48+
} = checkoutState;
49+
50+
const checkout = getCheckout();
51+
const customer = checkoutState.data.getCustomer();
52+
const isGuestCustomer = customer?.isGuest;
53+
const isInstrumentFeatureAvailable =
54+
!isGuestCustomer && Boolean(method.config.isVaultingEnabled);
55+
56+
return (
57+
<HostedWidgetPaymentComponent
58+
containerId={`${method.id}Widget`}
59+
deinitializePayment={checkoutService.deinitializePayment}
60+
disableSubmit={disableSubmit}
61+
hidePaymentSubmitButton={hidePaymentSubmitButton}
62+
initializePayment={initializeKlarnaPayment}
63+
instruments={instruments}
64+
isInstrumentCardCodeRequired={isInstrumentCardCodeRequiredSelector(checkoutState)}
65+
isInstrumentCardNumberRequired={isInstrumentCardNumberRequiredSelector(checkoutState)}
66+
isInstrumentFeatureAvailable={isInstrumentFeatureAvailable}
67+
isLoadingInstruments={isLoadingInstruments()}
68+
isPaymentDataRequired={isPaymentDataRequired()}
69+
isSignedIn={some(checkout?.payments, { providerId: method.id })}
70+
loadInstruments={checkoutService.loadInstruments}
71+
method={method}
72+
setFieldValue={setFieldValue}
73+
setSubmit={setSubmit}
74+
setValidationSchema={setValidationSchema}
75+
signOut={checkoutService.signOutCustomer}
76+
{...rest}
77+
/>
78+
);
79+
};
80+
81+
export default toResolvableComponent<PaymentMethodProps, PaymentMethodResolveId>(
82+
KlarnaPaymentMethod,
83+
[{ id: 'klarna' }],
84+
);

0 commit comments

Comments
 (0)
Please sign in to comment.