import { Component, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { QR_VITANOVA_API } from '../../api'; import { TranslatePipe } from '../../translate/translate.pipe'; import { TranslationService } from '../../translate/translation.service'; type PaymentMethod = 'sbp'; type Currency = 'RUB'; interface SettingsResponse { sbp?: boolean; wechat?: boolean; visa?: boolean; mastercard?: boolean; alipay?: boolean; rubles?: boolean; usd?: boolean; euro?: boolean; cny?: boolean; dram?: boolean; minAmount?: number; maxAmount?: number; qrTTL?: number; } interface CreateQrResponse { qrId?: string; nspkID?: string; Payload?: string; // per API doc (capital P) nspkurl?: string; // actual field name in real responses qrUrl?: string; status?: string; // e.g. "REGISTERED" } interface QrStatusResponse { status?: string; // "REGISTERED" | "NEW" | "APPROVED" | "REJECTED" | "COMPLETED" [key: string]: unknown; } @Component({ selector: 'app-create-page', imports: [FormsModule, TranslatePipe], templateUrl: './create-page.html', styleUrl: './create-page.scss' }) export class CreatePage { private http = inject(HttpClient); private i18n = inject(TranslationService); private readonly sites: Record = { '51': 'fastcheck.store' }; private t(key: string): string { return this.i18n.translate(key); } // Limits – updated from settings API on init. minAmount = signal(30); maxAmount = signal(200_000); amount = signal(null); note = signal(''); error = signal(''); loading = signal(false); currency = signal('RUB'); payment = signal('sbp'); selectPayment(method: PaymentMethod, enabled: boolean): void { if (!enabled) return; this.payment.set(method); } selectCurrency(c: Currency, enabled: boolean): void { if (!enabled) return; this.currency.set(c); } // QR display state qrImageUrl = signal(null); qrPolling = signal(false); qrStatus = signal(''); paymentDone = signal(false); private pollHandle: ReturnType | null = null; /** Auth credentials passed by the host page as URL params. */ private get authKey(): string { return new URLSearchParams(window.location.search).get('authorization-key') ?? ''; } private get userId(): string { return new URLSearchParams(window.location.search).get('userid-value') ?? ''; } private get reference(): string { return new URLSearchParams(window.location.search).get('ref') ?? window.location.hostname; } private get partnerqrID(): string { return new URLSearchParams(window.location.search).get('id') ?? ''; } private get fromSite(): string { return new URLSearchParams(window.location.search).get('from') ?? ''; } get isMobile(): boolean { return window.innerWidth < 768; } constructor() { this.loadSettings(); } private loadSettings(): void { // Fetch limits from /qr/settings. If the call fails, keep defaults. const url = `${QR_VITANOVA_API}/qr/settings`; this.http.get(url).subscribe({ next: (s) => { if (typeof s?.minAmount === 'number') this.minAmount.set(s.minAmount); if (typeof s?.maxAmount === 'number') this.maxAmount.set(s.maxAmount); } }); } createCheck(): void { const val = this.amount(); if (val !== null && val < this.minAmount()) { this.error.set(`${this.t('errors.invalid_amount')} (мин. ${this.minAmount()} ₽)`); return; } if (val !== null && val > this.maxAmount()) { this.error.set(`${this.t('errors.invalid_amount')} (макс. ${this.maxAmount().toLocaleString('ru')} ₽)`); return; } const partnerqrID = this.partnerqrID; if (!partnerqrID) { this.error.set(this.t('errors.lookup_failed')); return; } this.error.set(''); this.loading.set(true); const headers: Record = {}; if (this.authKey) headers['authorization-key'] = this.authKey; if (this.userId) headers['userid-value'] = this.userId; this.http .post( `${QR_VITANOVA_API}/qr`, { qrtype: 'QRDynamic', ...(val !== null ? { amount: val } : {}), currency: this.currency(), partnerqrID, qrDescription: this.note().trim(), Userid: this.userId, Reference: this.reference, RedirectUrl: `https://fastcheck.store?id=${partnerqrID}` }, { headers } ) .subscribe({ next: (res) => { this.loading.set(false); const qrId = res?.qrId ?? res?.nspkID ?? ''; // Real API uses 'nspkurl'; doc says 'Payload' — try both const nspkUrl = res?.nspkurl ?? res?.Payload; this.qrStatus.set(res?.status ?? ''); if (nspkUrl && this.isMobile) { window.location.href = nspkUrl; return; } if (qrId || nspkUrl) { const qrData = nspkUrl ? `https://api.qrserver.com/v1/create-qr-code/?size=256x256&margin=8&data=${encodeURIComponent(nspkUrl)}` : (res.qrUrl ?? null); this.qrImageUrl.set(qrData); if (qrId) this.startPolling(qrId); } else { this.error.set(this.t('errors.payment_failed')); } }, error: (err) => { this.loading.set(false); const msg: string | undefined = err?.error?.message; this.error.set(msg ?? this.t('errors.lookup_failed')); } }); } private startPolling(qrId: string): void { this.stopPolling(); this.qrPolling.set(true); this.pollHandle = setInterval(() => { this.http.get(`${QR_VITANOVA_API}/qr/dynamic/${encodeURIComponent(this.partnerqrID)}/${qrId}`) .subscribe({ next: (res) => { const st = res?.status ?? ''; this.qrStatus.set(st); if (st === 'COMPLETED' || st === 'APPROVED') { this.handlePaymentSuccess(res); } else if (st === 'REJECTED') { this.stopPolling(); this.error.set(this.t('errors.payment_failed')); this.qrImageUrl.set(null); } // REGISTERED / NEW / '' — keep polling }, error: () => { this.closeQr(); this.error.set('оплата не прошла'); } }); }, 5000); } private stopPolling(): void { if (this.pollHandle !== null) { clearInterval(this.pollHandle); this.pollHandle = null; } this.qrPolling.set(false); } onAmountChange(value: number | null): void { this.amount.set(value || null); if (value && value > 0) this.error.set(''); } onNoteChange(value: string): void { this.note.set(value); } private handlePaymentSuccess(paidQr: QrStatusResponse): void { this.stopPolling(); this.qrImageUrl.set(null); this.qrStatus.set(''); this.paymentDone.set(true); const id = this.partnerqrID; if (!id) { this.redirectToSource(); return; } this.http .post(`https://fastcheck.store/api/fastcheck/settings/${encodeURIComponent(id)}`, paidQr) .subscribe({ next: () => this.redirectToSource(id), error: () => this.redirectToSource(id) }); } private redirectToSource(id?: string): void { const withId = (target: string): string => { if (!id) return target; const normalizedTarget = /^https?:\/\//i.test(target) ? target : `https://${target}`; const url = new URL(normalizedTarget); url.searchParams.set('id', id); return url.toString(); }; const from = this.fromSite.trim(); const target = this.sites[from]; if (target) { window.location.href = withId(target); return; } if (window.history.length > 1) { window.history.back(); } } closeQr(): void { this.stopPolling(); this.qrImageUrl.set(null); this.qrStatus.set(''); this.paymentDone.set(false); } }