api change
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user