diff --git a/examples/secure-component-styles/index.html b/examples/secure-component-styles/index.html new file mode 100644 index 0000000..9e0cefc --- /dev/null +++ b/examples/secure-component-styles/index.html @@ -0,0 +1,140 @@ + + + + + + + Secure component styles + + + + + + + + + +

Secure component styles

+ +
+ + +
+ + + + + + + + diff --git a/src/core/event-name.enum.ts b/src/core/event-name.enum.ts index 8ff9ee1..1d19bfa 100644 --- a/src/core/event-name.enum.ts +++ b/src/core/event-name.enum.ts @@ -14,4 +14,5 @@ export const enum EventName { financeDetails = 'financeDetails', nextAction = 'nextAction', warning = 'warning', + setSecureComponentStyles = 'setSecureComponentStyles', } diff --git a/src/features/headless-checkout/headless-checkout.spec.ts b/src/features/headless-checkout/headless-checkout.spec.ts index 6ef246e..c7606ac 100644 --- a/src/features/headless-checkout/headless-checkout.spec.ts +++ b/src/features/headless-checkout/headless-checkout.spec.ts @@ -72,7 +72,7 @@ describe('HeadlessCheckout', () => { const spy = spyOn(postMessagesClient, 'init'); spyOn(windowService.document.body, 'appendChild'); spyOn(windowService.document, 'createElement').and.callFake( - () => new MockIframeElement() as unknown as HTMLIFrameElement + () => new MockIframeElement() as unknown as HTMLIFrameElement, ); await headlessCheckout.init({ isWebview: false }); @@ -82,7 +82,7 @@ describe('HeadlessCheckout', () => { it('Should init localization', () => { const localizeSpy = spyOn( localizeService, - 'initDictionaries' + 'initDictionaries', ).and.resolveTo(); void headlessCheckout.init({ isWebview: false }); @@ -101,7 +101,7 @@ describe('HeadlessCheckout', () => { headlessCheckout.events.onCoreEvent( EventName.initPayment, mockHandler, - stub + stub, ); expect(spy).toHaveBeenCalled(); }); @@ -110,7 +110,7 @@ describe('HeadlessCheckout', () => { const spy = spyOn(postMessagesClient, 'listen'); spyOn(windowService.document.body, 'appendChild'); spyOn(windowService.document, 'createElement').and.callFake( - () => new MockIframeElement() as unknown as HTMLIFrameElement + () => new MockIframeElement() as unknown as HTMLIFrameElement, ); await headlessCheckout.init({ isWebview: false }); @@ -123,6 +123,12 @@ describe('HeadlessCheckout', () => { expect(spy).toHaveBeenCalled(); }); + it('Should set secure component styles', async () => { + const spy = spyOn(postMessagesClient, 'send'); + await headlessCheckout.setSecureComponentStyles('styles'); + expect(spy).toHaveBeenCalled(); + }); + it('Should throw exception if empty token', async () => { try { await headlessCheckout.setToken(''); @@ -136,7 +142,7 @@ describe('HeadlessCheckout', () => { await headlessCheckout.getFinanceDetails(); expect(spy).toHaveBeenCalledWith( { name: EventName.financeDetails }, - getFinanceDetailsHandler + getFinanceDetailsHandler, ); }); @@ -157,7 +163,7 @@ describe('HeadlessCheckout', () => { spyOn(windowService.customElements, 'get').and.returnValue(undefined); spyOn(windowService.document.body, 'appendChild'); spyOn(windowService.document, 'createElement').and.callFake( - () => new MockIframeElement() as unknown as HTMLIFrameElement + () => new MockIframeElement() as unknown as HTMLIFrameElement, ); await headlessCheckout.init({ isWebview: false }); @@ -166,11 +172,11 @@ describe('HeadlessCheckout', () => { it('Should web components not be redefined', async () => { spyOn(windowService.customElements, 'get').and.returnValue( - CustomElementMock + CustomElementMock, ); spyOn(windowService.document.body, 'appendChild'); spyOn(windowService.document, 'createElement').and.callFake( - () => new MockIframeElement() as unknown as HTMLIFrameElement + () => new MockIframeElement() as unknown as HTMLIFrameElement, ); const spy = spyOn(window.customElements, 'define'); diff --git a/src/features/headless-checkout/headless-checkout.ts b/src/features/headless-checkout/headless-checkout.ts index fffc60e..a9d69e2 100644 --- a/src/features/headless-checkout/headless-checkout.ts +++ b/src/features/headless-checkout/headless-checkout.ts @@ -28,6 +28,7 @@ import { headlessCheckoutAppUrl } from './environment'; import { FinanceDetails } from '../../core/finance-details/finance-details.interface'; import { getFinanceDetailsHandler } from './post-messages-handlers/get-finance-details.handler'; import { FormStatus } from '../../core/status/form-status.enum'; +import { setSecureComponentStylesHandler } from './post-messages-handlers/set-secure-component-styles.handler'; @singleton() export class HeadlessCheckout { @@ -52,7 +53,7 @@ export class HeadlessCheckout { onCoreEvent: ( eventName: EventName, handler: Handler, - callback: (value?: T) => void + callback: (value?: T) => void, ): (() => void) => { return this.postMessagesClient.listen(eventName, handler, callback); }, @@ -81,7 +82,7 @@ export class HeadlessCheckout { } this.formSpy.formWasInit = true; this.formStatus = FormStatus.active; - }) + }), ) as Promise
; }, @@ -93,7 +94,7 @@ export class HeadlessCheckout { if (nextAction) { callbackFn(nextAction); } - } + }, ); }, @@ -124,7 +125,7 @@ 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: { @@ -145,7 +146,7 @@ export class HeadlessCheckout { getErrorHandler, (error) => { throw new Error(error); - } + }, ); } @@ -173,7 +174,17 @@ export class HeadlessCheckout { return this.postMessagesClient.send(msg, (message) => setTokenHandler(message, () => { this.headlessCheckoutSpy.appWasInit = true; - }) + }), + ); + } + + public async setSecureComponentStyles(styles: string): Promise { + return this.postMessagesClient.send( + { + name: EventName.setSecureComponentStyles, + data: styles, + }, + setSecureComponentStylesHandler, ); } @@ -188,7 +199,7 @@ export class HeadlessCheckout { return this.postMessagesClient.send( msg, - getFinanceDetailsHandler + getFinanceDetailsHandler, ) as Promise; } @@ -208,7 +219,7 @@ export class HeadlessCheckout { return this.postMessagesClient.send( msg, - getRegularMethodsHandler + getRegularMethodsHandler, ) as Promise; } @@ -228,7 +239,7 @@ export class HeadlessCheckout { return this.postMessagesClient.send( msg, - getQuickMethodsHandler + getQuickMethodsHandler, ) as Promise; } @@ -239,7 +250,7 @@ export class HeadlessCheckout { return this.postMessagesClient.send( msg, - getSavedMethodsHandler + getSavedMethodsHandler, ) as Promise; } @@ -250,7 +261,7 @@ export class HeadlessCheckout { return this.postMessagesClient.send( msg, - getUserBalanceHandler + getUserBalanceHandler, ) as Promise; } @@ -263,7 +274,7 @@ export class HeadlessCheckout { }; return this.postMessagesClient.send(msg, (message) => - getPaymentStatusHandler(message) + getPaymentStatusHandler(message), ) as Promise; } diff --git a/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts new file mode 100644 index 0000000..1f07735 --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.spec.ts @@ -0,0 +1,24 @@ +import { EventName } from '../../../core/event-name.enum'; +import { Message } from '../../../core/message.interface'; +import { PaymentMethod } from '../../../core/payment-method.interface'; +import { setSecureComponentStylesHandler } from './set-secure-component-styles.handler'; + +const mockMessage: Message<{ methods: PaymentMethod[] }> = { + name: EventName.setSecureComponentStyles, +}; + +describe('setSecureComponentStylesHandler', () => { + it('Should handle data', () => { + expect(setSecureComponentStylesHandler(mockMessage)).toEqual({ + isHandled: true, + }); + }); + + it('Should return null', () => { + expect( + setSecureComponentStylesHandler({ + name: EventName.getPaymentMethodsList, + }), + ).toBeNull(); + }); +}); diff --git a/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts new file mode 100644 index 0000000..92ebe1c --- /dev/null +++ b/src/features/headless-checkout/post-messages-handlers/set-secure-component-styles.handler.ts @@ -0,0 +1,14 @@ +import { Handler } from '../../../core/post-messages-client/handler.type'; +import { Message } from '../../../core/message.interface'; +import { EventName } from '../../../core/event-name.enum'; + +export const setSecureComponentStylesHandler: Handler = ( + message: Message, +): { isHandled: boolean } | null => { + if (message.name === EventName.setSecureComponentStyles) { + return { + isHandled: true, + }; + } + return null; +}; diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts index 46e1634..3435b28 100644 --- a/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts +++ b/src/features/headless-checkout/web-components/payment-form/payment-form-fields.service.spec.ts @@ -8,7 +8,7 @@ const getTextInputElements = (names: string[]): NodeListOf => { const mockContainer = document.createElement('div'); for (const name of names) { const mockElement = document.createElement( - WebComponentTagName.TextComponent + WebComponentTagName.TextComponent, ); mockElement.setAttribute('name', name); mockContainer.appendChild(mockElement); @@ -23,6 +23,7 @@ describe('PaymentFormFieldsManager', () => { beforeEach(() => { windowService = window; + container.clearInstances(); container.register(Window, { useValue: windowService }); paymentFormFieldsManager = container @@ -33,14 +34,14 @@ describe('PaymentFormFieldsManager', () => { it('Should create missed fields and append them into body', () => { const textInputElements = getTextInputElements([]); spyOn(windowService.document, 'querySelectorAll').and.returnValue( - textInputElements + textInputElements, ); const missedFields = ['card']; const mockBody = { appendChild: noopStub, } as unknown as HTMLElement; spyOn(windowService.document, 'querySelector').and.returnValue( - mockBody as unknown as Element + mockBody as unknown as Element, ); const spy = spyOn(mockBody, 'appendChild'); paymentFormFieldsManager.createMissedFields(missedFields, mockBody); @@ -53,7 +54,7 @@ describe('PaymentFormFieldsManager', () => { remove: noopStub, }; spyOn(windowService.document, 'querySelector').and.returnValue( - mockElement as unknown as Element + mockElement as unknown as Element, ); const spy = spyOn(mockElement, 'remove'); paymentFormFieldsManager.removeExtraFields(extraFields); @@ -66,7 +67,7 @@ describe('PaymentFormFieldsManager', () => { element.removeAttribute('name'); }); spyOn(windowService.document, 'querySelectorAll').and.returnValue( - textInputElements + textInputElements, ); const spy = spyOn(textInputElements[0], 'remove'); paymentFormFieldsManager.removeEmptyNameFields();