Files
qr_vitanova/src/app/pages/create-page/create-page.ts

226 lines
6.8 KiB
TypeScript
Raw Normal View History

2026-04-30 01:17:17 +04:00
import { Component, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
2026-05-07 00:13:45 +04:00
import { QR_VITANOVA_API } from '../../api';
2026-05-04 23:56:38 +04:00
import { TranslatePipe } from '../../translate/translate.pipe';
2026-05-05 00:52:03 +04:00
import { TranslationService } from '../../translate/translation.service';
type PaymentMethod = 'sbp';
type Currency = 'RUB';
interface SettingsResponse {
minAmount?: number;
maxAmount?: number;
}
interface CreateQrResponse {
qrId?: string;
nspkID?: string;
Payload?: string; // per API doc (capital P)
nspkurl?: string; // actual field name in real responses
qrUrl?: string;
2026-05-05 11:53:52 +04:00
status?: string; // e.g. "REGISTERED"
2026-05-05 00:52:03 +04:00
}
interface QrStatusResponse {
2026-05-05 11:53:52 +04:00
status?: string; // "REGISTERED" | "NEW" | "APPROVED" | "REJECTED" | "COMPLETED"
2026-05-05 00:52:03 +04:00
}
2026-04-30 01:17:17 +04:00
@Component({
selector: 'app-create-page',
2026-05-07 00:13:45 +04:00
imports: [FormsModule, TranslatePipe],
2026-04-30 01:17:17 +04:00
templateUrl: './create-page.html',
styleUrl: './create-page.scss'
})
export class CreatePage {
private http = inject(HttpClient);
2026-05-05 00:52:03 +04:00
private i18n = inject(TranslationService);
private t(key: string): string { return this.i18n.translate(key); }
2026-04-30 01:17:17 +04:00
2026-05-05 00:52:03 +04:00
// Limits updated from settings API on init.
minAmount = signal<number>(30);
maxAmount = signal<number>(200_000);
amount = signal<number | null>(null);
2026-04-30 01:17:17 +04:00
note = signal<string>('');
error = signal<string>('');
loading = signal<boolean>(false);
currency = signal<Currency>('RUB');
2026-05-05 00:52:03 +04:00
payment = signal<PaymentMethod>('sbp');
2026-04-30 01:17:17 +04:00
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);
}
2026-05-05 00:52:03 +04:00
// QR display state
qrImageUrl = signal<string | null>(null);
qrPolling = signal<boolean>(false);
2026-05-05 11:53:52 +04:00
qrStatus = signal<string>('');
2026-05-07 00:13:45 +04:00
paymentDone = signal<boolean>(false);
2026-05-05 00:52:03 +04:00
private pollHandle: ReturnType<typeof setInterval> | 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') ?? '';
2026-05-05 00:52:03 +04:00
}
private get userId(): string {
return new URLSearchParams(window.location.search).get('userid-value') ?? '';
2026-05-05 00:52:03 +04:00
}
private get reference(): string {
return new URLSearchParams(window.location.search).get('ref') ?? window.location.hostname;
}
2026-05-08 18:58:25 +04:00
private get partnerqrID(): string {
return new URLSearchParams(window.location.search).get('id') ?? '';
}
2026-05-05 00:52:03 +04:00
get isMobile(): boolean {
return window.innerWidth < 768;
}
2026-05-05 00:52:03 +04:00
constructor() {
this.loadSettings();
}
private loadSettings(): void {
2026-05-08 23:42:42 +04:00
// The `id` query param is the user's id. Fetch per-user amount limits.
// If the call fails or omits a value, keep current defaults.
const userId = this.partnerqrID;
2026-05-08 23:55:07 +04:00
if (!userId) return;
2026-05-08 23:42:42 +04:00
const url = `${QR_VITANOVA_API}/settings?id=${encodeURIComponent(userId)}`;
this.http.get<SettingsResponse>(url).subscribe({
2026-05-05 00:52:03 +04:00
next: (s) => {
if (typeof s?.minAmount === 'number') this.minAmount.set(s.minAmount);
if (typeof s?.maxAmount === 'number') this.maxAmount.set(s.maxAmount);
2026-05-08 23:55:07 +04:00
}
2026-05-05 00:52:03 +04:00
});
}
2026-04-30 01:17:17 +04:00
createCheck(): void {
const val = this.amount();
if (val !== null && val < this.minAmount()) {
2026-05-05 00:52:03 +04:00
this.error.set(`${this.t('errors.invalid_amount')} (мин. ${this.minAmount()} ₽)`);
return;
}
if (val !== null && val > this.maxAmount()) {
2026-05-05 00:52:03 +04:00
this.error.set(`${this.t('errors.invalid_amount')} (макс. ${this.maxAmount().toLocaleString('ru')} ₽)`);
2026-04-30 01:17:17 +04:00
return;
}
2026-05-08 18:58:25 +04:00
const partnerqrID = this.partnerqrID;
if (!partnerqrID) {
this.error.set(this.t('errors.lookup_failed'));
return;
}
2026-04-30 01:17:17 +04:00
this.error.set('');
this.loading.set(true);
const headers: Record<string, string> = {};
2026-05-05 00:52:03 +04:00
if (this.authKey) headers['authorization-key'] = this.authKey;
if (this.userId) headers['userid-value'] = this.userId;
this.http
.post<CreateQrResponse>(
`${QR_VITANOVA_API}/qr`,
{
qrtype: 'QRDynamic',
...(val !== null ? { amount: val } : {}),
2026-05-05 00:52:03 +04:00
currency: this.currency(),
partnerqrID,
qrDescription: this.note().trim(),
Userid: this.userId,
Reference: this.reference
},
{ 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;
2026-05-05 11:53:52 +04:00
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);
2026-05-05 00:52:03 +04:00
this.qrImageUrl.set(qrData);
if (qrId) this.startPolling(qrId);
2026-05-05 00:52:03 +04:00
} 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(() => {
2026-05-13 16:47:51 +04:00
this.http.get<QrStatusResponse>(`${QR_VITANOVA_API}/qr/dynamic/${encodeURIComponent(this.partnerqrID)}/${qrId}`)
2026-05-05 00:52:03 +04:00
.subscribe({
next: (res) => {
2026-05-05 11:53:52 +04:00
const st = res?.status ?? '';
this.qrStatus.set(st);
if (st === 'COMPLETED' || st === 'APPROVED') {
2026-05-05 00:52:03 +04:00
this.stopPolling();
2026-05-07 00:13:45 +04:00
this.paymentDone.set(true);
2026-05-05 11:53:52 +04:00
} else if (st === 'REJECTED') {
this.stopPolling();
this.error.set(this.t('errors.payment_failed'));
this.qrImageUrl.set(null);
2026-05-05 00:52:03 +04:00
}
2026-05-05 11:53:52 +04:00
// REGISTERED / NEW / '' — keep polling
2026-05-05 00:52:03 +04:00
},
2026-05-13 14:35:50 +04:00
error: () => {
2026-05-13 14:42:02 +04:00
this.closeQr();
this.error.set('оплата не прошла');
2026-05-13 14:35:50 +04:00
}
2026-05-05 00:52:03 +04:00
});
2026-05-05 11:53:52 +04:00
}, 5000);
2026-05-05 00:52:03 +04:00
}
private stopPolling(): void {
if (this.pollHandle !== null) {
clearInterval(this.pollHandle);
this.pollHandle = null;
2026-04-30 01:17:17 +04:00
}
2026-05-05 00:52:03 +04:00
this.qrPolling.set(false);
}
onAmountChange(value: number | null): void {
this.amount.set(value || null);
if (value && value > 0) this.error.set('');
2026-04-30 01:17:17 +04:00
}
onNoteChange(value: string): void {
this.note.set(value);
}
closeQr(): void {
2026-05-08 23:55:07 +04:00
this.stopPolling();
this.qrImageUrl.set(null);
2026-05-05 11:53:52 +04:00
this.qrStatus.set('');
2026-05-07 00:13:45 +04:00
this.paymentDone.set(false);
}
2026-04-30 01:17:17 +04:00
}