Skip to content

Commit

Permalink
feat(PAYMENTS-14328): add legal component
Browse files Browse the repository at this point in the history
  • Loading branch information
a.kornienko committed Jul 13, 2023
1 parent c9088d3 commit be955bd
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 34 deletions.
264 changes: 233 additions & 31 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"devDependencies": {
"@commitlint/cli": "^17.6.5",
"@commitlint/config-conventional": "^17.6.5",
"@types/jasmine": "^4.3.2",
"@types/i18next": "^13.0.0",
"@types/jasmine": "^4.3.2",
"@types/prettier": "^2.7.2",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
Expand All @@ -56,6 +56,7 @@
"stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-standard-scss": "^9.0.0",
"stylelint-order": "^6.0.3",
"svgo-loader": "^4.0.0",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"webpack": "^5.82.1",
Expand Down
8 changes: 8 additions & 0 deletions src/assets/icons/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/images.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '*.svg';
3 changes: 3 additions & 0 deletions src/core/event-name.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export const enum EventName {
getSavedMethods = 'getSavedMethods',
getUserBalance = 'getUserBalance',
submitButton = 'submitButton',
getLegalComponentConfig = 'getLegalComponentConfig',
legalComponentPing = 'legalComponentPing',
legalComponentPong = 'legalComponentPong',
}
16 changes: 16 additions & 0 deletions src/core/guards/legal-config-event-message.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Message } from '../../core/message.interface';
import { isEventMessage } from './event-message.guard';
import { EventName } from '../../core/event-name.enum';
import { LegalComponentConfig } from '../../features/headless-checkout/web-components/legal/legal-component.config.interface';

export const isLegalConfigEventMessage = (
messageData: unknown
): messageData is Message<{ config: LegalComponentConfig }> => {
if (isEventMessage(messageData)) {
return (
messageData.name === EventName.getLegalComponentConfig &&
(messageData.data as { [key: string]: unknown })?.config !== undefined
);
}
return false;
};
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 @@ -2,4 +2,5 @@ export enum WebComponentTagName {
CardNumberComponent = 'psdk-card-number',
SubmitButtonComponent = 'psdk-submit-button',
PaymentMethodsComponent = 'psdk-payment-methods',
LegalComponent = 'psdk-legal',
}
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 @@ -2,11 +2,13 @@ import { CardNumberComponent } from '../../features/headless-checkout/web-compon
import { SubmitButtonComponent } from '../../features/headless-checkout/web-components/submit-button/submit-button.component';
import { WebComponentTagName } from './web-component-tag-name.enum';
import { PaymentMethodsComponent } from '../../features/headless-checkout/web-components/payment-methods/payment-methods.component';
import { LegalComponent } from '../../features/headless-checkout/web-components/legal/legal.component';

export const webComponents: {
[key in WebComponentTagName]: CustomElementConstructor;
} = {
[WebComponentTagName.CardNumberComponent]: CardNumberComponent,
[WebComponentTagName.SubmitButtonComponent]: SubmitButtonComponent,
[WebComponentTagName.PaymentMethodsComponent]: PaymentMethodsComponent,
[WebComponentTagName.LegalComponent]: LegalComponent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { EventName } from '../../../core/event-name.enum';
import { Message } from '../../../core/message.interface';
import { getLegalComponentConfigHandler } from './get-legal-component-config.handler';
import { LegalComponentConfig } from '../web-components/legal/legal-component.config.interface';

const mockMessage: Message<{ config: LegalComponentConfig }> = {
name: EventName.getLegalComponentConfig,
data: { config: {} as unknown as LegalComponentConfig },
};
describe('getLegalComponentConfigHandler', () => {
it('Should handle data', () => {
expect(getLegalComponentConfigHandler(mockMessage)).toEqual({
isHandled: true,
value: {} as unknown as LegalComponentConfig,
});
});
it('Should return null', () => {
expect(
getLegalComponentConfigHandler({ name: EventName.initPayment })
).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Handler } from '../../../core/post-messages-client/handler.type';
import { Message } from '../../../core/message.interface';
import { EventName } from '../../../core/event-name.enum';
import { LegalComponentConfig } from '../web-components/legal/legal-component.config.interface';
import { isLegalConfigEventMessage } from '../../../core/guards/legal-config-event-message.guard';

export const getLegalComponentConfigHandler: Handler<LegalComponentConfig> = (
message: Message
): { isHandled: boolean; value: LegalComponentConfig } | null => {
if (
isLegalConfigEventMessage(message) &&
message.name === EventName.getLegalComponentConfig
) {
const config = message.data?.config;
return {
isHandled: true,
value: config!,
};
}
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Handler } from '../../../../../core/post-messages-client/handler.type';
import { Message } from '../../../../../core/message.interface';
import { EventName } from '../../../../../core/event-name.enum';

export const legalPingHandler: Handler<void> = (
message: Message
): { isHandled: boolean } | null => {
console.log(message);
if (message.name === EventName.legalComponentPing) {
return {
isHandled: true,
};
}
return null;
};
Original file line number Diff line number Diff line change
@@ -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 legalPongHandler: Handler<void> = (
message: Message
): { isHandled: boolean } | null => {
if (message.name === EventName.legalComponentPong) {
return {
isHandled: true,
};
}
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface LegalComponentConfig {
isJapanUser: boolean;
refundPolicyUrl: string;
sctlPolicyUrl?: string;
secureConnection: {
secureConnectionUrl?: string;
isWhiteLabel?: boolean;
};
disclaimer?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { LegalComponentConfig } from './legal-component.config.interface';
import { getSecureConnectionTemplate } from './secure-connection.component.template';
import i18next from 'i18next';

export const getLegalComponentTemplate = (
config: LegalComponentConfig
): string => {
const {
isJapanUser,
refundPolicyUrl,
sctlPolicyUrl,
secureConnection,
disclaimer,
} = config;
return `
${
disclaimer
? `
<div class="disclaimer">
${disclaimer}
</div>`
: ''
}
${getSecureConnectionTemplate(secureConnection)}
<div class="legal-links">
<a
class="link link-legal"
href="https://xsolla.com/legal-agreements"
target="_blank"
>
${i18next.t('legal')}
</a>
<div class="divider"></div>
<a
class="link link-legal"
href="https://xsolla.com/cookie"
target="_blank"
>
${i18next.t('cookie-policy')}
</a>
<div class="divider"></div>
<a
class="link link-legal"
href="https://xsolla.com/privacypolicy"
target="_blank"
>
${i18next.t('privacy-policy')}
</a>
<div class="divider"></div>
<a
class="link link-refund"
href="${refundPolicyUrl}"
target="_blank"
>
${i18next.t('refund-policy')}
</a>
${
isJapanUser && sctlPolicyUrl
? `
<div class="divider"></div>
<a
class="link sctl-link"
href="${sctlPolicyUrl}"
target="_blank"
>
${i18next.t('sctl-indications')}
</a>`
: ''
}
</div>`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract';
import { container } from 'tsyringe';
import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client';
import { legalPingHandler } from './handlers/legal-ping-handler';
import { EventName } from '../../../../core/event-name.enum';
import { HeadlessCheckout } from '../../headless-checkout';
import { legalPongHandler } from './handlers/legal-pong.handler';
import { getLegalComponentTemplate } from './legal.component.tempate';
import { Message } from '../../../../core/message.interface';
import { HeadlessCheckoutSpy } from '../../../../core/headless-checkout-spy/headless-checkout-spy';
import { getLegalComponentConfigHandler } from '../../post-messages-handlers/get-legal-component-config.handler';
import { LegalComponentConfig } from './legal-component.config.interface';

export class LegalComponent extends WebComponentAbstract {
private readonly postMessagesClient: PostMessagesClient;
private readonly headlessCheckout: HeadlessCheckout;
private readonly headlessCheckoutSpy: HeadlessCheckoutSpy;
private config?: LegalComponentConfig;
private subscription?: () => void;

public constructor() {
super();
this.headlessCheckoutSpy = container.resolve(HeadlessCheckoutSpy);
this.headlessCheckout = container.resolve(HeadlessCheckout);
this.postMessagesClient = container.resolve(PostMessagesClient);
}

protected connectedCallback(): void {
if (!this.headlessCheckoutSpy.appWasInit) {
this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback());
return;
}
void this.getLegalComponentConfig().then(this.configLoadedHandler);
}

protected readonly configLoadedHandler = (
config: LegalComponentConfig
): void => {
this.config = config;

super.render();

if (this.config) {
this.listenPings();
}
};

protected listenPings(): void {
this.subscription = this.headlessCheckout.events.onCoreEvent(
EventName.legalComponentPing,
legalPingHandler,
this.pongCallback
);
}

protected disconnectedCallback(): void {
super.disconnectedCallback();
if (this.subscription) {
this.subscription();
}
}

protected async getLegalComponentConfig(): Promise<LegalComponentConfig> {
const msg: Message = {
name: EventName.getLegalComponentConfig,
};

return this.postMessagesClient.send<LegalComponentConfig>(
msg,
getLegalComponentConfigHandler
) as Promise<LegalComponentConfig>;
}

protected getHtml(): string {
if (this.config) {
return getLegalComponentTemplate(this.config);
}
return '';
}

private readonly pongCallback = (): void => {
void this.headlessCheckout.events.send(
{ name: EventName.legalComponentPong },
legalPongHandler
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { LegalComponentConfig } from './legal-component.config.interface';
import logo from '../../../../assets/icons/logo.svg';
import i18next from 'i18next';
export const getSecureConnectionTemplate = (
secureConnection?: LegalComponentConfig['secureConnection']
): string => {
const isWhiteLabel = secureConnection?.isWhiteLabel;
const secureConnectionUrl = secureConnection?.secureConnectionUrl;
return `
<div class="info">
<div class="company">
${
!isWhiteLabel && secureConnectionUrl
? `
<a
class="logo"
[href]="secureConnectionUrl"
target="_blank"
>
<img src="${logo as string}">
</a>`
: `
<span class="logo">
<img src="${logo as string}">
</span>`
}
<div class="connection">
${i18next.t('secure-connection')}
</div>
</div>
</div>
`;
};
8 changes: 7 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{
"en": {
"translation": {
"hello": "Hello world!"
"hello": "Hello world!",
"legal": "Legal",
"cookie-policy": "Cookie Policy",
"privacy-policy": "Privacy Policy",
"refund-policy": "Refund policy",
"sctl-indications": "SCTL Indications",
"secure-connection": "Secure connection"
}
}
}
8 changes: 7 additions & 1 deletion src/web-components.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { SubmitButtonComponent } from './features/headless-checkout/web-components/submit-button/submit-button.component';
import { CardNumberComponent } from './features/headless-checkout/web-components/card-number/card-number.component';
import { PaymentMethodsComponent } from './features/headless-checkout/web-components/payment-methods/payment-methods.component';
import { LegalComponent } from './features/headless-checkout/web-components/legal/legal.component';

export { SubmitButtonComponent, CardNumberComponent, PaymentMethodsComponent };
export {
SubmitButtonComponent,
CardNumberComponent,
PaymentMethodsComponent,
LegalComponent,
};
Loading

0 comments on commit be955bd

Please sign in to comment.