From c6bc05560ecb0d17b07fdbfdc8565b01a69e6902 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Tue, 2 Jun 2026 01:46:12 +0400 Subject: [PATCH] change --- src/app/interceptors/mock-data.interceptor.ts | 28 ++++ src/app/pages/cart/cart.component.ts | 92 ++++++++----- src/app/services/api.service.ts | 121 ++++++------------ 3 files changed, 128 insertions(+), 113 deletions(-) diff --git a/src/app/interceptors/mock-data.interceptor.ts b/src/app/interceptors/mock-data.interceptor.ts index 14ede6a..3f821ac 100644 --- a/src/app/interceptors/mock-data.interceptor.ts +++ b/src/app/interceptors/mock-data.interceptor.ts @@ -800,6 +800,14 @@ export const mockDataInterceptor: HttpInterceptorFn = (req, next) => { }, 300); } + // ── GET /qr/settings + if (url.endsWith('/qr/settings') && req.method === 'GET') { + return respond({ + minAmount: 30, + maxAmount: 200000, + }); + } + // ── POST /qr (create payment QR directly) if (url.endsWith('/qr') && req.method === 'POST') { return respond({ @@ -858,6 +866,26 @@ export const mockDataInterceptor: HttpInterceptorFn = (req, next) => { }, 500); } + // ── GET /qr/dynamic/:partnerID/:qrID (dynamic QR status) + if (url.match(/\/qr\/dynamic\/[^/]+\/[^/]+$/) && req.method === 'GET') { + return respond({ + status: 'APPROVED', + paymentStatus: 'COMPLETED', + code: 'SUCCESS', + amount: 0, + currency: 'RUB', + qrId: 'mock', + transactionId: 999, + transactionDate: new Date().toISOString(), + additionalInfo: '', + paymentPurpose: '', + createDate: new Date().toISOString(), + order: 'mock-order', + qrExpirationDate: new Date().toISOString(), + phoneNumber: '+70000000000' + }, 500); + } + // ── GET /qr/payment/:qrId (legacy/direct payment status) if (url.match(/\/qr\/payment\/[^/]+$/) && req.method === 'GET') { return respond({ diff --git a/src/app/pages/cart/cart.component.ts b/src/app/pages/cart/cart.component.ts index 0948da1..4e41a2f 100644 --- a/src/app/pages/cart/cart.component.ts +++ b/src/app/pages/cart/cart.component.ts @@ -58,7 +58,7 @@ export class CartComponent implements OnDestroy { maxChecks = PAYMENT_MAX_CHECKS; private pollingSubscription?: Subscription; private closeTimeout?: ReturnType; - private paymentBackend: 'websession' | 'qr' | 'cart' | null = null; + private qrPartnerId = ''; constructor( private cartService: CartService, @@ -160,7 +160,7 @@ export class CartComponent implements OnDestroy { openPaymentPopup(): void { this.showPaymentPopup.set(true); this.paymentStatus.set('creating'); - this.paymentBackend = null; + this.qrPartnerId = ''; this.paymentId.set(''); this.qrCodeUrl.set(''); this.paymentUrl.set(''); @@ -192,7 +192,7 @@ export class CartComponent implements OnDestroy { } this.paymentStatus.set('creating'); - this.paymentBackend = null; + this.qrPartnerId = ''; this.paymentId.set(''); this.qrCodeUrl.set(''); this.paymentUrl.set(''); @@ -202,6 +202,12 @@ export class CartComponent implements OnDestroy { createPayment(): void { const sessionId = this.authService.session()?.sessionId || ''; + const partnerQrId = this.getPartnerQrId(); + if (!partnerQrId) { + this.setPaymentError(); + return; + } + const cartItems = this.items().map((item: CartItem) => ({ itemID: item.itemID, quantity: item.quantity, @@ -212,23 +218,6 @@ export class CartComponent implements OnDestroy { : item.price, })); - const paymentData = { - amount: this.totalPrice(), - currency: this.currentCurrency, - siteuserID: this.getPaymentUserId(), - siteorderID: this.generateOrderId(), - redirectUrl: '', - telegramUsername: this.getTelegramUsername(), - items: this.items().map((item: CartItem) => ({ - itemID: item.itemID, - price: item.discount > 0 - ? item.price * (1 - item.discount / 100) - : item.price, - name: item.name, - quantity: item.quantity, - })) - }; - const syncCart$ = sessionId ? this.apiService.addToCart(sessionId, cartItems).pipe( catchError((err) => { @@ -238,25 +227,45 @@ export class CartComponent implements OnDestroy { ) : of(null); + const userIdValue = this.getUrlParam('userid-value') || undefined; + const authorizationKey = this.getUrlParam('authorization-key') || undefined; + const qrPayload = { + qrtype: 'QRDynamic' as const, + amount: Number(this.totalPrice()), + currency: 'RUB' as const, + partnerqrID: partnerQrId, + qrDescription: `Order ${this.generateOrderId()}, total: ${this.totalPrice().toFixed(2)} ${this.currentCurrency}`, + Userid: userIdValue ?? this.getPaymentUserId(), + Reference: this.getUrlParam('ref') || (typeof window !== 'undefined' ? window.location.hostname : ''), + RedirectUrl: typeof window !== 'undefined' ? window.location.origin : undefined, + }; + syncCart$ .pipe( - switchMap(() => this.apiService.createPayment(paymentData, sessionId || undefined)) + switchMap(() => this.apiService.createPayment(qrPayload, { authorizationKey, userIdValue })) ) .subscribe({ next: (response) => { - const qrId = this.apiService.resolvePaymentQrId(response.response); - const qrUrl = this.apiService.resolvePaymentQrUrl(response.response); + const qrId = this.apiService.resolvePaymentQrId(response); + const qrUrl = this.apiService.resolvePaymentQrUrl(response); + const paymentLink = this.apiService.resolvePaymentLink(response); if (!qrId || !qrUrl) { - console.error('Payment response missing qr fields:', response.response); + console.error('Payment response missing qr fields:', response); this.setPaymentError(); return; } - this.paymentBackend = response.backend; + this.qrPartnerId = partnerQrId; this.paymentId.set(qrId); this.qrCodeUrl.set(qrUrl); - this.paymentUrl.set(this.apiService.resolvePaymentLink(response.response)); + this.paymentUrl.set(paymentLink); + + if (paymentLink && typeof window !== 'undefined' && window.innerWidth < 768) { + window.location.href = paymentLink; + return; + } + this.paymentStatus.set('waiting'); this.startPolling(); }, @@ -269,15 +278,16 @@ export class CartComponent implements OnDestroy { startPolling(): void { this.stopPolling(); + if (!this.qrPartnerId || !this.paymentId()) { + this.setPaymentError(); + return; + } + this.pollingSubscription = interval(PAYMENT_POLL_INTERVAL_MS) .pipe( take(this.maxChecks), // maximum 36 checks (3 minutes) switchMap(() => { - const sessionId = this.authService.session()?.sessionId || ''; - return this.apiService.checkPaymentStatus(this.paymentId(), { - sessionId: sessionId || undefined, - backend: this.paymentBackend ?? undefined, - }).pipe( + return this.apiService.checkPaymentStatus(this.qrPartnerId, this.paymentId()).pipe( catchError((err) => { console.error('Error checking payment status:', err); return of(null); @@ -338,7 +348,7 @@ export class CartComponent implements OnDestroy { private setPaymentError(): void { this.paymentStatus.set('error'); this.stopPolling(); - this.paymentBackend = null; + this.qrPartnerId = ''; if (this.closeTimeout) { clearTimeout(this.closeTimeout); this.closeTimeout = undefined; @@ -442,6 +452,24 @@ export class CartComponent implements OnDestroy { return this.getTelegramUserId() ?? `web_${Date.now()}`; } + private getPartnerQrId(): string { + const fromQuery = this.getUrlParam('id'); + if (fromQuery) { + return fromQuery; + } + + const envValue = (environment as unknown as Record)['partnerqrID']; + return typeof envValue === 'string' ? envValue : ''; + } + + private getUrlParam(name: string): string | null { + if (typeof window === 'undefined') { + return null; + } + + return new URLSearchParams(window.location.search).get(name); + } + private generateOrderId(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index 749b0cd..fbefdd6 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -1,48 +1,41 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Observable, timer } from 'rxjs'; -import { catchError, map, retry } from 'rxjs/operators'; +import { map, retry } from 'rxjs/operators'; import { Category, Item, Subcategory } from '../models'; import { environment } from '../../environments/environment'; -type PaymentBackend = 'websession' | 'qr' | 'cart'; - -interface PaymentRequestItem { - itemID: number; - price: number; - name: string; - quantity?: number; -} - -export interface PaymentRequest { +export interface QrCreateRequest { + qrtype: 'QRDynamic'; amount: number; - currency: string; - siteuserID: string; - siteorderID: string; - redirectUrl: string; - telegramUsername: string; - items: PaymentRequestItem[]; + currency: 'RUB'; + partnerqrID: string; + qrDescription?: string; + Userid?: string; + Reference?: string; + RedirectUrl?: string; } -export interface PaymentCreateResponse { +export interface QrCreateResponse { qrId?: string; qrID?: string; + nspkID?: string; + nspkId?: string; + nspkurl?: string; + status?: string; qrStatus?: string; qrExpirationDate?: string; payload?: string; Payload?: string; qrUrl?: string; + partnerqrID?: string | number; partnerID?: string | number; partnerId?: string | number; PartnerID?: string | number; } -export interface PaymentCreateResult { - backend: PaymentBackend; - response: PaymentCreateResponse; -} - -export interface PaymentStatusResponse { +export interface QrStatusResponse { + status?: string; additionalInfo: string; paymentPurpose: string; amount: number; @@ -400,74 +393,40 @@ export class ApiService { return this.http.post<{ message: string }>(`${this.baseUrl}/items/${itemID}/questiion`, body); } - createPayment(paymentData: PaymentRequest, sessionId?: string): Observable { - const directQrPayment$ = this.http.post(`${this.baseUrl}/qr`, paymentData).pipe( - map(response => ({ backend: 'qr' as const, response })) - ); - const legacyCartPayment$ = this.http.post(`${this.baseUrl}/cart`, paymentData).pipe( - map(response => ({ backend: 'cart' as const, response })) - ); - const directPayment$ = directQrPayment$.pipe( - catchError(() => legacyCartPayment$) - ); + createPayment(payload: QrCreateRequest, headers?: { authorizationKey?: string; userIdValue?: string }): Observable { + let httpHeaders = new HttpHeaders(); - if (!sessionId) { - return directPayment$; + if (headers?.authorizationKey) { + httpHeaders = httpHeaders.set('authorization-key', headers.authorizationKey); + } + if (headers?.userIdValue) { + httpHeaders = httpHeaders.set('userid-value', headers.userIdValue); } - return this.http.post(`${this.baseUrl}/websession/${sessionId}/qr`, {}).pipe( - map(response => ({ backend: 'websession' as const, response })), - catchError(() => directPayment$) + return this.http.post(`${this.baseUrl}/qr`, payload, { headers: httpHeaders }); + } + + checkPaymentStatus(partnerQrId: string, qrId: string): Observable { + return this.http.get( + `${this.baseUrl}/qr/dynamic/${encodeURIComponent(partnerQrId)}/${encodeURIComponent(qrId)}` ); } - checkPaymentStatus(qrId: string, options?: { sessionId?: string; backend?: PaymentBackend }): Observable { - const legacyStatus$ = this.http.get(`${this.baseUrl}/qr/payment/${qrId}`); - - if (options?.backend === 'websession' && options.sessionId) { - return this.http.get(`${this.baseUrl}/websession/${options.sessionId}/${qrId}`).pipe( - catchError(() => legacyStatus$) - ); - } - - if (options?.backend === 'qr' || options?.backend === 'cart') { - return legacyStatus$; - } - - if (options?.sessionId) { - return this.http.get(`${this.baseUrl}/websession/${options.sessionId}/${qrId}`).pipe( - catchError(() => legacyStatus$) - ); - } - - return legacyStatus$; + resolvePaymentQrId(response: QrCreateResponse): string { + return response.qrId ?? response.qrID ?? response.nspkID ?? response.nspkId ?? ''; } - resolvePaymentQrId(response: PaymentCreateResponse): string { - return response.qrId ?? response.qrID ?? ''; + resolvePaymentLink(response: QrCreateResponse): string { + return response.nspkurl ?? response.Payload ?? response.payload ?? response.qrUrl ?? ''; } - resolvePaymentQrUrl(response: PaymentCreateResponse): string { - if (response.qrUrl) { - return response.qrUrl; + resolvePaymentQrUrl(response: QrCreateResponse): string { + const paymentLink = this.resolvePaymentLink(response); + if (paymentLink) { + return `https://api.qrserver.com/v1/create-qr-code/?size=256x256&margin=8&data=${encodeURIComponent(paymentLink)}`; } - const qrId = this.resolvePaymentQrId(response); - const partnerId = response.partnerID ?? response.partnerId ?? response.PartnerID; - - if (!qrId) { - return ''; - } - - if (partnerId != null) { - return `${this.baseUrl}/qr/dynamic/${encodeURIComponent(String(partnerId))}/${encodeURIComponent(qrId)}`; - } - - return `${this.baseUrl}/qr/static/${encodeURIComponent(qrId)}`; - } - - resolvePaymentLink(response: PaymentCreateResponse): string { - return response.payload ?? response.Payload ?? this.resolvePaymentQrUrl(response); + return response.qrUrl ?? ''; } submitPurchaseEmail(emailData: {