import { Component, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { FastcheckService } from '../../fastcheck.service'; import { FASTCHECK_API } from '../../api'; import { TranslatePipe } from '../../translate/translate.pipe'; import { TranslationService } from '../../translate/translation.service'; interface WebSessionResponse { sessionId: string; userId: string; expires: string; userSessionId: string; Status: boolean; } interface CheckFastcheckResponse { id: string; code: string; owneID: string; amount: number; currency: string; createdAt: string; creattransactionID: string; firedAT: string; firetransactionID: string; } @Component({ selector: 'app-fastcheck-page', imports: [FormsModule, TranslatePipe], templateUrl: './fastcheck-page.html', styleUrl: './fastcheck-page.scss' }) export class FastcheckPage { private http = inject(HttpClient); private store = inject(FastcheckService); private i18n = inject(TranslationService); private t(key: string): string { return this.i18n.translate(key); } // Telegram bot used for the sign-in deep link. private readonly telegramBot = 'DexarSupport_bot'; fastcheckNumber = signal(''); fastcheckAmount = signal(null); fastcheckCode = signal(''); codeEnabled = signal(false); error = signal(''); amountLoading = signal(false); // Pass-through partner id from ?id= used by the "New" button link. partnerId = signal(''); newQrUrl = computed(() => { const id = this.partnerId(); return id ? `https://qr.vitanova.network/?id=${encodeURIComponent(id)}` : 'https://qr.vitanova.network/'; }); popupOpen = signal(false); popupLoading = signal(false); popupError = signal(''); webSessionId = signal(''); paid = signal(false); loginOnly = signal(false); sessionToken = signal(localStorage.getItem('fc_session') ?? ''); private pollHandle: ReturnType | null = null; private lastLookedUpNumber = ''; canPay = computed(() => { const digits = this.fastcheckNumber().replace(/\D/g, ''); const codeDigits = this.fastcheckCode().replace(/\D/g, ''); return digits.length === 18 && codeDigits.length === 6 && this.codeEnabled() && !this.amountLoading(); }); telegramLink = computed(() => { const sid = this.webSessionId(); return sid ? `https://t.me/${this.telegramBot}?start=${encodeURIComponent(sid)}` : `https://t.me/${this.telegramBot}`; }); qrUrl = computed(() => { const link = this.telegramLink(); return `https://api.qrserver.com/v1/create-qr-code/?size=240x240&margin=8&data=${encodeURIComponent(link)}`; }); get isMobile(): boolean { return typeof window !== 'undefined' && window.innerWidth < 768; } constructor() { // Pull autofill data: prefer router navigation state, fall back to service. const navState = typeof window !== 'undefined' ? (window.history?.state ?? {}) : {}; const created = (navState?.fastcheck) ? { fastcheck: navState.fastcheck, code: navState.code ?? '', amount: navState.amount ?? null, expiration: navState.expiration } : this.store.consume(); if (created) { this.fastcheckNumber.set(created.fastcheck); this.fastcheckAmount.set(created.amount); this.fastcheckCode.set(created.code); this.codeEnabled.set(true); } const params = new URLSearchParams(window.location.search); // ?id= — just propagated to the "New" button URL. this.partnerId.set(params.get('id') ?? ''); // ?iid=xxxxxx-xxxxxx-xxxxxx — auto-fill and trigger lookup const iidParam = params.get('iid') ?? ''; if (iidParam && !created) { const digits = iidParam.replace(/\D/g, '').slice(0, 18); const groups: string[] = []; for (let i = 0; i < digits.length; i += 6) groups.push(digits.slice(i, i + 6)); const masked = groups.join('-'); this.fastcheckNumber.set(masked); if (digits.length === 18) this.lookupFastcheck(masked); } } pay(): void { if (!this.canPay()) { return; } this.error.set(''); this.loginOnly.set(false); this.openPopup(); } private openPopup(): void { this.popupOpen.set(true); this.popupError.set(''); this.paid.set(false); this.popupLoading.set(true); const existing = this.sessionToken(); if (existing) { this.http.get(`${FASTCHECK_API}/websession/${existing}`).subscribe({ next: (res) => { if (res?.Status) { this.popupLoading.set(false); this.webSessionId.set(existing); if (this.loginOnly()) { this.paid.set(true); } else { this.acceptFastcheck(existing); } } else { this.sessionToken.set(''); this.createNewSession(); } }, error: () => { this.sessionToken.set(''); this.createNewSession(); } }); return; } this.createNewSession(); } private createNewSession(): void { this.http.get(`${FASTCHECK_API}/websession`).subscribe({ next: (res) => { this.popupLoading.set(false); this.webSessionId.set(res.sessionId); if (this.isMobile) { window.location.href = `https://t.me/${this.telegramBot}?start=${encodeURIComponent(res.sessionId)}`; } else { this.startPolling(res.sessionId); } }, error: () => { this.popupLoading.set(false); this.popupError.set(this.t('errors.session_failed')); } }); } closePopup(): void { this.popupOpen.set(false); this.stopPolling(); if (this.loginOnly() && this.paid()) { // Keep session alive — user is logged in, preserve token for next action. const tok = this.webSessionId(); localStorage.setItem('fc_session', tok); this.sessionToken.set(tok); } else if (this.webSessionId()) { // Best-effort logout; ignore errors. this.http .request('DELETE', `${FASTCHECK_API}/websession/${this.webSessionId()}`, { body: { sessionId: this.webSessionId() } }) .subscribe({ error: () => undefined }); localStorage.removeItem('fc_session'); this.sessionToken.set(''); } this.webSessionId.set(''); } private startPolling(sessionId: string): void { this.stopPolling(); this.pollHandle = setInterval(() => { this.http .get(`${FASTCHECK_API}/websession/${sessionId}`) .subscribe({ next: (res) => { if (res?.Status) { this.stopPolling(); if (this.loginOnly()) { this.paid.set(true); } else { this.acceptFastcheck(sessionId); } } }, error: () => undefined }); }, 3000); } private stopPolling(): void { if (this.pollHandle !== null) { clearInterval(this.pollHandle); this.pollHandle = null; } } private acceptFastcheck(sessionId: string): void { this.popupLoading.set(true); this.http .post( `${FASTCHECK_API}/fastcheck`, { fastcheck: this.fastcheckNumber().trim(), code: this.fastcheckCode().trim() }, { headers: { Authorization: JSON.stringify({ sessionID: sessionId }) } } ) .subscribe({ next: () => { this.popupLoading.set(false); this.paid.set(true); // Fire DELETE to mark fastcheck as consumed on the merchant side. this.http .delete(`${FASTCHECK_API}/fastcheck/${encodeURIComponent(this.fastcheckNumber())}`) .subscribe({ error: () => undefined }); this.fireMerchantCallback(); }, error: () => { this.popupLoading.set(false); this.popupError.set(this.t('errors.payment_failed')); } }); } private fireMerchantCallback(): void { const params = new URLSearchParams(window.location.search); const returnUrl = params.get('return_url'); if (returnUrl) { setTimeout(() => { window.location.href = `${returnUrl}${returnUrl.includes('?') ? '&' : '?'}fastcheck=${encodeURIComponent( this.fastcheckNumber() )}&status=ok`; }, 1500); } } onAmountChange(value: number | null): void { this.fastcheckAmount.set(value); } /** Mask fastcheck number as XXXXXX-XXXXXX-XXXXXX, allow only digits. */ onNumberChange(raw: string): void { const digits = (raw ?? '').replace(/\D/g, '').slice(0, 18); const groups: string[] = []; for (let i = 0; i < digits.length; i += 6) { groups.push(digits.slice(i, i + 6)); } const masked = groups.join('-'); this.fastcheckNumber.set(masked); this.error.set(''); if (digits.length < 18 && this.lastLookedUpNumber) { this.fastcheckAmount.set(null); this.codeEnabled.set(false); this.lastLookedUpNumber = ''; } if (digits.length === 18 && masked !== this.lastLookedUpNumber) { this.lookupFastcheck(masked); } } /** Allow only digits, max 6, in the code field. */ onCodeChange(raw: string): void { const digits = (raw ?? '').replace(/\D/g, '').slice(0, 6); this.fastcheckCode.set(digits); this.error.set(''); } private lookupFastcheck(number: string): void { this.lastLookedUpNumber = number; this.amountLoading.set(true); this.fastcheckAmount.set(null); this.codeEnabled.set(false); // API doc: GET /fastcheck/ this.http .get(`${FASTCHECK_API}/fastcheck/${number}`) .subscribe({ next: (res) => { this.amountLoading.set(false); if (res?.id) { this.fastcheckAmount.set(typeof res.amount === 'number' ? res.amount : null); this.codeEnabled.set(true); } else { this.error.set(this.t('errors.not_found')); this.lastLookedUpNumber = ''; } }, error: (err) => { this.amountLoading.set(false); const serverMsg: string | undefined = err?.error?.message; this.error.set(serverMsg ?? this.t('errors.lookup_failed')); this.lastLookedUpNumber = ''; } }); } shareByTelegram(): void { this.loginOnly.set(true); this.openPopup(); } }