api change

This commit is contained in:
sdarbinyan
2026-06-02 00:57:36 +04:00
parent 1bec150822
commit 63b0e18396
10 changed files with 373 additions and 88 deletions

View File

@@ -4,8 +4,8 @@ import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { CartService, ApiService, LanguageService, AuthService } from '../../services';
import { Item, CartItem } from '../../models';
import { interval, Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { interval, of, Subscription } from 'rxjs';
import { catchError, switchMap, take } from 'rxjs/operators';
import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-cart-icon.component';
import { TelegramLoginComponent } from '../../components/telegram-login/telegram-login.component';
import { environment } from '../../../environments/environment';
@@ -39,7 +39,7 @@ export class CartComponent implements OnDestroy {
// Payment popup states
showPaymentPopup = signal<boolean>(false);
paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout'>('creating');
paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout' | 'error'>('creating');
qrCodeUrl = signal<string>('');
paymentUrl = signal<string>('');
paymentId = signal<string>('');
@@ -58,6 +58,7 @@ export class CartComponent implements OnDestroy {
maxChecks = PAYMENT_MAX_CHECKS;
private pollingSubscription?: Subscription;
private closeTimeout?: ReturnType<typeof setTimeout>;
private paymentBackend: 'websession' | 'qr' | 'cart' | null = null;
constructor(
private cartService: CartService,
@@ -159,6 +160,11 @@ export class CartComponent implements OnDestroy {
openPaymentPopup(): void {
this.showPaymentPopup.set(true);
this.paymentStatus.set('creating');
this.paymentBackend = null;
this.paymentId.set('');
this.qrCodeUrl.set('');
this.paymentUrl.set('');
this.linkCopied.set(false);
this.userEmail.set('');
this.userPhone.set('');
this.emailTouched.set(false);
@@ -179,14 +185,23 @@ export class CartComponent implements OnDestroy {
}
}
createPayment(): void {
const sessionId = this.authService.session()?.sessionId || '';
if (!sessionId) {
this.paymentStatus.set('timeout');
return;
retryPayment(): void {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = undefined;
}
// First sync cart items to server via websession, then create QR
this.paymentStatus.set('creating');
this.paymentBackend = null;
this.paymentId.set('');
this.qrCodeUrl.set('');
this.paymentUrl.set('');
this.linkCopied.set(false);
this.createPayment();
}
createPayment(): void {
const sessionId = this.authService.session()?.sessionId || '';
const cartItems = this.items().map((item: CartItem) => ({
itemID: item.itemID,
quantity: item.quantity,
@@ -197,35 +212,59 @@ export class CartComponent implements OnDestroy {
: item.price,
}));
this.apiService.addToCart(sessionId, cartItems).subscribe({
next: () => {
this.apiService.createPayment(sessionId).subscribe({
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) => {
console.error('Error syncing cart:', err);
return of(null);
})
)
: of(null);
syncCart$
.pipe(
switchMap(() => this.apiService.createPayment(paymentData, sessionId || undefined))
)
.subscribe({
next: (response) => {
this.paymentId.set(response.qrId);
this.qrCodeUrl.set(response.qrUrl);
this.paymentUrl.set(response.Payload);
const qrId = this.apiService.resolvePaymentQrId(response.response);
const qrUrl = this.apiService.resolvePaymentQrUrl(response.response);
if (!qrId || !qrUrl) {
console.error('Payment response missing qr fields:', response.response);
this.setPaymentError();
return;
}
this.paymentBackend = response.backend;
this.paymentId.set(qrId);
this.qrCodeUrl.set(qrUrl);
this.paymentUrl.set(this.apiService.resolvePaymentLink(response.response));
this.paymentStatus.set('waiting');
this.startPolling();
},
error: (err) => {
console.error('Error creating payment:', err);
this.paymentStatus.set('timeout');
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.closePaymentPopup();
}, PAYMENT_ERROR_CLOSE_MS);
this.setPaymentError();
}
});
},
error: (err) => {
console.error('Error syncing cart:', err);
this.paymentStatus.set('timeout');
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.closePaymentPopup();
}, PAYMENT_ERROR_CLOSE_MS);
}
});
}
startPolling(): void {
@@ -235,13 +274,38 @@ export class CartComponent implements OnDestroy {
take(this.maxChecks), // maximum 36 checks (3 minutes)
switchMap(() => {
const sessionId = this.authService.session()?.sessionId || '';
return this.apiService.checkPaymentStatus(sessionId, this.paymentId());
return this.apiService.checkPaymentStatus(this.paymentId(), {
sessionId: sessionId || undefined,
backend: this.paymentBackend ?? undefined,
}).pipe(
catchError((err) => {
console.error('Error checking payment status:', err);
return of(null);
})
);
})
)
.subscribe({
next: (response) => {
if (!response) {
return;
}
const paymentStatus = response.paymentStatus?.toUpperCase();
const paymentCode = response.code?.toUpperCase();
if (paymentStatus === 'EXPIRED' || paymentStatus === 'CANCELLED') {
this.paymentStatus.set('timeout');
this.stopPolling();
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.closePaymentPopup();
}, PAYMENT_TIMEOUT_CLOSE_MS);
return;
}
// Check if payment is successful
if (response.paymentStatus === 'SUCCESS' && response.code === 'SUCCESS') {
if ((paymentStatus === 'COMPLETED' || paymentStatus === 'SUCCESS') && paymentCode === 'SUCCESS') {
this.paymentStatus.set('success');
this.stopPolling();
// Clear cart but don't close popup - wait for email submission
@@ -260,14 +324,6 @@ export class CartComponent implements OnDestroy {
this.closePaymentPopup();
}, PAYMENT_TIMEOUT_CLOSE_MS);
}
},
error: (err) => {
console.error('Error checking payment status:', err);
// Continue checking even on error until time runs out
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.closePaymentPopup();
}, PAYMENT_TIMEOUT_CLOSE_MS);
}
});
}
@@ -275,6 +331,17 @@ export class CartComponent implements OnDestroy {
stopPolling(): void {
if (this.pollingSubscription) {
this.pollingSubscription.unsubscribe();
this.pollingSubscription = undefined;
}
}
private setPaymentError(): void {
this.paymentStatus.set('error');
this.stopPolling();
this.paymentBackend = null;
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = undefined;
}
}
@@ -346,12 +413,41 @@ export class CartComponent implements OnDestroy {
}
private getTelegramUserId(): string | null {
const sessionTelegramUserId = this.authService.session()?.telegramUserId;
if (sessionTelegramUserId) {
return sessionTelegramUserId.toString();
}
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user) {
return window.Telegram.WebApp.initDataUnsafe.user.id.toString();
}
return null;
}
private getTelegramUsername(): string {
const sessionUsername = this.authService.session()?.username;
if (sessionUsername) {
return sessionUsername;
}
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user) {
return window.Telegram.WebApp.initDataUnsafe.user.username || 'nontelegram';
}
return 'nontelegram';
}
private getPaymentUserId(): string {
return this.getTelegramUserId() ?? `web_${Date.now()}`;
}
private generateOrderId(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
return `order_${timestamp}_${random}`;
}
onPhoneInput(event: Event): void {
const input = event.target as HTMLInputElement;
let value = input.value.replace(/\D/g, ''); // Remove all non-digits