bank type added

This commit is contained in:
sdarbinyan
2026-06-28 22:18:35 +04:00
parent d1c1297fcd
commit 3cf0ef87f8
8 changed files with 142 additions and 15 deletions

View File

@@ -82,8 +82,10 @@ export const en: Translations = {
creatingPayment: 'Creating payment...', creatingPayment: 'Creating payment...',
waitFewSeconds: 'Please wait a few seconds', waitFewSeconds: 'Please wait a few seconds',
scanQr: 'Scan the QR code to pay', scanQr: 'Scan the QR code to pay',
choosePaymentMethod: 'Choose a payment method',
amountToPay: 'Amount due:', amountToPay: 'Amount due:',
waitingPayment: 'Waiting for payment...', waitingPayment: 'Waiting for payment...',
payByBankCard: 'Pay by bank card',
copied: '✓ Copied', copied: '✓ Copied',
copyLink: 'Copy link', copyLink: 'Copy link',
openNewTab: 'Open in new tab', openNewTab: 'Open in new tab',

View File

@@ -82,8 +82,10 @@ export const hy: Translations = {
creatingPayment: 'Վճարման ստեղծում...', creatingPayment: 'Վճարման ստեղծում...',
waitFewSeconds: 'Խնդրում ենք սպասել մի քանի վայրկյան', waitFewSeconds: 'Խնդրում ենք սպասել մի քանի վայրկյան',
scanQr: 'Սքանավորեք QR կոդը վճարման համար', scanQr: 'Սքանավորեք QR կոդը վճարման համար',
choosePaymentMethod: 'Ընտրեք վճարման եղանակը',
amountToPay: 'Վճարման գումար՝', amountToPay: 'Վճարման գումար՝',
waitingPayment: 'Սպասում ենք վճարմանը...', waitingPayment: 'Սպասում ենք վճարմանը...',
payByBankCard: 'Վճարել բանկային քարտով',
copied: '✓ Պատճենված է', copied: '✓ Պատճենված է',
copyLink: 'Պատճենել հղումը', copyLink: 'Պատճենել հղումը',
openNewTab: 'Բացել նոր ներդիրում', openNewTab: 'Բացել նոր ներդիրում',

View File

@@ -82,8 +82,10 @@ export const ru: Translations = {
creatingPayment: 'Создание платежа...', creatingPayment: 'Создание платежа...',
waitFewSeconds: 'Подождите несколько секунд', waitFewSeconds: 'Подождите несколько секунд',
scanQr: 'Сканируйте QR-код для оплаты', scanQr: 'Сканируйте QR-код для оплаты',
choosePaymentMethod: 'Выберите способ оплаты',
amountToPay: 'Сумма к оплате:', amountToPay: 'Сумма к оплате:',
waitingPayment: 'Ожидание оплаты...', waitingPayment: 'Ожидание оплаты...',
payByBankCard: 'Оплатить банковской картой',
copied: '✓ Скопировано', copied: '✓ Скопировано',
copyLink: 'Скопировать ссылку', copyLink: 'Скопировать ссылку',
openNewTab: 'Открыть в новой вкладке', openNewTab: 'Открыть в новой вкладке',

View File

@@ -80,8 +80,10 @@ export interface Translations {
creatingPayment: string; creatingPayment: string;
waitFewSeconds: string; waitFewSeconds: string;
scanQr: string; scanQr: string;
choosePaymentMethod: string;
amountToPay: string; amountToPay: string;
waitingPayment: string; waitingPayment: string;
payByBankCard: string;
copied: string; copied: string;
copyLink: string; copyLink: string;
openNewTab: string; openNewTab: string;

View File

@@ -208,14 +208,16 @@
@if (paymentStatus() === 'waiting') { @if (paymentStatus() === 'waiting') {
<div class="payment-active"> <div class="payment-active">
<h2>{{ 'cart.scanQr' | translate }}</h2> <h2>{{ qrCodeUrl() ? ('cart.scanQr' | translate) : ('cart.choosePaymentMethod' | translate) }}</h2>
@if (qrCodeUrl()) {
<div class="qr-section"> <div class="qr-section">
<div class="qr-wrapper"> <div class="qr-wrapper">
<img [src]="qrCodeUrl()" alt="QR код для оплаты" class="qr-code" /> <img [src]="qrCodeUrl()" alt="QR код для оплаты" class="qr-code" />
<div class="scan-line"></div> <div class="scan-line"></div>
</div> </div>
</div> </div>
}
<div class="payment-info"> <div class="payment-info">
<div class="payment-amount"> <div class="payment-amount">
@@ -229,13 +231,21 @@
</div> </div>
</div> </div>
@if (bankPaymentUrl()) {
<button class="bank-payment-btn" (click)="openBankPaymentPopup()">
{{ 'cart.payByBankCard' | translate }}
</button>
}
<div class="payment-actions"> <div class="payment-actions">
@if (paymentUrl()) {
<button class="copy-btn" (click)="copyPaymentLink()"> <button class="copy-btn" (click)="copyPaymentLink()">
{{ linkCopied() ? ('cart.copied' | translate) : ('cart.copyLink' | translate) }} {{ linkCopied() ? ('cart.copied' | translate) : ('cart.copyLink' | translate) }}
</button> </button>
<a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn"> <a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn">
{{ 'cart.openNewTab' | translate }} {{ 'cart.openNewTab' | translate }}
</a> </a>
}
</div> </div>
</div> </div>
} }
@@ -322,6 +332,19 @@
</div> </div>
} }
</div> </div>
@if (showBankPaymentPopup() && bankPaymentFrameUrl()) {
<div class="bank-payment-modal-overlay">
<div class="bank-payment-modal">
<button class="close-modal-btn" (click)="closeBankPaymentPopup()" [attr.aria-label]="'cart.close' | translate">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
<iframe [src]="bankPaymentFrameUrl()" class="bank-payment-frame" title="Bank card payment"></iframe>
</div>
</div>
}
</div> </div>
} }

View File

@@ -1445,6 +1445,54 @@
} }
} }
} }
.bank-payment-btn {
width: 100%;
padding: 14px 18px;
border: none;
border-radius: 10px;
background: #1f2937;
color: white;
font-weight: 700;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: #111827;
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(31, 41, 55, 0.22);
}
}
}
.bank-payment-modal-overlay {
position: fixed;
inset: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(17, 24, 39, 0.55);
}
.bank-payment-modal {
position: relative;
width: min(960px, 92vw);
height: min(760px, 86vh);
padding: 56px 16px 16px;
background: white;
border-radius: 13px;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.28);
}
.bank-payment-frame {
width: 100%;
height: 100%;
border: 0;
border-radius: 8px;
background: white;
} }
@keyframes pulse { @keyframes pulse {
@@ -1623,6 +1671,16 @@
} }
} }
.bank-payment-modal-overlay {
padding: 12px;
}
.bank-payment-modal {
width: 94vw;
height: 82vh;
padding: 52px 10px 10px;
}
.payment-active h2 { .payment-active h2 {
font-size: 1.25rem; font-size: 1.25rem;
margin-bottom: 16px; margin-bottom: 16px;

View File

@@ -2,6 +2,7 @@ import { Component, ChangeDetectionStrategy, signal, OnDestroy, inject } from '@
import { DecimalPipe } from '@angular/common'; import { DecimalPipe } from '@angular/common';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { CartService, ApiService, LanguageService, AuthService } from '../../services'; import { CartService, ApiService, LanguageService, AuthService } from '../../services';
import { Item, CartItem, DeliveryOption } from '../../models'; import { Item, CartItem, DeliveryOption } from '../../models';
import { interval, of, Subscription } from 'rxjs'; import { interval, of, Subscription } from 'rxjs';
@@ -47,6 +48,9 @@ export class CartComponent implements OnDestroy {
paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout' | 'error' | null>('creating'); paymentStatus = signal<'creating' | 'waiting' | 'success' | 'timeout' | 'error' | null>('creating');
qrCodeUrl = signal<string>(''); qrCodeUrl = signal<string>('');
paymentUrl = signal<string>(''); paymentUrl = signal<string>('');
bankPaymentUrl = signal<string>('');
bankPaymentFrameUrl = signal<SafeResourceUrl | null>(null);
showBankPaymentPopup = signal<boolean>(false);
paymentId = signal<string>(''); paymentId = signal<string>('');
linkCopied = signal<boolean>(false); linkCopied = signal<boolean>(false);
@@ -68,7 +72,8 @@ export class CartComponent implements OnDestroy {
private cartService: CartService, private cartService: CartService,
private apiService: ApiService, private apiService: ApiService,
private router: Router, private router: Router,
private langService: LanguageService private langService: LanguageService,
private sanitizer: DomSanitizer
) { ) {
this.items = this.cartService.items; this.items = this.cartService.items;
this.itemCount = this.cartService.itemCount; this.itemCount = this.cartService.itemCount;
@@ -175,6 +180,9 @@ export class CartComponent implements OnDestroy {
this.paymentId.set(''); this.paymentId.set('');
this.qrCodeUrl.set(''); this.qrCodeUrl.set('');
this.paymentUrl.set(''); this.paymentUrl.set('');
this.bankPaymentUrl.set('');
this.bankPaymentFrameUrl.set(null);
this.showBankPaymentPopup.set(false);
this.linkCopied.set(false); this.linkCopied.set(false);
this.userEmail.set(''); this.userEmail.set('');
this.userPhone.set(''); this.userPhone.set('');
@@ -189,6 +197,7 @@ export class CartComponent implements OnDestroy {
closePaymentPopup(): void { closePaymentPopup(): void {
this.showPaymentPopup.set(false); this.showPaymentPopup.set(false);
this.closeBankPaymentPopup();
this.stopPolling(); this.stopPolling();
if (this.closeTimeout) { if (this.closeTimeout) {
clearTimeout(this.closeTimeout); clearTimeout(this.closeTimeout);
@@ -206,6 +215,9 @@ export class CartComponent implements OnDestroy {
this.paymentId.set(''); this.paymentId.set('');
this.qrCodeUrl.set(''); this.qrCodeUrl.set('');
this.paymentUrl.set(''); this.paymentUrl.set('');
this.bankPaymentUrl.set('');
this.bankPaymentFrameUrl.set(null);
this.showBankPaymentPopup.set(false);
this.linkCopied.set(false); this.linkCopied.set(false);
this.createPayment(); this.createPayment();
} }
@@ -228,9 +240,10 @@ export class CartComponent implements OnDestroy {
const qrId = this.apiService.resolvePaymentQrId(response); const qrId = this.apiService.resolvePaymentQrId(response);
const qrUrl = this.apiService.resolvePaymentQrUrl(response); const qrUrl = this.apiService.resolvePaymentQrUrl(response);
const paymentLink = this.apiService.resolvePaymentLink(response); const paymentLink = this.apiService.resolvePaymentLink(response);
const bankUrl = this.apiService.resolveBankPaymentUrl(response);
if (!qrId || !qrUrl) { if (!qrId || (!qrUrl && !bankUrl)) {
console.error('Payment response missing qr fields:', response); console.error('Payment response missing payment fields:', response);
this.setPaymentError(); this.setPaymentError();
return; return;
} }
@@ -238,6 +251,7 @@ export class CartComponent implements OnDestroy {
this.paymentId.set(qrId); this.paymentId.set(qrId);
this.qrCodeUrl.set(qrUrl); this.qrCodeUrl.set(qrUrl);
this.paymentUrl.set(paymentLink); this.paymentUrl.set(paymentLink);
this.bankPaymentUrl.set(bankUrl);
this.paymentStatus.set('waiting'); this.paymentStatus.set('waiting');
this.startPolling(); this.startPolling();
@@ -280,6 +294,7 @@ export class CartComponent implements OnDestroy {
if (paymentStatus === 'FAILED' || paymentStatus === 'EXPIRED' || paymentStatus === 'CANCELLED' || paymentStatus === 'REJECTED') { if (paymentStatus === 'FAILED' || paymentStatus === 'EXPIRED' || paymentStatus === 'CANCELLED' || paymentStatus === 'REJECTED') {
this.paymentStatus.set('timeout'); this.paymentStatus.set('timeout');
this.closeBankPaymentPopup();
this.stopPolling(); this.stopPolling();
if (this.closeTimeout) clearTimeout(this.closeTimeout); if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => { this.closeTimeout = setTimeout(() => {
@@ -291,6 +306,7 @@ export class CartComponent implements OnDestroy {
// Check if payment is successful // Check if payment is successful
if (paymentStatus === 'COMPLETED' || paymentStatus === 'APPROVED' || paymentStatus === 'PAID' || paymentCode === 'SUCCESS') { if (paymentStatus === 'COMPLETED' || paymentStatus === 'APPROVED' || paymentStatus === 'PAID' || paymentCode === 'SUCCESS') {
this.paymentStatus.set('success'); this.paymentStatus.set('success');
this.closeBankPaymentPopup();
this.stopPolling(); this.stopPolling();
// Auto-submit purchase after 5 seconds // Auto-submit purchase after 5 seconds
if (this.closeTimeout) clearTimeout(this.closeTimeout); if (this.closeTimeout) clearTimeout(this.closeTimeout);
@@ -308,6 +324,7 @@ export class CartComponent implements OnDestroy {
// If all checks are done but payment not completed // If all checks are done but payment not completed
if (this.paymentStatus() === 'waiting') { if (this.paymentStatus() === 'waiting') {
this.paymentStatus.set('timeout'); this.paymentStatus.set('timeout');
this.closeBankPaymentPopup();
// Close popup after showing timeout message // Close popup after showing timeout message
if (this.closeTimeout) clearTimeout(this.closeTimeout); if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => { this.closeTimeout = setTimeout(() => {
@@ -325,8 +342,24 @@ export class CartComponent implements OnDestroy {
} }
} }
openBankPaymentPopup(): void {
const bankUrl = this.bankPaymentUrl();
if (!bankUrl) {
return;
}
this.bankPaymentFrameUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(bankUrl));
this.showBankPaymentPopup.set(true);
}
closeBankPaymentPopup(): void {
this.showBankPaymentPopup.set(false);
this.bankPaymentFrameUrl.set(null);
}
private setPaymentError(): void { private setPaymentError(): void {
this.paymentStatus.set('error'); this.paymentStatus.set('error');
this.closeBankPaymentPopup();
this.stopPolling(); this.stopPolling();
if (this.closeTimeout) { if (this.closeTimeout) {
clearTimeout(this.closeTimeout); clearTimeout(this.closeTimeout);

View File

@@ -23,6 +23,7 @@ export interface QrCreateResponse {
nspkID?: string; nspkID?: string;
nspkId?: string; nspkId?: string;
nspkurl?: string; nspkurl?: string;
bankUrl?: string;
status?: string; status?: string;
qrStatus?: string; qrStatus?: string;
qrExpirationDate?: string; qrExpirationDate?: string;
@@ -543,6 +544,10 @@ export class ApiService {
return response.nspkurl ?? response.Payload ?? response.payload ?? response.qrUrl ?? ''; return response.nspkurl ?? response.Payload ?? response.payload ?? response.qrUrl ?? '';
} }
resolveBankPaymentUrl(response: QrCreateResponse): string {
return response.bankUrl ?? '';
}
resolvePaymentQrUrl(response: QrCreateResponse): string { resolvePaymentQrUrl(response: QrCreateResponse): string {
const paymentLink = this.resolvePaymentLink(response); const paymentLink = this.resolvePaymentLink(response);
if (paymentLink) { if (paymentLink) {