Skip to content

Commit

Permalink
Merge pull request #62 from xsolla/PAYMENTS-17869
Browse files Browse the repository at this point in the history
feat(PAYMENTS-17869): QR code component
  • Loading branch information
ekireevxs authored Mar 5, 2024
2 parents dfd1c70 + b52e0b2 commit ef7762e
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 16 deletions.
4 changes: 3 additions & 1 deletion src/core/actions/next-action.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ShowFieldsAction } from './show-fields.action.type';
import { StatusUpdatedAction } from './status-updated.action.type';
import { ThreeDsAction } from './three-ds/three-ds.action.type';
import { SpecialButtonAction } from './special-button.action.type';
import { ShowQrCodeAction } from './show-qr-code.action.type';

export type NextAction =
| CheckStatusAction
Expand All @@ -13,4 +14,5 @@ export type NextAction =
| ShowErrorsAction
| RedirectAction
| ThreeDsAction
| SpecialButtonAction;
| SpecialButtonAction
| ShowQrCodeAction;
5 changes: 5 additions & 0 deletions src/core/actions/show-qr-code.action.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Action } from './action.interface';

export type ShowQrCodeActionType = 'show_qr_code';

export type ShowQrCodeAction = Action<ShowQrCodeActionType, null>;
1 change: 1 addition & 0 deletions src/core/web-components/web-component-tag-name.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export enum WebComponentTagName {
PaymentFormMessages = 'psdk-payment-form-messages',
GooglePayButtonComponent = 'psdk-google-pay-button',
ApplePayComponent = 'psdk-apple-pay',
QrCodeComponent = 'psdk-qr-code',
}
2 changes: 2 additions & 0 deletions src/core/web-components/web-components.map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SavedMethodsComponent } from '../../features/headless-checkout/web-comp
import { UserBalanceComponent } from '../../features/headless-checkout/web-components/user-balance-component/user-balance-component';
import { PaymentFormMessagesComponent } from '../../features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component';
import { GooglePayButtonComponent } from '../../features/headless-checkout/web-components/pages/google-pay/google-pay-button.component';
import { QrCodeComponent } from '../../features/headless-checkout/web-components/qr-code/qr-code.component';
import { ApplePayComponent } from '../../features/headless-checkout/web-components/apple-pay/apple-pay.component';

export const webComponents: {
Expand All @@ -38,5 +39,6 @@ export const webComponents: {
[WebComponentTagName.UserBalanceComponent]: UserBalanceComponent,
[WebComponentTagName.PaymentFormMessages]: PaymentFormMessagesComponent,
[WebComponentTagName.GooglePayButtonComponent]: GooglePayButtonComponent,
[WebComponentTagName.QrCodeComponent]: QrCodeComponent,
[WebComponentTagName.ApplePayComponent]: ApplePayComponent,
};
34 changes: 19 additions & 15 deletions src/features/headless-checkout/headless-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class HeadlessCheckout {
onCoreEvent: <T>(
eventName: EventName,
handler: Handler<T>,
callback: (value?: T) => void
callback: (value?: T) => void,
): (() => void) => {
return this.postMessagesClient.listen(eventName, handler, callback);
},
Expand Down Expand Up @@ -84,7 +84,7 @@ export class HeadlessCheckout {
}
this.formSpy.formWasInit = true;
this.formStatus = FormStatus.active;
})
}),
) as Promise<Form>;
},

Expand All @@ -96,12 +96,12 @@ export class HeadlessCheckout {
if (nextAction) {
callbackFn(nextAction);
}
}
},
);
},

onFieldsStatusChange: (
callbackFn: (fieldsStatus: FormFieldsStatus) => void
callbackFn: (fieldsStatus: FormFieldsStatus) => void,
): void => {
this.postMessagesClient.listen<FormFieldsStatus>(
EventName.formFieldsStatusChanged,
Expand All @@ -110,7 +110,7 @@ export class HeadlessCheckout {
if (fieldsStatus) {
callbackFn(fieldsStatus);
}
}
},
);
},

Expand All @@ -132,6 +132,7 @@ export class HeadlessCheckout {
private formStatus: FormStatus = FormStatus.undefined;
private isWebView?: boolean;
private isSandbox?: boolean;
private isMobile!: boolean;
private coreIframe!: HTMLIFrameElement;
private errorsSubscription?: () => void;
private readonly headlessAppUrl = headlessCheckoutAppUrl;
Expand All @@ -141,15 +142,17 @@ export class HeadlessCheckout {
private readonly postMessagesClient: PostMessagesClient,
private readonly localizeService: LocalizeService,
private readonly headlessCheckoutSpy: HeadlessCheckoutSpy,
private readonly formSpy: FormSpy
private readonly formSpy: FormSpy,
) {}

public async init(environment: {
isWebview?: boolean;
sandbox?: boolean;
isMobile?: boolean;
}): Promise<void> {
this.isWebView = environment.isWebview;
this.isSandbox = environment.sandbox;
this.isMobile = !!environment.isMobile;

await this.localizeService.initDictionaries();

Expand All @@ -162,7 +165,7 @@ export class HeadlessCheckout {
getErrorHandler,
(error) => {
throw new Error(error);
}
},
);
}

Expand All @@ -183,14 +186,15 @@ export class HeadlessCheckout {
token,
isWebView: this.isWebView,
sandbox: this.isSandbox,
isMobile: this.isMobile,
},
},
};

return this.postMessagesClient.send<void>(msg, (message) =>
setTokenHandler(message, () => {
this.headlessCheckoutSpy.appWasInit = true;
})
}),
);
}

Expand All @@ -200,7 +204,7 @@ export class HeadlessCheckout {
name: EventName.setSecureComponentStyles,
data: styles,
},
setSecureComponentStylesHandler
setSecureComponentStylesHandler,
);
}

Expand All @@ -215,7 +219,7 @@ export class HeadlessCheckout {

return this.postMessagesClient.send<FinanceDetails | null>(
msg,
getFinanceDetailsHandler
getFinanceDetailsHandler,
) as Promise<FinanceDetails | null>;
}

Expand All @@ -239,7 +243,7 @@ export class HeadlessCheckout {

return this.postMessagesClient.send<PaymentMethod[]>(
msg,
getRegularMethodsHandler
getRegularMethodsHandler,
) as Promise<PaymentMethod[]>;
}

Expand All @@ -259,7 +263,7 @@ export class HeadlessCheckout {

return this.postMessagesClient.send<PaymentMethod[]>(
msg,
getQuickMethodsHandler
getQuickMethodsHandler,
) as Promise<PaymentMethod[]>;
}

Expand All @@ -270,7 +274,7 @@ export class HeadlessCheckout {

return this.postMessagesClient.send<SavedMethod[]>(
msg,
getSavedMethodsHandler
getSavedMethodsHandler,
) as Promise<SavedMethod[]>;
}

Expand All @@ -281,7 +285,7 @@ export class HeadlessCheckout {

return this.postMessagesClient.send<UserBalance>(
msg,
getUserBalanceHandler
getUserBalanceHandler,
) as Promise<UserBalance>;
}

Expand All @@ -294,7 +298,7 @@ export class HeadlessCheckout {
};

return this.postMessagesClient.send<Status>(msg, (message) =>
getPaymentStatusHandler(message)
getPaymentStatusHandler(message),
) as Promise<Status>;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { container } from 'tsyringe';
import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy';
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';
import { noopStub } from '../../../../tests/stubs/noop.stub';
import { HeadlessCheckout } from '../../headless-checkout';
import { QrCodeComponent } from './qr-code.component';

class HeadlessCheckoutMock {
public events = {
onCoreEvent: noopStub,
};
}

function createComponent(): void {
const element = document.createElement(WebComponentTagName.QrCodeComponent);

(document.getElementById('container')! as HTMLElement).appendChild(element);
}

describe('QrCodeComponent', () => {
let headlessCheckout: HeadlessCheckoutMock;
let headlessCheckoutSpy: HeadlessCheckoutSpy;

window.customElements.define(
WebComponentTagName.QrCodeComponent,
QrCodeComponent,
);

beforeEach(() => {
document.body.innerHTML = '<div id="container"></div>';

headlessCheckout = new HeadlessCheckoutMock();
headlessCheckoutSpy = {
listenAppInit: noopStub,
get appWasInit() {
return;
},
} as unknown as HeadlessCheckoutSpy;

container.clearInstances();

container
.register<HeadlessCheckoutSpy>(HeadlessCheckoutSpy, {
useValue: headlessCheckoutSpy,
})
.register<HeadlessCheckout>(HeadlessCheckout, {
useValue: headlessCheckout as unknown as HeadlessCheckout,
});
});

afterEach(() => {
document.body.innerHTML = '';
});

it('Should create component', () => {
createComponent();
expect(
document.querySelector(WebComponentTagName.QrCodeComponent),
).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SecureComponentAbstract } from '../../../../core/web-components/secure-component/secure-component.abstract';
import { EventName } from '../../../../core/event-name.enum';
import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler';
import { container } from 'tsyringe';
import { HeadlessCheckout } from '../../headless-checkout';

export class QrCodeComponent extends SecureComponentAbstract {
protected componentName = 'qr-code';
private readonly headlessCheckout: HeadlessCheckout;

public constructor() {
super();
this.headlessCheckout = container.resolve(HeadlessCheckout);
}

protected connectedCallback(): void {
this.startLoadingComponentHandler();

this.headlessCheckout.events.onCoreEvent(
EventName.finishLoadComponent,
finishLoadComponentHandler,
() => this.finishLoadingComponentHandler('qr-code'),
);

super.connectedCallback();
}

protected getHtml(): string {
return this.getSecureHtml();
}
}
2 changes: 2 additions & 0 deletions src/web-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { CheckboxComponent } from './features/headless-checkout/web-components/c
import { UserBalanceComponent } from './features/headless-checkout/web-components/user-balance-component/user-balance-component';
import { PaymentFormMessagesComponent } from './features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component';
import { GooglePayButtonComponent } from './features/headless-checkout/web-components/pages/google-pay/google-pay-button.component';
import { QrCodeComponent } from './features/headless-checkout/web-components/qr-code/qr-code.component';
import { ApplePayComponent } from './features/headless-checkout/web-components/apple-pay/apple-pay.component';

export {
Expand All @@ -31,5 +32,6 @@ export {
UserBalanceComponent,
PaymentFormMessagesComponent,
GooglePayButtonComponent,
QrCodeComponent,
ApplePayComponent,
};

0 comments on commit ef7762e

Please sign in to comment.