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

WIP: Handling multiple instances of the Klarna Widget #2996

Closed
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -58,10 +58,19 @@ export class DropinComponent extends Component<DropinComponentProps, DropinCompo
};

private setActivePaymentMethod = paymentMethod => {
// console.log('\n### DropinComponent::setActivePaymentMethod:: cachedPaymentMethods', this.state.cachedPaymentMethods);
this.setState(prevState => ({
activePaymentMethod: paymentMethod,
cachedPaymentMethods: { ...prevState.cachedPaymentMethods, [paymentMethod._id]: true }
}));

for (const [key] of Object.entries(this.state.cachedPaymentMethods)) {
if (key === paymentMethod._id) {
// console.log('### DropinComponent::setActivePaymentMethod:: payment method is cached=', key);
paymentMethod.onPaymentMethodActive();
break;
}
}
};

componentDidUpdate(prevProps, prevState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ class PaymentMethodItem extends Component<PaymentMethodItemProps> {
const showBrands = !paymentMethod.props.oneClick && paymentMethod.brands && paymentMethod.brands.length > 0;

return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
<div key={paymentMethod._id} className={paymentMethodClassnames} onClick={this.handleOnListItemClick}>
<div className="adyen-checkout__payment-method__header">
<div key={paymentMethod._id} className={paymentMethodClassnames}>
{/*eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions*/}
<div className="adyen-checkout__payment-method__header" onClick={this.handleOnListItemClick}>
<ExpandButton buttonId={buttonId} showRadioButton={showRadioButton} isSelected={isSelected} expandContentId={containerId}>
<PaymentMethodIcon
// Only add alt attribute to storedPaymentMethods (to avoid SR reading the PM name twice)
Expand Down
9 changes: 3 additions & 6 deletions packages/lib/src/components/Klarna/KlarnaPayments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,15 @@ describe('KlarnaPayments', () => {
expect(screen.queryByRole('button', { name: 'Continue to Pay By Bank' })).toBeFalsy();
});

test('should call setStatus if elementRef is a drop-in', async () => {
test('should call setStatus if elementRef is a drop-in', () => {
const KlarnaPaymentsEle = new KlarnaPayments(global.core, {
...coreProps,
...{ paymentData: '', paymentMethodType: '', sdkData: undefined, useKlarnaWidget: false, showPayButton: false }
});
KlarnaPaymentsEle.elementRef = new Dropin(global.core);
render(KlarnaPaymentsEle.render());
const spy = jest.spyOn(KlarnaPaymentsEle.elementRef, 'setStatus');
// @ts-ignore to test
await waitFor(() => KlarnaPaymentsEle.componentRef);
// @ts-ignore to test
KlarnaPaymentsEle.componentRef.props.onLoaded();
KlarnaPaymentsEle.onLoaded();
expect(spy).toHaveBeenCalled();
});

Expand All @@ -55,7 +52,7 @@ describe('KlarnaPayments', () => {
// @ts-ignore to test
await waitFor(() => KlarnaPaymentsEle.componentRef);
// @ts-ignore to test
KlarnaPaymentsEle.componentRef.props.onComplete();
KlarnaPaymentsEle.componentRef.onComplete();
expect(onAdditionalDetailsMock).toHaveBeenCalled();
});
});
9 changes: 6 additions & 3 deletions packages/lib/src/components/Klarna/KlarnaPayments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ class KlarnaPayments extends UIElement<KlarnConfiguration> {
this.setElementStatus('ready');
}

protected onPaymentMethodActive() {
// Reinit the KlarnaSDK widget
this.componentRef?.initWidget();
}

render() {
return (
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext} resources={this.resources}>
<KlarnaContainer
{...this.props}
ref={ref => {
this.componentRef = ref;
}}
setComponentRef={this.setComponentRef}
displayName={this.displayName}
onComplete={state => this.handleAdditionalDetails(state)}
onError={this.props.onError}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { h } from 'preact';
import { KlarnaWidget } from '../KlarnaWidget/KlarnaWidget';
import { useState } from 'preact/hooks';
import { useState, useRef } from 'preact/hooks';
import { KlarnaAction, KlarnaContainerRef } from '../../types';

export function KlarnaContainer(props) {
const [action, setAction] = useState({
const [action, setAction] = useState<KlarnaAction>({
sdkData: props.sdkData,
paymentMethodType: props.paymentMethodType,
paymentData: props.paymentData
});
const [status, setStatus] = useState('ready');

this.setAction = setAction;
this.setStatus = setStatus;
const klarnaContainerRef = useRef<KlarnaContainerRef>({});
// Just call once to create the object by which we expose the members expected by the parent comp
if (!Object.keys(klarnaContainerRef.current).length) {
props.setComponentRef(klarnaContainerRef.current);
}

klarnaContainerRef.current.setAction = setAction;
klarnaContainerRef.current.setStatus = setStatus;
klarnaContainerRef.current.onComplete = props.onComplete; // Needed for unit tests

if (action.sdkData) {
return (
<KlarnaWidget
containerRef={klarnaContainerRef.current}
sdkData={action.sdkData}
paymentMethodType={action.paymentMethodType}
paymentData={action.paymentData}
Expand All @@ -36,7 +45,7 @@ export function KlarnaContainer(props) {
status,
disabled: status === 'loading',
classNameModifiers: ['standalone'],
label: `${this.props.i18n.get('continueTo')} ${props.displayName}`
label: `${props.i18n.get('continueTo')} ${props.displayName}`
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/preact';
import { KlarnaWidget } from './KlarnaWidget';
import Script from '../../../../utils/Script';
import { KLARNA_WIDGET_URL } from '../../constants';
import { KlarnaWidgetAuthorizeResponse } from '../../types';
import { KlarnaWidgetAuthorizeResponse, KlarnaWidgetProps } from '../../types';

jest.mock('../../../../utils/Script', () => {
return jest.fn().mockImplementation(() => {
Expand Down Expand Up @@ -34,14 +34,15 @@ describe('KlarnaWidget', () => {
Pay with Klarna
</button>
);
const props = {
const props: KlarnaWidgetProps = {
onLoaded,
onComplete,
onError,
paymentData,
paymentMethodType,
sdkData,
payButton
payButton,
containerRef: null
};

beforeAll(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Script from '../../../../utils/Script';
import { useEffect, useRef, useState } from 'preact/hooks';
import { h } from 'preact';
import { KlarnaWidgetAuthorizeResponse, KlarnaWidgetProps } from '../../types';
import { KlarnaContainerRef, KlarnaWidgetAuthorizeResponse, KlarnaWidgetProps } from '../../types';
import { KLARNA_WIDGET_URL } from '../../constants';
import './KlarnaWidget.scss';

Expand All @@ -20,6 +20,7 @@ export function KlarnaWidget({ sdkData, paymentMethodType, payButton, ...props }
};

const initializeKlarnaWidget = () => {
console.log('### KlarnaWidget::initializeKlarnaWidget:: ');
window.Klarna.Payments.init({
client_token: sdkData.client_token
});
Expand All @@ -43,9 +44,14 @@ export function KlarnaWidget({ sdkData, paymentMethodType, payButton, ...props }
);
};

if (props.containerRef) {
(props.containerRef as KlarnaContainerRef).initWidget = initializeKlarnaWidget;
}

const authorizeKlarna = () => {
setStatus('loading');
try {
console.log('### KlarnaWidget::authorizeKlarna:: sdkData.payment_method_category', sdkData.payment_method_category);
window.Klarna.Payments.authorize(
{
payment_method_category: sdkData.payment_method_category
Expand Down Expand Up @@ -80,14 +86,16 @@ export function KlarnaWidget({ sdkData, paymentMethodType, payButton, ...props }

// Add Klarna Payments Widget SDK
useEffect(() => {
window.klarnaAsyncCallback = function () {
initializeKlarnaWidget();
};
console.log('\n### KlarnaWidget:::: useEffect sdkData.payment_method_category=', sdkData.payment_method_category);

const script = new Script(KLARNA_WIDGET_URL);
void script.load();
void script.load()?.then(() => {
console.log('### KlarnaWidget:::: useEffect script: LOADED ');
initializeKlarnaWidget();
});

return () => {
console.log('### KlarnaWidget:::: remove');
script.remove();
};
}, []);
Expand Down
18 changes: 17 additions & 1 deletion packages/lib/src/components/Klarna/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UIElementProps } from '../internal/UIElement/types';
import { ComponentMethodsRef, UIElementProps } from '../internal/UIElement/types';

declare global {
interface Window {
Expand Down Expand Up @@ -37,6 +37,7 @@ export interface KlarnaWidgetProps extends KlarnaPaymentsShared {

onComplete: (detailsData) => void;
onError: (error) => void;
containerRef: any;
}

export type KlarnConfiguration = UIElementProps &
Expand All @@ -50,3 +51,18 @@ export interface KlarnaWidgetAuthorizeResponse {
authorization_token: string;
error?: any;
}

export interface KlarnaAction {
sdkData: {
client_token: string;
payment_method_category: string;
};
paymentMethodType: string;
paymentData: string;
}

export interface KlarnaContainerRef extends ComponentMethodsRef {
initWidget?: () => void;
setAction?(action: KlarnaAction): void;
onComplete?: (state: any) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
return this;
}

protected onPaymentMethodActive() {
return null;
}

protected onChange(): void {
this.props.onChange?.(
{
Expand Down
Loading