import { Component, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Router, RouterLink } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { FastcheckService } from '../../fastcheck.service'; import { FASTCHECK_API, 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 { minAmount?: number; maxAmount?: number; [key: string]: unknown; } interface CreateQrResponse { qrId?: string; payload?: string; // raw QR data string to encode qrUrl?: string; // pre-rendered image URL (if provided) Status?: boolean; [key: string]: unknown; } interface QrStatusResponse { qrStatus?: string; // e.g. "PAID" Status?: boolean; [key: string]: unknown; } interface CreateFastcheckResponse { fastcheck: string; expiration: string; code: string; Status: boolean; } /** Generate a v4-like UUID without crypto dependency. */ function generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); }); } @Component({ selector: 'app-create-page', imports: [FormsModule, RouterLink, TranslatePipe], templateUrl: './create-page.html', styleUrl: './create-page.scss' }) export class CreatePage { private http = inject(HttpClient); private store = inject(FastcheckService); private router = inject(Router); private i18n = inject(TranslationService); 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(100); note = signal(''); error = signal(''); loading = signal(false); settingsLoaded = 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); private pollHandle: ReturnType | null = null; private activeQrId = ''; /** Auth credentials passed by the host page as URL params. */ private get authKey(): string { return new URLSearchParams(window.location.search).get('auth-key') ?? ''; } private get userId(): string { return new URLSearchParams(window.location.search).get('user-id') ?? ''; } private get sessionId(): string { return new URLSearchParams(window.location.search).get('session') ?? ''; } private get reference(): string { return new URLSearchParams(window.location.search).get('ref') ?? window.location.hostname; } constructor() { this.loadSettings(); } private loadSettings(): void { this.http.get(`${QR_VITANOVA_API}/settings`).subscribe({ next: (s) => { if (typeof s?.minAmount === 'number') this.minAmount.set(s.minAmount); if (typeof s?.maxAmount === 'number') this.maxAmount.set(s.maxAmount); this.settingsLoaded.set(true); }, error: () => this.settingsLoaded.set(true) // proceed with defaults }); } createCheck(): void { const val = this.amount(); if (!val || val < this.minAmount()) { this.error.set(`${this.t('errors.invalid_amount')} (мин. ${this.minAmount()} ₽)`); return; } if (val > this.maxAmount()) { this.error.set(`${this.t('errors.invalid_amount')} (макс. ${this.maxAmount().toLocaleString('ru')} ₽)`); 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; const partnerqrID = generateUUID(); this.http .post( `${QR_VITANOVA_API}/qr`, { qrtype: 'QRDynamic', amount: val, currency: this.currency(), partnerqrID, qrDescription: this.note().trim(), Userid: this.userId, Reference: this.reference }, { headers } ) .subscribe({ next: (res) => { this.loading.set(false); if (res?.qrId || res?.payload || res?.qrUrl) { this.activeQrId = res.qrId ?? ''; // Use server-provided image URL or generate one from payload string. const qrData = res.qrUrl ?? (res.payload ? `https://api.qrserver.com/v1/create-qr-code/?size=240x240&margin=8&data=${encodeURIComponent(res.payload)}` : null); this.qrImageUrl.set(qrData); if (this.activeQrId) this.startPolling(this.activeQrId); } 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/${qrId}`) .subscribe({ next: (res) => { const paid = res?.qrStatus === 'PAID' || res?.Status === true; if (paid) { this.stopPolling(); this.createFastcheck(); } }, error: () => undefined }); }, 3000); } private stopPolling(): void { if (this.pollHandle !== null) { clearInterval(this.pollHandle); this.pollHandle = null; } this.qrPolling.set(false); } private createFastcheck(): void { const headers: Record = {}; if (this.sessionId) headers['Authorization'] = JSON.stringify({ sessionID: this.sessionId }); this.http .post( `${FASTCHECK_API}/fastcheck`, { amount: this.amount(), currency: this.currency() }, { headers } ) .subscribe({ next: (res) => { if (res?.fastcheck) { this.store.setCreated({ fastcheck: res.fastcheck, code: res.code, amount: this.amount(), expiration: res.expiration }); } this.router.navigate(['/']); }, error: () => this.router.navigate(['/']) }); } onAmountChange(value: number): void { this.amount.set(value); if (value > 0) this.error.set(''); } onNoteChange(value: string): void { this.note.set(value); } }