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