Skip to content

Commit

Permalink
feat: added status component
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikolay Poluhin committed Aug 11, 2023
1 parent 65895ed commit 7300539
Show file tree
Hide file tree
Showing 17 changed files with 200 additions and 30 deletions.
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-webpack-plugin": "^4.0.1",
"file-loader": "^6.2.0",
"husky": "^8.0.3",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.2.0",
Expand Down
1 change: 1 addition & 0 deletions src/assets/images.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
declare module '*.svg';
declare module '*.png';
Binary file added src/assets/statuses/failed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/statuses/success.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/core/status/status.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ export enum StatusEnum {
done = 'done',
error = 'error',
canceled = 'canceled',
unknown = 'unknown',
}
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 @@ -3,4 +3,5 @@ export enum WebComponentTagName {
SubmitButtonComponent = 'psdk-submit-button',
PaymentMethodsComponent = 'psdk-payment-methods',
LegalComponent = 'psdk-legal',
StatusComponent = 'psdk-status',
}
4 changes: 3 additions & 1 deletion src/core/web-components/web-components.map.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TextComponent } from '../../features/headless-checkout/web-components/text-component/text.component';
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';
import { StatusComponent } from '../../features/headless-checkout/web-components/status/status.component';
import { WebComponentTagName } from './web-component-tag-name.enum';

export const webComponents: {
[key in WebComponentTagName]: CustomElementConstructor;
Expand All @@ -11,4 +12,5 @@ export const webComponents: {
[WebComponentTagName.SubmitButtonComponent]: SubmitButtonComponent,
[WebComponentTagName.PaymentMethodsComponent]: PaymentMethodsComponent,
[WebComponentTagName.LegalComponent]: LegalComponent,
[WebComponentTagName.StatusComponent]: StatusComponent,
};
3 changes: 1 addition & 2 deletions src/features/headless-checkout/environment.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const headlessCheckoutAppUrl =
'https://secure.xsolla.com/headless-checkout';
export const headlessCheckoutAppUrl = 'http://localhost:4200';
export const cdnUrl = 'https://cdn3.xsolla.com';
40 changes: 17 additions & 23 deletions src/features/headless-checkout/headless-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Form } from '../../core/form/form.interface';
import { NextAction } from '../../core/actions/next-action.interface';
import { FormSpy } from '../../core/spy/form-spy/form-spy';
import { Status } from '../../core/status/status.interface';
import { StatusEnum } from '../../core/status/status.enum';
import { getErrorHandler } from './post-messages-handlers/error.handler';
import { initFormHandler } from './post-messages-handlers/init-form.handler';
import { getQuickMethodsHandler } from './post-messages-handlers/get-quick-methods.handler';
Expand Down Expand Up @@ -85,30 +84,10 @@ export class HeadlessCheckout {
}
);
},

getStatus: async (): Promise<Status> => {
const msg: Message = {
name: EventName.getPaymentStatus,
};

const status = await this.postMessagesClient.send<Status>(
msg,
(message) => getPaymentStatusHandler(message)
);

if (!status) {
return {
statusState: StatusEnum.unknown,
statusMessage: 'Unknown status',
group: 'unknown',
};
}

return status;
},
};

private isWebView?: boolean;
private isSandbox?: boolean;
private coreIframe!: HTMLIFrameElement;
private errorsSubscription?: () => void;
private readonly headlessAppUrl = headlessCheckoutAppUrl;
Expand All @@ -121,8 +100,12 @@ export class HeadlessCheckout {
private readonly formSpy: FormSpy
) {}

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

await this.localizeService.initDictionaries();

Expand Down Expand Up @@ -155,6 +138,7 @@ export class HeadlessCheckout {
configuration: {
token,
isWebView: this.isWebView,
sandbox: this.isSandbox,
},
},
};
Expand Down Expand Up @@ -228,6 +212,16 @@ export class HeadlessCheckout {
) as Promise<UserBalance>;
}

public async getStatus(): Promise<Status> {
const msg: Message = {
name: EventName.getPaymentStatus,
};

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

private async setupCoreIframe(): Promise<void> {
this.coreIframe = this.window.document.createElement('iframe');
this.coreIframe.width = '0px';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LegalComponentConfig } from './legal-component.config.interface';
import logo from '../../../../assets/icons/logo.svg';
import i18next from 'i18next';
import logo from '../../../../assets/icons/logo.svg';
import { LegalComponentConfig } from './legal-component.config.interface';

export const getSecureConnectionTemplate = (
secureConnection?: LegalComponentConfig['secureConnection']
): string => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface StatusComponentConfig {
title: string;
image: string | null;
description: string;
showDescription: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { StatusComponentConfig } from './status.component.config.interface';

export const getStatusComponentTemplate = (
statusConfig: StatusComponentConfig
): string => {
return `
<div class="status">
${
statusConfig.image
? `
<div class="image-container">
<img class="image" src="${statusConfig.image}" alt="${statusConfig.title}" />
</div>`
: ''
}
<div class="title">
<h2 class="title-text">${statusConfig.title}</h2>
</div>
${
statusConfig.showDescription
? `<p class="description">${statusConfig.description}</p>`
: ''
}
</div>
`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { container } from 'tsyringe';
import i18next from 'i18next';
import { WebComponentAbstract } from '../../../../core/web-components/web-component.abstract';
import { Status } from '../../../../core/status/status.interface';
import { StatusEnum } from '../../../../core/status/status.enum';
import { HeadlessCheckout } from '../../headless-checkout';
import { getStatusComponentTemplate } from './status.component.template';
import { StatusComponentConfig } from './status.component.config.interface';
import successImage from '../../../../assets/statuses/success.png';
import failedImage from '../../../../assets/statuses/failed.png';

export class StatusComponent extends WebComponentAbstract {
private readonly headlessCheckout: HeadlessCheckout;

private statusConfig: StatusComponentConfig | null = null;

public constructor() {
super();

this.headlessCheckout = container.resolve(HeadlessCheckout);
}

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

this.headlessCheckout.form.onNextAction((nextAction) => {
if (nextAction.type === 'status_updated') {
void this.getStatus();
}
});
}

protected getHtml(): string {
if (this.statusConfig) {
return getStatusComponentTemplate(this.statusConfig);
}

return '';
}

private statusLoadedHandler(
statusConfig: StatusComponentConfig | null
): void {
this.statusConfig = statusConfig;

this.render();
}

private async getStatus(): Promise<void> {
const status = await this.headlessCheckout.getStatus();

const statusConfig = this.getStatusConfig(status);

this.statusLoadedHandler(statusConfig);
}

private getStatusConfig(status: Status): StatusComponentConfig | null {
if (!status) {
return null;
}

const isProcessing = [
StatusEnum.processing,
StatusEnum.created,
StatusEnum.held,
].includes(status.statusState);
const isError =
[StatusEnum.canceled, StatusEnum.error].includes(status.statusState) ||
status.isCancelUser;
const isSuccess =
status.statusState === StatusEnum.done ??
status.statusState === StatusEnum.authorized;

if (isProcessing) {
return {
image: null,
title: i18next.t('status.processing.title'),
description: i18next.t('status.processing.description'),
showDescription: true,
};
}

if (isError) {
return {
image: failedImage,
title: i18next.t('status.error.title'),
description: '',
showDescription: false,
};
}

if (isSuccess) {
return {
image: successImage,
title: i18next.t('status.success.title'),
description: i18next.t('status.success.description', {
email: status.email,
}),
showDescription: !!status.email,
};
}

return null;
}
}
7 changes: 6 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
"privacy-policy": "Privacy policy",
"refund-policy": "Refund policy",
"sctl-indications": "SCTL Indications",
"secure-connection": "Secure connection"
"secure-connection": "Secure connection",
"status.processing.title": "Processing payment",
"status.processing.description": "Waiting for payment to complete...",
"status.error.title": "Payment failed",
"status.success.title": "Payment successful",
"status.success.description": "We sent your receipt to {{email}}"
}
}
}
2 changes: 2 additions & 0 deletions src/web-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { SubmitButtonComponent } from './features/headless-checkout/web-componen
import { TextComponent } from './features/headless-checkout/web-components/text-component/text.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';
import { StatusComponent } from './features/headless-checkout/web-components/status/status.component';

export {
SubmitButtonComponent,
TextComponent,
PaymentMethodsComponent,
LegalComponent,
StatusComponent,
};
5 changes: 5 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const config = {
type: 'asset',
use: 'svgo-loader',
},
{
test: /\.png$/,
type: 'asset',
use: 'file-loader',
},
],
},
resolve: {
Expand Down

0 comments on commit 7300539

Please sign in to comment.