changes
This commit is contained in:
@@ -118,62 +118,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Telegram sign-in popup -->
|
||||
@if (popupOpen()) {
|
||||
<div class="modal" (click)="closePopup()">
|
||||
<div class="modal__card" (click)="$event.stopPropagation()">
|
||||
<button class="modal__close" type="button" (click)="closePopup()" aria-label="Закрыть">×</button>
|
||||
|
||||
@if (paid()) {
|
||||
<div class="modal__success">
|
||||
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="#16a34a"
|
||||
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 6L9 17l-5-5" />
|
||||
</svg>
|
||||
@if (loginOnly()) {
|
||||
<h2 class="modal__title">{{ 'fastcheck.modal_loggedin_title' | translate }}</h2>
|
||||
<p class="modal__sub">{{ 'fastcheck.modal_loggedin_sub' | translate }}</p>
|
||||
} @else {
|
||||
<h2 class="modal__title">{{ 'fastcheck.modal_paid_title' | translate }}</h2>
|
||||
<p class="modal__sub">
|
||||
<span class="brand"><span class="brand__fast">fast</span><span class="brand__check">CHECK</span></span>
|
||||
{{ 'fastcheck.modal_paid_sub' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<img class="brand-logo brand-logo--small" src="/logo_small.png"
|
||||
alt="fastCHECK" width="32" height="32" />
|
||||
<h2 class="modal__title">{{ 'fastcheck.modal_title' | translate }}</h2>
|
||||
<p class="modal__sub">{{ 'fastcheck.modal_sub' | translate }}</p>
|
||||
|
||||
@if (popupLoading() && !webSessionId()) {
|
||||
<div class="qr__placeholder">{{ 'fastcheck.modal_loading' | translate }}</div>
|
||||
}
|
||||
|
||||
@if (webSessionId() && !isMobile) {
|
||||
<img [src]="qrUrl()" width="240" height="240" alt="QR Telegram" style="border-radius:12px;display:block;margin:0 auto 12px;" />
|
||||
}
|
||||
|
||||
@if (webSessionId()) {
|
||||
<a class="tg-link" [href]="telegramLink()" target="_blank" rel="noopener">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M9.04 15.65l-.36 4.06c.51 0 .73-.22.99-.48l2.38-2.27 4.93 3.6c.9.5 1.55.24 1.79-.83l3.24-15.18h.01c.29-1.34-.48-1.86-1.36-1.54L1.13 9.66c-1.32.5-1.3 1.23-.22 1.56l4.92 1.53L17.27 5.6c.54-.34 1.03-.15.62.19" />
|
||||
</svg>
|
||||
{{ 'fastcheck.modal_open_tg' | translate }}
|
||||
</a>
|
||||
}
|
||||
|
||||
@if (popupLoading() && webSessionId()) {
|
||||
<p class="modal__hint">{{ 'fastcheck.modal_confirming' | translate }}</p>
|
||||
} @else if (webSessionId()) {
|
||||
<p class="modal__hint">{{ 'fastcheck.modal_waiting' | translate }}</p>
|
||||
}
|
||||
|
||||
@if (popupError()) {
|
||||
<p class="modal__error">{{ popupError() }}</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<app-auth-dialog
|
||||
[open]="authOpen()"
|
||||
[mode]="authMode()"
|
||||
[processing]="authProcessing()"
|
||||
(authorized)="onAuthAuthorized($event)"
|
||||
(closed)="onAuthClosed()"
|
||||
/>
|
||||
|
||||
@@ -93,164 +93,3 @@
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Modal (Telegram QR popup) ──────────────────────────────────────────────
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(15, 23, 42, .55);
|
||||
backdrop-filter: blur(6px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
animation: fade-in .15s ease-out;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__card {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 24px;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
padding: 28px 24px 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 24px 60px rgba(0,0,0,.25);
|
||||
animation: pop-in .2s ease-out;
|
||||
margin: auto;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
max-width: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
padding: calc(28px + env(safe-area-inset-top)) 20px calc(28px + env(safe-area-inset-bottom));
|
||||
margin: 0;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background .15s;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:hover { background: #e2e8f0; }
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
margin: 4px 0 6px;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
&__error {
|
||||
font-size: 13px;
|
||||
color: #ef4444;
|
||||
font-weight: 500;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
&__success {
|
||||
padding: 12px 0 4px;
|
||||
|
||||
svg { display: block; margin: 0 auto 10px; }
|
||||
}
|
||||
}
|
||||
|
||||
.qr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
width: 264px;
|
||||
height: 264px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 380px) {
|
||||
width: min(264px, 70vw);
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
&__placeholder {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 240px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.tg-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
padding: 14px 22px;
|
||||
min-height: 48px;
|
||||
border-radius: 12px;
|
||||
background: #229ED9;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: opacity .15s;
|
||||
|
||||
&:hover { opacity: .9; }
|
||||
&:active { transform: scale(.97); }
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes pop-in {
|
||||
from { transform: translateY(12px) scale(.98); opacity: 0; }
|
||||
to { transform: translateY(0) scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FastcheckService } from '../../fastcheck.service';
|
||||
import { API_VITANOVA_NETWORK, FASTCHECK_API, FASTCHECK_STORE_API, QR_VITANOVA_API } from '../../api';
|
||||
import { AuthDialogAuthorizedEvent, AuthDialogComponent, AuthDialogMode } from '../../auth-dialog/auth-dialog';
|
||||
import { TranslatePipe } from '../../translate/translate.pipe';
|
||||
import { TranslationService } from '../../translate/translation.service';
|
||||
|
||||
@@ -46,7 +47,7 @@ interface SettingsResponse {
|
||||
|
||||
@Component({
|
||||
selector: 'app-fastcheck-page',
|
||||
imports: [FormsModule, TranslatePipe],
|
||||
imports: [FormsModule, TranslatePipe, AuthDialogComponent],
|
||||
templateUrl: './fastcheck-page.html',
|
||||
styleUrl: './fastcheck-page.scss'
|
||||
})
|
||||
@@ -59,9 +60,6 @@ export class FastcheckPage {
|
||||
|
||||
private t(key: string): string { return this.i18n.translate(key); }
|
||||
|
||||
// Telegram bot used for the sign-in deep link.
|
||||
private readonly telegramBot = 'DexarSupport_bot';
|
||||
|
||||
fastcheckNumber = signal<string>('');
|
||||
fastcheckAmount = signal<number | null>(null);
|
||||
fastcheckCode = signal<string>('');
|
||||
@@ -91,15 +89,9 @@ export class FastcheckPage {
|
||||
telegramId = signal<string>('');
|
||||
fastcheckCurrency = signal<string>('RUB');
|
||||
|
||||
popupOpen = signal<boolean>(false);
|
||||
popupLoading = signal<boolean>(false);
|
||||
popupError = signal<string>('');
|
||||
webSessionId = signal<string>('');
|
||||
paid = signal<boolean>(false);
|
||||
loginOnly = signal<boolean>(false);
|
||||
isNewFlow = signal<boolean>(false);
|
||||
sessionToken = signal<string>(localStorage.getItem('fc_session') ?? '');
|
||||
private pollHandle: ReturnType<typeof setInterval> | null = null;
|
||||
authOpen = signal<boolean>(false);
|
||||
authMode = signal<AuthDialogMode>('payment');
|
||||
authProcessing = signal<boolean>(false);
|
||||
private lastLookedUpNumber = '';
|
||||
|
||||
canPay = computed(() => {
|
||||
@@ -114,22 +106,6 @@ export class FastcheckPage {
|
||||
*/
|
||||
canShare = computed(() => this.canPay());
|
||||
|
||||
telegramLink = computed(() => {
|
||||
const sid = this.webSessionId();
|
||||
return sid
|
||||
? `https://t.me/${this.telegramBot}?start=${encodeURIComponent(sid)}`
|
||||
: `https://t.me/${this.telegramBot}`;
|
||||
});
|
||||
|
||||
qrUrl = computed(() => {
|
||||
const link = this.telegramLink();
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=240x240&margin=8&data=${encodeURIComponent(link)}`;
|
||||
});
|
||||
|
||||
get isMobile(): boolean {
|
||||
return typeof window !== 'undefined' && window.innerWidth < 768;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Pull autofill data: prefer router navigation state, fall back to service.
|
||||
const navState = typeof window !== 'undefined' ? (window.history?.state ?? {}) : {};
|
||||
@@ -263,7 +239,7 @@ export class FastcheckPage {
|
||||
|
||||
const status = (res.status ?? '').toUpperCase();
|
||||
if (status === 'COMPLETED' || status === 'APPROVED') {
|
||||
this.paid.set(true);
|
||||
this.error.set('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +252,7 @@ export class FastcheckPage {
|
||||
pay(): void {
|
||||
if (!this.canPay()) return;
|
||||
this.error.set('');
|
||||
this.loginOnly.set(false);
|
||||
this.isNewFlow.set(false);
|
||||
this.openPopup();
|
||||
this.openAuth('payment');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +263,6 @@ export class FastcheckPage {
|
||||
shareByTelegram(): void {
|
||||
if (!this.canShare()) return;
|
||||
this.error.set('');
|
||||
this.isNewFlow.set(false);
|
||||
|
||||
const tg = this.telegramId();
|
||||
if (tg) {
|
||||
@@ -297,16 +270,14 @@ export class FastcheckPage {
|
||||
return;
|
||||
}
|
||||
|
||||
// No telegramID yet — trigger identification via Telegram-bot.
|
||||
this.loginOnly.set(true);
|
||||
this.openPopup();
|
||||
this.openAuth('login');
|
||||
}
|
||||
|
||||
createNewFastcheck(event: Event): void {
|
||||
event.preventDefault();
|
||||
|
||||
const id = this.partnerId() || this.defaultPartnerId;
|
||||
const sessionId = this.sessionToken();
|
||||
const sessionId = localStorage.getItem('fc_session') ?? '';
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: JSON.stringify({ sessionID: sessionId, partnerID: id })
|
||||
};
|
||||
@@ -316,26 +287,20 @@ export class FastcheckPage {
|
||||
.subscribe({
|
||||
next: () => {
|
||||
// Authorized partner: skip Telegram auth popup and go directly.
|
||||
this.isNewFlow.set(true);
|
||||
this.loginOnly.set(false);
|
||||
this.doRedirectToNew();
|
||||
this.doRedirectToNew(sessionId);
|
||||
},
|
||||
error: () => {
|
||||
// Not authorized: force fresh Telegram auth QR popup.
|
||||
this.sessionToken.set('');
|
||||
localStorage.removeItem('fc_session');
|
||||
this.isNewFlow.set(true);
|
||||
this.loginOnly.set(false);
|
||||
this.openPopup();
|
||||
this.openAuth('new');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private doRedirectToNew(): void {
|
||||
const tok = this.webSessionId();
|
||||
private doRedirectToNew(sessionId?: string): void {
|
||||
const tok = sessionId || localStorage.getItem('fc_session') || '';
|
||||
if (tok) {
|
||||
localStorage.setItem('fc_session', tok);
|
||||
this.sessionToken.set(tok);
|
||||
}
|
||||
window.location.href = this.newQrUrl();
|
||||
}
|
||||
@@ -352,126 +317,56 @@ export class FastcheckPage {
|
||||
};
|
||||
this.http.post(url, body).subscribe({
|
||||
next: () => {
|
||||
this.popupOpen.set(true);
|
||||
this.paid.set(true);
|
||||
this.loginOnly.set(true);
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
this.error.set(this.t('errors.payment_failed'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private openPopup(): void {
|
||||
this.popupOpen.set(true);
|
||||
this.popupError.set('');
|
||||
this.paid.set(false);
|
||||
this.popupLoading.set(true);
|
||||
private openAuth(mode: AuthDialogMode): void {
|
||||
this.authMode.set(mode);
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(true);
|
||||
}
|
||||
|
||||
const existing = this.sessionToken();
|
||||
if (existing) {
|
||||
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession/${existing}`).subscribe({
|
||||
next: (res) => {
|
||||
if (res?.Status) {
|
||||
this.popupLoading.set(false);
|
||||
this.webSessionId.set(existing);
|
||||
if (this.isNewFlow()) {
|
||||
this.doRedirectToNew();
|
||||
} else if (this.loginOnly()) {
|
||||
this.paid.set(true);
|
||||
} else {
|
||||
this.acceptFastcheck(existing);
|
||||
}
|
||||
} else {
|
||||
this.sessionToken.set('');
|
||||
this.createNewSession();
|
||||
}
|
||||
},
|
||||
error: () => { this.sessionToken.set(''); this.createNewSession(); }
|
||||
});
|
||||
onAuthClosed(): void {
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
}
|
||||
|
||||
onAuthAuthorized(event: AuthDialogAuthorizedEvent): void {
|
||||
this.authProcessing.set(true);
|
||||
|
||||
if (this.authMode() === 'new') {
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
this.doRedirectToNew(event.sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.createNewSession();
|
||||
}
|
||||
|
||||
private createNewSession(): void {
|
||||
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession`).subscribe({
|
||||
next: (res) => {
|
||||
this.popupLoading.set(false);
|
||||
this.webSessionId.set(res.sessionId);
|
||||
if (this.isMobile) {
|
||||
window.location.href = `https://t.me/${this.telegramBot}?start=${encodeURIComponent(res.sessionId)}`;
|
||||
} else {
|
||||
this.startPolling(res.sessionId);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.popupLoading.set(false);
|
||||
this.popupError.set(this.t('errors.session_failed'));
|
||||
if (this.authMode() === 'login') {
|
||||
const tg = event.userId || event.userSessionId || '';
|
||||
if (!tg) {
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
this.error.set(this.t('errors.payment_failed'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closePopup(): void {
|
||||
this.popupOpen.set(false);
|
||||
this.stopPolling();
|
||||
if ((this.loginOnly() || this.isNewFlow()) && this.paid()) {
|
||||
// Keep session alive — user is logged in, preserve token for next action.
|
||||
const tok = this.webSessionId();
|
||||
localStorage.setItem('fc_session', tok);
|
||||
this.sessionToken.set(tok);
|
||||
} else if (this.webSessionId()) {
|
||||
// Best-effort logout; ignore errors.
|
||||
this.http
|
||||
.request('DELETE', `${FASTCHECK_API}/websession/${this.webSessionId()}`, {
|
||||
body: { sessionId: this.webSessionId() }
|
||||
})
|
||||
.subscribe({ error: () => undefined });
|
||||
localStorage.removeItem('fc_session');
|
||||
this.sessionToken.set('');
|
||||
this.telegramId.set(tg);
|
||||
this.sendFastcheckToTelegram(tg);
|
||||
return;
|
||||
}
|
||||
this.webSessionId.set('');
|
||||
}
|
||||
|
||||
private startPolling(sessionId: string): void {
|
||||
this.stopPolling();
|
||||
this.pollHandle = setInterval(() => {
|
||||
this.http
|
||||
.get<WebSessionResponse>(`${FASTCHECK_API}/websession/${sessionId}`)
|
||||
.subscribe({
|
||||
next: (res) => {
|
||||
if (res?.Status) {
|
||||
this.stopPolling();
|
||||
if (this.isNewFlow()) {
|
||||
this.doRedirectToNew();
|
||||
} else if (this.loginOnly()) {
|
||||
// Identified — use userId as telegramID and send the fastcheck.
|
||||
const tg = res.userId || res.userSessionId || '';
|
||||
if (tg) {
|
||||
this.telegramId.set(tg);
|
||||
this.sendFastcheckToTelegram(tg);
|
||||
}
|
||||
this.paid.set(true);
|
||||
} else {
|
||||
this.acceptFastcheck(sessionId);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: () => undefined
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
private stopPolling(): void {
|
||||
if (this.pollHandle !== null) {
|
||||
clearInterval(this.pollHandle);
|
||||
this.pollHandle = null;
|
||||
}
|
||||
this.acceptFastcheck(event.sessionId);
|
||||
}
|
||||
|
||||
private acceptFastcheck(sessionId: string): void {
|
||||
this.popupLoading.set(true);
|
||||
this.http
|
||||
.post(
|
||||
`${FASTCHECK_API}/fastcheck`,
|
||||
@@ -480,8 +375,8 @@ export class FastcheckPage {
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.popupLoading.set(false);
|
||||
this.paid.set(true);
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
// Fire DELETE to mark fastcheck as consumed on the merchant side.
|
||||
this.http
|
||||
.delete(`${FASTCHECK_API}/fastcheck/${encodeURIComponent(this.fastcheckNumber())}`)
|
||||
@@ -489,8 +384,9 @@ export class FastcheckPage {
|
||||
this.fireMerchantCallback();
|
||||
},
|
||||
error: () => {
|
||||
this.popupLoading.set(false);
|
||||
this.popupError.set(this.t('errors.payment_failed'));
|
||||
this.authProcessing.set(false);
|
||||
this.authOpen.set(false);
|
||||
this.error.set(this.t('errors.payment_failed'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user