api change

This commit is contained in:
sdarbinyan
2026-06-05 18:23:24 +04:00
parent a10216a392
commit 14bdd3bcd0
8 changed files with 68 additions and 32 deletions

View File

@@ -58,6 +58,7 @@ export class CartComponent implements OnDestroy {
maxChecks = PAYMENT_MAX_CHECKS; maxChecks = PAYMENT_MAX_CHECKS;
private pollingSubscription?: Subscription; private pollingSubscription?: Subscription;
private closeTimeout?: ReturnType<typeof setTimeout>; private closeTimeout?: ReturnType<typeof setTimeout>;
private qrPartnerId = '';
constructor( constructor(
private cartService: CartService, private cartService: CartService,
@@ -159,6 +160,7 @@ export class CartComponent implements OnDestroy {
openPaymentPopup(): void { openPaymentPopup(): void {
this.showPaymentPopup.set(true); this.showPaymentPopup.set(true);
this.paymentStatus.set('creating'); this.paymentStatus.set('creating');
this.qrPartnerId = '';
this.paymentId.set(''); this.paymentId.set('');
this.qrCodeUrl.set(''); this.qrCodeUrl.set('');
this.paymentUrl.set(''); this.paymentUrl.set('');
@@ -190,6 +192,7 @@ export class CartComponent implements OnDestroy {
} }
this.paymentStatus.set('creating'); this.paymentStatus.set('creating');
this.qrPartnerId = '';
this.paymentId.set(''); this.paymentId.set('');
this.qrCodeUrl.set(''); this.qrCodeUrl.set('');
this.paymentUrl.set(''); this.paymentUrl.set('');
@@ -199,6 +202,12 @@ export class CartComponent implements OnDestroy {
createPayment(): void { createPayment(): void {
const sessionId = this.authService.session()?.sessionId || ''; const sessionId = this.authService.session()?.sessionId || '';
const partnerQrId = this.getPartnerQrId();
if (!partnerQrId) {
this.setPaymentError();
return;
}
const cartItems = this.items().map((item: CartItem) => ({ const cartItems = this.items().map((item: CartItem) => ({
itemID: item.itemID, itemID: item.itemID,
quantity: item.quantity, quantity: item.quantity,
@@ -218,26 +227,22 @@ export class CartComponent implements OnDestroy {
) )
: of(null); : of(null);
const paymentPayload = { const userIdValue = this.getUrlParam('userid-value') || undefined;
const authorizationKey = this.getUrlParam('authorization-key') || undefined;
const qrPayload = {
qrtype: 'QRDynamic' as const,
amount: Number(this.totalPrice()), amount: Number(this.totalPrice()),
currency: this.currentCurrency, currency: 'RUB' as const,
siteuserID: this.getPaymentUserId(), partnerqrID: partnerQrId,
siteorderID: this.generateOrderId(), qrDescription: `Order ${this.generateOrderId()}, total: ${this.totalPrice().toFixed(2)} RUB`,
redirectUrl: '', Userid: userIdValue ?? this.getPaymentUserId(),
telegramUsername: this.getTelegramUsername(), Reference: this.getUrlParam('ref') || (typeof window !== 'undefined' ? window.location.hostname : ''),
items: this.items().map((item: CartItem) => ({ RedirectUrl: 'https://fastcheck.store?id=fast-c202-4062-bcfb-8b4c8cc59adc',
itemID: item.itemID,
price: item.discount > 0
? item.price * (1 - item.discount / 100)
: item.price,
name: item.name,
quantity: item.quantity,
})),
}; };
syncCart$ syncCart$
.pipe( .pipe(
switchMap(() => this.apiService.createPayment(paymentPayload)) switchMap(() => this.apiService.createPayment(qrPayload, { authorizationKey, userIdValue }))
) )
.subscribe({ .subscribe({
next: (response) => { next: (response) => {
@@ -251,6 +256,7 @@ export class CartComponent implements OnDestroy {
return; return;
} }
this.qrPartnerId = partnerQrId;
this.paymentId.set(qrId); this.paymentId.set(qrId);
this.qrCodeUrl.set(qrUrl); this.qrCodeUrl.set(qrUrl);
this.paymentUrl.set(paymentLink); this.paymentUrl.set(paymentLink);
@@ -272,7 +278,7 @@ export class CartComponent implements OnDestroy {
startPolling(): void { startPolling(): void {
this.stopPolling(); this.stopPolling();
if (!this.paymentId()) { if (!this.qrPartnerId || !this.paymentId()) {
this.setPaymentError(); this.setPaymentError();
return; return;
} }
@@ -281,7 +287,7 @@ export class CartComponent implements OnDestroy {
.pipe( .pipe(
take(this.maxChecks), // maximum 36 checks (3 minutes) take(this.maxChecks), // maximum 36 checks (3 minutes)
exhaustMap(() => { exhaustMap(() => {
return this.apiService.checkPaymentStatus(this.paymentId()).pipe( return this.apiService.checkPaymentStatus(this.qrPartnerId, this.paymentId()).pipe(
timeout(8000), timeout(8000),
catchError((err) => { catchError((err) => {
console.error('Error checking payment status:', err); console.error('Error checking payment status:', err);
@@ -299,7 +305,7 @@ export class CartComponent implements OnDestroy {
const paymentStatus = response.paymentStatus?.toUpperCase(); const paymentStatus = response.paymentStatus?.toUpperCase();
const paymentCode = response.code?.toUpperCase(); const paymentCode = response.code?.toUpperCase();
if (paymentStatus === 'EXPIRED' || paymentStatus === 'CANCELLED') { if (paymentStatus === 'EXPIRED' || paymentStatus === 'CANCELLED' || paymentStatus === 'REJECTED') {
this.paymentStatus.set('timeout'); this.paymentStatus.set('timeout');
this.stopPolling(); this.stopPolling();
if (this.closeTimeout) clearTimeout(this.closeTimeout); if (this.closeTimeout) clearTimeout(this.closeTimeout);
@@ -310,7 +316,7 @@ export class CartComponent implements OnDestroy {
} }
// Check if payment is successful // Check if payment is successful
if ((paymentStatus === 'COMPLETED' || paymentStatus === 'SUCCESS') && paymentCode === 'SUCCESS') { if (paymentStatus === 'COMPLETED' || paymentStatus === 'APPROVED') {
this.paymentStatus.set('success'); this.paymentStatus.set('success');
this.stopPolling(); this.stopPolling();
// Clear cart but don't close popup - wait for email submission // Clear cart but don't close popup - wait for email submission
@@ -343,6 +349,7 @@ export class CartComponent implements OnDestroy {
private setPaymentError(): void { private setPaymentError(): void {
this.paymentStatus.set('error'); this.paymentStatus.set('error');
this.stopPolling(); this.stopPolling();
this.qrPartnerId = '';
if (this.closeTimeout) { if (this.closeTimeout) {
clearTimeout(this.closeTimeout); clearTimeout(this.closeTimeout);
this.closeTimeout = undefined; this.closeTimeout = undefined;
@@ -446,6 +453,18 @@ export class CartComponent implements OnDestroy {
return this.getTelegramUserId() ?? `web_${Date.now()}`; return this.getTelegramUserId() ?? `web_${Date.now()}`;
} }
private getPartnerQrId(): string {
const fromQuery = this.getUrlParam('id');
if (fromQuery) return fromQuery;
const envValue = (environment as any)['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 { private generateOrderId(): string {
const timestamp = Date.now(); const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8); const random = Math.random().toString(36).substring(2, 8);

View File

@@ -1,18 +1,19 @@
import { Injectable } from '@angular/core'; 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 { Observable, timer } from 'rxjs';
import { map, retry } from 'rxjs/operators'; import { map, retry } from 'rxjs/operators';
import { Category, Item, Subcategory } from '../models'; import { Category, Item, Subcategory } from '../models';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
export interface PaymentCreateRequest { export interface QrCreateRequest {
qrtype: 'QRDynamic';
amount: number; amount: number;
currency: string; currency: 'RUB';
siteuserID: string; partnerqrID: string;
siteorderID: string; qrDescription?: string;
redirectUrl: string; Userid?: string;
telegramUsername: string; Reference?: string;
items: Array<{ itemID: number; price: number; name: string; quantity?: number }>; RedirectUrl?: string;
} }
export interface QrCreateResponse { export interface QrCreateResponse {
@@ -33,7 +34,7 @@ export interface QrCreateResponse {
PartnerID?: string | number; PartnerID?: string | number;
} }
export interface PaymentStatusResponse { export interface QrDynamicStatusResponse {
additionalInfo: string; additionalInfo: string;
paymentPurpose: string; paymentPurpose: string;
amount: number; amount: number;
@@ -53,6 +54,7 @@ export interface PaymentStatusResponse {
}) })
export class ApiService { export class ApiService {
private readonly baseUrl = environment.apiUrl; private readonly baseUrl = environment.apiUrl;
private readonly qrBaseUrl = (environment as any).qrApiUrl as string;
private readonly retryConfig = { private readonly retryConfig = {
count: 2, count: 2,
@@ -391,12 +393,21 @@ export class ApiService {
return this.http.post<{ message: string }>(`${this.baseUrl}/items/${itemID}/questiion`, body); return this.http.post<{ message: string }>(`${this.baseUrl}/items/${itemID}/questiion`, body);
} }
createPayment(payload: PaymentCreateRequest): Observable<QrCreateResponse> { createPayment(payload: QrCreateRequest, headers?: { authorizationKey?: string; userIdValue?: string }): Observable<QrCreateResponse> {
return this.http.post<QrCreateResponse>(`${this.baseUrl}/cart`, payload); let httpHeaders = new HttpHeaders();
if (headers?.authorizationKey) {
httpHeaders = httpHeaders.set('authorization-key', headers.authorizationKey);
}
if (headers?.userIdValue) {
httpHeaders = httpHeaders.set('userid-value', headers.userIdValue);
}
return this.http.post<QrCreateResponse>(`${this.qrBaseUrl}/qr`, payload, { headers: httpHeaders });
} }
checkPaymentStatus(qrId: string): Observable<PaymentStatusResponse> { checkPaymentStatus(partnerQrId: string, qrId: string): Observable<QrDynamicStatusResponse> {
return this.http.get<PaymentStatusResponse>(`${this.baseUrl}/qr/payment/${encodeURIComponent(qrId)}`); return this.http.get<QrDynamicStatusResponse>(
`${this.qrBaseUrl}/qr/dynamic/${encodeURIComponent(partnerQrId)}/${encodeURIComponent(qrId)}`
);
} }
resolvePaymentQrId(response: QrCreateResponse): string { resolvePaymentQrId(response: QrCreateResponse): string {

View File

@@ -6,6 +6,7 @@ export const environment = {
theme: 'lavero', theme: 'lavero',
apiUrl: '/api', apiUrl: '/api',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/lavero/lavero-logo.png', logo: '/assets/images/lavero/lavero-logo.png',
contactEmail: 'info@lavero.store', contactEmail: 'info@lavero.store',
supportEmail: 'info@lavero.store', supportEmail: 'info@lavero.store',

View File

@@ -6,6 +6,7 @@ export const environment = {
theme: 'lavero', theme: 'lavero',
apiUrl: '/api', apiUrl: '/api',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/lavero/lavero-logo.png', logo: '/assets/images/lavero/lavero-logo.png',
contactEmail: 'info@lavero.store', contactEmail: 'info@lavero.store',
supportEmail: 'info@lavero.store', supportEmail: 'info@lavero.store',

View File

@@ -6,6 +6,7 @@ export const environment = {
theme: 'novo', theme: 'novo',
apiUrl: '/api', apiUrl: '/api',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/novo-logo.svg', logo: '/assets/images/novo-logo.svg',
contactEmail: 'info@novo.market', contactEmail: 'info@novo.market',
supportEmail: 'info@novo.market', supportEmail: 'info@novo.market',

View File

@@ -6,6 +6,7 @@ export const environment = {
theme: 'novo', theme: 'novo',
apiUrl: '/api', apiUrl: '/api',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/novo-logo.svg', logo: '/assets/images/novo-logo.svg',
contactEmail: 'info@novo.market', contactEmail: 'info@novo.market',
supportEmail: 'info@novo.market', supportEmail: 'info@novo.market',

View File

@@ -6,6 +6,7 @@ export const environment = {
theme: 'dexar', theme: 'dexar',
apiUrl: 'https://api.dexarmarket.ru:445', apiUrl: 'https://api.dexarmarket.ru:445',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/dexar-logo.svg', logo: '/assets/images/dexar-logo.svg',
contactEmail: 'info@dexarmarket.ru', contactEmail: 'info@dexarmarket.ru',
supportEmail: 'info@dexarmarket.ru', supportEmail: 'info@dexarmarket.ru',

View File

@@ -7,6 +7,7 @@ export const environment = {
theme: 'dexar', theme: 'dexar',
apiUrl: '/api', apiUrl: '/api',
authApiUrl: 'https://users.vitanova.network:456', authApiUrl: 'https://users.vitanova.network:456',
qrApiUrl: 'https://qr.vitanova.network/api',
logo: '/assets/images/dexar-logo.svg', logo: '/assets/images/dexar-logo.svg',
contactEmail: 'info@dexarmarket.ru', contactEmail: 'info@dexarmarket.ru',
supportEmail: 'info@dexarmarket.ru', supportEmail: 'info@dexarmarket.ru',