Files
vitanovaPackages/dist/fesm2022/marketplaces-payment.mjs

211 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as i0 from '@angular/core';
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, input, booleanAttribute, output, signal, DestroyRef, Component } from '@angular/core';
import * as QRCode from 'qrcode';
import { catchError, throwError, timer, switchMap } from 'rxjs';
import { HttpHeaders, HttpClient, HttpErrorResponse } from '@angular/common/http';
const MARKETPLACES_PAYMENT_CONFIG = new InjectionToken('@marketplaces/payment config');
function provideMarketplacesPayment(config) {
return makeEnvironmentProviders([{
provide: MARKETPLACES_PAYMENT_CONFIG,
useValue: {
...config,
apiUrl: config.apiUrl.replace(/\/$/, ''),
paymentsPath: config.paymentsPath ?? '/api/v1/payments',
pollIntervalMs: config.pollIntervalMs ?? 1500,
},
}]);
}
const MARKETPLACE_DOMAIN_HEADER = 'X-Marketplace-Domain';
function normalizeMarketplaceDomain(domain) {
return domain.trim().toLowerCase().replace(/\.$/, '');
}
class PaymentMarketplaceContext {
constructor() {
this.config = inject(MARKETPLACES_PAYMENT_CONFIG);
}
domain() {
const configured = this.config.marketplaceDomain;
const domain = typeof configured === 'function' ? configured() : configured ?? (typeof location === 'undefined' ? '' : location.hostname);
return normalizeMarketplaceDomain(domain);
}
headers() {
const domain = this.domain();
if (!domain)
throw new Error('Marketplace domain cannot be resolved');
return new HttpHeaders({ [MARKETPLACE_DOMAIN_HEADER]: domain });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMarketplaceContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMarketplaceContext, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMarketplaceContext, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
const MARKETPLACES_PAYMENT_GATEWAY = new InjectionToken('@marketplaces/payment gateway', { providedIn: 'root', factory: () => inject(HttpMarketplacesPaymentGateway) });
class HttpMarketplacesPaymentGateway {
constructor() {
this.http = inject(HttpClient);
this.config = inject(MARKETPLACES_PAYMENT_CONFIG);
this.context = inject(PaymentMarketplaceContext);
}
create(method, request) {
return this.http.post(this.baseUrl(), { ...request, method }, { headers: this.context.headers() }).pipe(this.handle(method));
}
status(paymentId, method) {
return this.http.get(`${this.baseUrl()}/${encodeURIComponent(paymentId)}`, { headers: this.context.headers() }).pipe(this.handle(method));
}
cancel(paymentId) {
return this.http.post(`${this.baseUrl()}/${encodeURIComponent(paymentId)}/cancel`, {}, { headers: this.context.headers() });
}
baseUrl() {
const path = this.config.paymentsPath ?? '';
return `${this.config.apiUrl}${path.startsWith('/') ? path : `/${path}`}`;
}
handle(method) {
return (source) => source.pipe(catchError(cause => {
const response = cause instanceof HttpErrorResponse ? cause : null;
const failure = {
method, code: response?.status === 402 ? 'declined' : 'backend',
message: response?.error?.message || response?.message || 'Payment failed', cause,
};
return throwError(() => failure);
}));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HttpMarketplacesPaymentGateway, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HttpMarketplacesPaymentGateway, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HttpMarketplacesPaymentGateway, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class MarketplacesPaymentComponent {
constructor() {
this.qr = input(false, { ...(ngDevMode ? { debugName: "qr" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.card = input(false, { ...(ngDevMode ? { debugName: "card" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.sbp = input(false, { ...(ngDevMode ? { debugName: "sbp" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.yandexPay = input(false, { ...(ngDevMode ? { debugName: "yandexPay" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.request = input.required(/* @ts-ignore */
...(ngDevMode ? [{ debugName: "request" }] : /* istanbul ignore next */ []));
this.title = input('Оплата', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
this.completed = output();
this.paymentError = output();
this.cancelled = output();
this.busy = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
this.error = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
this.attempt = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "attempt" }] : /* istanbul ignore next */ []));
this.qrImage = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "qrImage" }] : /* istanbul ignore next */ []));
this.actionUrl = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "actionUrl" }] : /* istanbul ignore next */ []));
this.gateway = inject(MARKETPLACES_PAYMENT_GATEWAY);
this.config = inject(MARKETPLACES_PAYMENT_CONFIG);
inject(DestroyRef).onDestroy(() => this.poll?.unsubscribe());
}
pay(method) {
this.poll?.unsubscribe();
this.error.set(null);
this.busy.set(true);
this.qrImage.set(null);
this.gateway.create(method, this.request()).subscribe({
next: created => void this.handleCreated(created).catch(cause => this.fail(method, cause)),
error: cause => this.fail(method, cause),
});
}
cancel() {
const current = this.attempt();
this.poll?.unsubscribe();
this.busy.set(false);
this.attempt.set(null);
if (current)
this.gateway.cancel(current.paymentId).subscribe({ error: () => undefined });
this.cancelled.emit();
}
async handleCreated(created) {
this.attempt.set(created);
if (created.action?.url) {
this.actionUrl.set(created.action.url);
if (created.action.type === 'qr')
this.qrImage.set(await QRCode.toDataURL(created.action.url, { width: 320, margin: 1 }));
else if (typeof window !== 'undefined' && !window.open(created.action.url, '_blank', 'noopener')) {
this.fail(created.method, { method: created.method, code: 'popup_blocked', message: 'Браузер заблокировал окно оплаты' });
return;
}
}
if (created.status === 'paid' || created.status === 'authorized') {
this.finish(created);
return;
}
if (created.status === 'failed' || created.status === 'expired') {
this.fail(created.method, created.failureMessage ?? 'Платёж отклонён');
return;
}
this.poll = timer(0, this.config.pollIntervalMs ?? 1500).pipe(switchMap(() => this.gateway.status(created.paymentId, created.method)))
.subscribe({ next: status => this.handleStatus(status), error: cause => this.fail(created.method, cause) });
}
handleStatus(current) {
this.attempt.set(current);
if (current.status === 'paid' || current.status === 'authorized')
this.finish(current);
else if (current.status === 'failed' || current.status === 'expired' || current.status === 'cancelled')
this.fail(current.method, current.failureMessage ?? 'Платёж не завершён');
}
finish(attempt) { this.poll?.unsubscribe(); this.busy.set(false); this.attempt.set(null); this.completed.emit(attempt); }
fail(method, cause) {
this.poll?.unsubscribe();
this.busy.set(false);
const failure = this.isFailure(cause) ? cause : { method, code: 'backend', message: typeof cause === 'string' ? cause : 'Не удалось провести платёж', cause };
this.error.set(failure);
this.paymentError.emit(failure);
}
isFailure(value) { return !!value && typeof value === 'object' && 'code' in value && 'message' in value; }
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MarketplacesPaymentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MarketplacesPaymentComponent, isStandalone: true, selector: "mp-payment, marketplaces-payment", inputs: { qr: { classPropertyName: "qr", publicName: "qr", isSignal: true, isRequired: false, transformFunction: null }, card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, sbp: { classPropertyName: "sbp", publicName: "sbp", isSignal: true, isRequired: false, transformFunction: null }, yandexPay: { classPropertyName: "yandexPay", publicName: "yandexPay", isSignal: true, isRequired: false, transformFunction: null }, request: { classPropertyName: "request", publicName: "request", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { completed: "completed", paymentError: "paymentError", cancelled: "cancelled" }, ngImport: i0, template: `
<section class="mp-payment" aria-labelledby="mp-payment-title">
<h2 id="mp-payment-title">{{ title() }}</h2>
<div class="methods">
@if (qr()) { <button type="button" (click)="pay('qr')" [disabled]="busy()">QR</button> }
@if (card()) { <button type="button" (click)="pay('card')" [disabled]="busy()">Карта</button> }
@if (sbp()) { <button type="button" (click)="pay('sbp')" [disabled]="busy()">СБП</button> }
@if (yandexPay()) { <button type="button" (click)="pay('yandex-pay')" [disabled]="busy()">Яндекс Pay</button> }
</div>
@if (qrImage()) { <a [href]="actionUrl()!" target="_blank" rel="noopener"><img [src]="qrImage()!" alt="QR-код оплаты" /></a> }
@if (busy()) { <p role="status">Проверяем оплату…</p> }
@if (error()) { <p class="error" role="alert">{{ error()!.message }}</p> }
@if (attempt()) { <button type="button" class="cancel" (click)="cancel()">Отменить</button> }
</section>
`, isInline: true, styles: [":host{display:block}.mp-payment{font:inherit;color:inherit;display:grid;gap:1rem;max-width:26rem}h2,p{margin:0}.methods{display:flex;gap:.6rem;flex-wrap:wrap}button{font:inherit;cursor:pointer;border:1px solid #111;border-radius:.65rem;padding:.75rem 1rem;background:#111;color:#fff}button:disabled{opacity:.55;cursor:wait}.cancel{background:transparent;color:inherit;border-color:#c7c7c7}img{display:block;width:min(15rem,100%);height:auto}.error{color:#b42318}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MarketplacesPaymentComponent, decorators: [{
type: Component,
args: [{ selector: 'mp-payment, marketplaces-payment', standalone: true, template: `
<section class="mp-payment" aria-labelledby="mp-payment-title">
<h2 id="mp-payment-title">{{ title() }}</h2>
<div class="methods">
@if (qr()) { <button type="button" (click)="pay('qr')" [disabled]="busy()">QR</button> }
@if (card()) { <button type="button" (click)="pay('card')" [disabled]="busy()">Карта</button> }
@if (sbp()) { <button type="button" (click)="pay('sbp')" [disabled]="busy()">СБП</button> }
@if (yandexPay()) { <button type="button" (click)="pay('yandex-pay')" [disabled]="busy()">Яндекс Pay</button> }
</div>
@if (qrImage()) { <a [href]="actionUrl()!" target="_blank" rel="noopener"><img [src]="qrImage()!" alt="QR-код оплаты" /></a> }
@if (busy()) { <p role="status">Проверяем оплату…</p> }
@if (error()) { <p class="error" role="alert">{{ error()!.message }}</p> }
@if (attempt()) { <button type="button" class="cancel" (click)="cancel()">Отменить</button> }
</section>
`, styles: [":host{display:block}.mp-payment{font:inherit;color:inherit;display:grid;gap:1rem;max-width:26rem}h2,p{margin:0}.methods{display:flex;gap:.6rem;flex-wrap:wrap}button{font:inherit;cursor:pointer;border:1px solid #111;border-radius:.65rem;padding:.75rem 1rem;background:#111;color:#fff}button:disabled{opacity:.55;cursor:wait}.cancel{background:transparent;color:inherit;border-color:#c7c7c7}img{display:block;width:min(15rem,100%);height:auto}.error{color:#b42318}\n"] }]
}], ctorParameters: () => [], propDecorators: { qr: [{ type: i0.Input, args: [{ isSignal: true, alias: "qr", required: false }] }], card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: false }] }], sbp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sbp", required: false }] }], yandexPay: [{ type: i0.Input, args: [{ isSignal: true, alias: "yandexPay", required: false }] }], request: [{ type: i0.Input, args: [{ isSignal: true, alias: "request", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], completed: [{ type: i0.Output, args: ["completed"] }], paymentError: [{ type: i0.Output, args: ["paymentError"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
/**
* Generated bundle index. Do not edit.
*/
export { HttpMarketplacesPaymentGateway, MARKETPLACES_PAYMENT_CONFIG, MARKETPLACES_PAYMENT_GATEWAY, MARKETPLACE_DOMAIN_HEADER, MarketplacesPaymentComponent, PaymentMarketplaceContext, normalizeMarketplaceDomain, provideMarketplacesPayment };
//# sourceMappingURL=marketplaces-payment.mjs.map