This commit is contained in:
sdarbinyan
2026-06-02 01:46:12 +04:00
parent 63b0e18396
commit c6bc05560e
3 changed files with 128 additions and 113 deletions

View File

@@ -58,7 +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;
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<string, unknown>)['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);