changes
This commit is contained in:
@@ -3,8 +3,32 @@ 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 } from '../../api';
|
||||
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;
|
||||
@@ -13,8 +37,13 @@ interface CreateFastcheckResponse {
|
||||
Status: boolean;
|
||||
}
|
||||
|
||||
type PaymentMethod = 'sbp' | 'wechat' | 'visa' | 'master';
|
||||
type Currency = 'RUB' | 'CNY' | 'USD' | 'EUR' | 'AMD';
|
||||
/** 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',
|
||||
@@ -26,19 +55,22 @@ export class CreatePage {
|
||||
private http = inject(HttpClient);
|
||||
private store = inject(FastcheckService);
|
||||
private router = inject(Router);
|
||||
private i18n = inject(TranslationService);
|
||||
|
||||
amount = signal<number>(10);
|
||||
private t(key: string): string { return this.i18n.translate(key); }
|
||||
|
||||
// Limits – updated from settings API on init.
|
||||
minAmount = signal<number>(30);
|
||||
maxAmount = signal<number>(200_000);
|
||||
|
||||
amount = signal<number>(100);
|
||||
note = signal<string>('');
|
||||
error = signal<string>('');
|
||||
loading = signal<boolean>(false);
|
||||
settingsLoaded = signal<boolean>(false);
|
||||
|
||||
payment = signal<PaymentMethod>('sbp');
|
||||
currency = signal<Currency>('RUB');
|
||||
|
||||
/** sessionID for the Authorization header. Comes from ?session=... or websession. */
|
||||
private get sessionId(): string {
|
||||
return new URLSearchParams(window.location.search).get('session') ?? '';
|
||||
}
|
||||
payment = signal<PaymentMethod>('sbp');
|
||||
|
||||
selectPayment(method: PaymentMethod, enabled: boolean): void {
|
||||
if (!enabled) return;
|
||||
@@ -50,10 +82,49 @@ export class CreatePage {
|
||||
this.currency.set(c);
|
||||
}
|
||||
|
||||
// QR display state
|
||||
qrImageUrl = signal<string | null>(null);
|
||||
qrPolling = signal<boolean>(false);
|
||||
private pollHandle: ReturnType<typeof setInterval> | 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<SettingsResponse>(`${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 <= 0) {
|
||||
this.error.set('Введите корректную сумму');
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -61,35 +132,97 @@ export class CreatePage {
|
||||
this.loading.set(true);
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
if (this.sessionId) {
|
||||
headers['Authorization'] = JSON.stringify({ sessionID: this.sessionId });
|
||||
}
|
||||
if (this.authKey) headers['authorization-key'] = this.authKey;
|
||||
if (this.userId) headers['userid-value'] = this.userId;
|
||||
|
||||
const partnerqrID = generateUUID();
|
||||
|
||||
this.http
|
||||
.post<CreateFastcheckResponse>(
|
||||
`${FASTCHECK_API}/fastcheck`,
|
||||
{ amount: val, currency: this.currency() },
|
||||
.post<CreateQrResponse>(
|
||||
`${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<QrStatusResponse>(`${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<string, string> = {};
|
||||
if (this.sessionId) headers['Authorization'] = JSON.stringify({ sessionID: this.sessionId });
|
||||
|
||||
this.http
|
||||
.post<CreateFastcheckResponse>(
|
||||
`${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: val,
|
||||
amount: this.amount(),
|
||||
expiration: res.expiration
|
||||
});
|
||||
this.router.navigate(['/']);
|
||||
} else {
|
||||
this.error.set('Не удалось создать платёж.');
|
||||
}
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
this.error.set('Ошибка при создании платежа. Попробуйте ещё раз.');
|
||||
}
|
||||
error: () => this.router.navigate(['/'])
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user