diff --git a/proxy.conf.json b/proxy.conf.json index f56c3f7..a61d3c6 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -12,5 +12,12 @@ "changeOrigin": true, "pathRewrite": { "^/proxy/qr-vitanova": "" }, "logLevel": "debug" + }, + "/proxy/fastcheck-store": { + "target": "https://fastcheck.store", + "secure": true, + "changeOrigin": true, + "pathRewrite": { "^/proxy/fastcheck-store": "" }, + "logLevel": "debug" } } diff --git a/src/app/api.ts b/src/app/api.ts index cce78ef..bd857fd 100644 --- a/src/app/api.ts +++ b/src/app/api.ts @@ -17,3 +17,12 @@ export const FASTCHECK_API = isDevMode() export const QR_VITANOVA_API = isDevMode() ? '/proxy/qr-vitanova/api' : 'https://qr.vitanova.network/api'; + +/** + * Base URL for the public fastcheck.store API (note: different host than + * FASTCHECK_API which points at api.fastcheck.store). + * Used for /api/fastcheck/settings/{id} and /api/fastcheck/message/{tgId}. + */ +export const FASTCHECK_STORE_API = isDevMode() + ? '/proxy/fastcheck-store/api' + : 'https://fastcheck.store/api'; diff --git a/src/app/pages/fastcheck-page/fastcheck-page.html b/src/app/pages/fastcheck-page/fastcheck-page.html index 7140bc7..679f1f5 100644 --- a/src/app/pages/fastcheck-page/fastcheck-page.html +++ b/src/app/pages/fastcheck-page/fastcheck-page.html @@ -59,18 +59,6 @@ } - -
- -
-
@@ -91,7 +79,7 @@ }
- @if (hasPartnerId()) { + @if (validId()) { + } @else { + } diff --git a/src/app/pages/fastcheck-page/fastcheck-page.ts b/src/app/pages/fastcheck-page/fastcheck-page.ts index f9d9404..ebaa088 100644 --- a/src/app/pages/fastcheck-page/fastcheck-page.ts +++ b/src/app/pages/fastcheck-page/fastcheck-page.ts @@ -2,7 +2,7 @@ import { FormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { FastcheckService } from '../../fastcheck.service'; -import { FASTCHECK_API, QR_VITANOVA_API } from '../../api'; +import { FASTCHECK_API, FASTCHECK_STORE_API, QR_VITANOVA_API } from '../../api'; import { TranslatePipe } from '../../translate/translate.pipe'; import { TranslationService } from '../../translate/translation.service'; @@ -27,24 +27,21 @@ interface CheckFastcheckResponse { } /** - * Response of GET /api/settings?id=. - * Shape is defensive — backend may include min/max limits and/or an - * already-active fastcheck (or QR) for this partner so the page can autofill. + * Response of POST /api/fastcheck/settings/{id}. + * Validates the partner id and returns active fastcheck data + callbackurl. */ interface SettingsResponse { - minAmount?: number; - maxAmount?: number; - // Possible active fastcheck data — backend may use different casing. fastcheck?: string; fastcheckNumber?: string; code?: string; + Code?: string; amount?: number; + currency?: string; note?: string; status?: string; - // QR-side info (not rendered on this page yet, but accepted for future use). - qrId?: string; - qrUrl?: string; - payload?: string; + telegramID?: string; + callbackurl?: string; + callbackUrl?: string; } @Component({ @@ -84,8 +81,15 @@ export class FastcheckPage { // Non-blocking settings hint shown above the form when /settings fails. settingsError = signal(''); settingsLoaded = signal(false); - // True only after /settings returned 200 — used to disable Pay button otherwise. - settingsOk = signal(false); + // True only after POST /fastcheck/settings/{id} returned 200. + // Drives button visibility: validId → Pay; !validId → Send-to-Telegram. + validId = signal(false); + // Ready callback URL from settings response — we just redirect here on Pay. + callbackUrl = signal(''); + // telegramID returned by settings (or from URL); empty means user has no + // linked telegram yet and we must run the login flow before sending. + telegramId = signal(''); + fastcheckCurrency = signal('RUB'); popupOpen = signal(false); popupLoading = signal(false); @@ -100,19 +104,14 @@ export class FastcheckPage { canPay = computed(() => { const digits = this.fastcheckNumber().replace(/\D/g, ''); const codeDigits = this.fastcheckCode().replace(/\D/g, ''); - return digits.length === 18 && codeDigits.length === 6 - && this.codeEnabled() && !this.amountLoading(); + return digits.length === 18 && codeDigits.length === 6 && !this.amountLoading(); }); /** - * Share via Telegram is allowed as soon as a valid 18-digit fastcheck is - * entered and the lookup is finished — even if there is nothing to pay - * (amount is null/zero). Pay button stays governed by canPay(). + * Send via Telegram is enabled with the same rules as Pay (18-digit number + * + 6-digit code). Button is only shown when validId is false. */ - canShare = computed(() => { - const digits = this.fastcheckNumber().replace(/\D/g, ''); - return digits.length === 18 && !this.amountLoading(); - }); + canShare = computed(() => this.canPay()); telegramLink = computed(() => { const sid = this.webSessionId(); @@ -185,41 +184,55 @@ export class FastcheckPage { } /** - * GET /api/settings?id=. Idempotent — applies response to the - * current UI without creating any new check. Errors are non-blocking. - * - * @param alreadyAutofilled - true when constructor already populated the form - * from nav state / iid; in that case settings must not overwrite it. + * POST /api/fastcheck/settings/{id} on each load. + * Validates the partner id and may return active fastcheck data + callbackurl. + * If response is non-200 → validId stays false and we fall back to the + * default partner id; UI shows the Telegram-send button instead of Pay. */ private loadSettings(alreadyAutofilled: boolean): void { const id = this.partnerId(); if (!id) { - this.settingsError.set(this.t('errors.settings_missing_id')); this.settingsLoaded.set(true); - this.settingsOk.set(false); + this.validId.set(false); return; } - const url = `${QR_VITANOVA_API}/settings?id=${encodeURIComponent(id)}`; - this.http.get(url).subscribe({ + // Pre-fill body from URL query params — partner usually forwards them. + const params = new URLSearchParams(window.location.search); + const body = { + fastcheck: params.get('fastcheck') ?? this.fastcheckNumber() ?? '', + Code: params.get('code') ?? this.fastcheckCode() ?? '', + telegramID: params.get('telegramID') ?? params.get('tg') ?? '', + callbackurl: params.get('callbackurl') ?? params.get('callback') ?? '' + }; + + const url = `${FASTCHECK_STORE_API}/fastcheck/settings/${encodeURIComponent(id)}`; + this.http.post(url, body).subscribe({ next: (res) => { this.settingsLoaded.set(true); this.settingsError.set(''); - this.settingsOk.set(true); + this.validId.set(true); this.applySettings(res ?? {}, alreadyAutofilled); }, error: () => { - // Non-blocking: keep manual mode available, but Pay stays disabled. + // Not a valid id — fall back to default and hide Pay button. this.settingsLoaded.set(true); - this.settingsOk.set(false); + this.validId.set(false); + this.partnerId.set(this.defaultPartnerId); + this.hasPartnerId.set(false); this.settingsError.set(this.t('errors.settings_failed')); } }); } - /** Apply settings response to UI state without creating any new check. */ + /** Apply settings response — autofill, callback url, telegram id. */ private applySettings(res: SettingsResponse, alreadyAutofilled: boolean): void { - // Active check autofill — only if user hasn't already got data on screen. + const cb = res.callbackurl ?? res.callbackUrl ?? ''; + if (cb) this.callbackUrl.set(cb); + + if (res.telegramID) this.telegramId.set(res.telegramID); + if (res.currency) this.fastcheckCurrency.set(res.currency); + if (alreadyAutofilled) return; const rawNumber = res.fastcheck ?? res.fastcheckNumber ?? ''; @@ -230,38 +243,85 @@ export class FastcheckPage { for (let i = 0; i < digits.length; i += 6) groups.push(digits.slice(i, i + 6)); this.fastcheckNumber.set(groups.join('-')); if (digits.length === 18) { - // Trigger the regular lookup so amount/code-enabled stay consistent. this.lookupFastcheck(groups.join('-')); } } } - if (typeof res.amount === 'number') { - this.fastcheckAmount.set(res.amount); - } + if (typeof res.amount === 'number') this.fastcheckAmount.set(res.amount); - if (typeof res.code === 'string' && res.code) { - const codeDigits = res.code.replace(/\D/g, '').slice(0, 6); + const rawCode = res.code ?? res.Code ?? ''; + if (rawCode) { + const codeDigits = String(rawCode).replace(/\D/g, '').slice(0, 6); this.fastcheckCode.set(codeDigits); this.codeEnabled.set(true); } - // Final statuses end the flow — no polling, no new requests. const status = (res.status ?? '').toUpperCase(); if (status === 'COMPLETED' || status === 'APPROVED') { this.paid.set(true); } } + /** + * Pay button — redirect to the ready callback URL returned by /settings. + * Falls back to opening the legacy Telegram-accept popup if no URL is set. + */ pay(): void { - if (!this.canPay()) { + if (!this.canPay()) return; + const url = this.callbackUrl(); + if (url) { + window.location.href = url; return; } + // No callback was provided — keep the legacy Telegram-accept flow. this.error.set(''); this.loginOnly.set(false); this.openPopup(); } + /** + * Send fastcheck to Telegram. If we already know the telegramID, fire + * POST /api/fastcheck/message/{telegramID} directly. Otherwise run the + * Telegram-login popup first; after login we'll retry the send. + */ + shareByTelegram(): void { + if (!this.canShare()) return; + this.error.set(''); + + const tg = this.telegramId(); + if (tg) { + this.sendFastcheckToTelegram(tg); + return; + } + + // No telegramID yet — trigger identification via Telegram-bot. + this.loginOnly.set(true); + this.openPopup(); + } + + /** POST /api/fastcheck/message/{telegramID} with all fastcheck fields. */ + private sendFastcheckToTelegram(telegramId: string): void { + const url = `${FASTCHECK_STORE_API}/fastcheck/message/${encodeURIComponent(telegramId)}`; + const body = { + id: this.partnerId(), + fastcheck: this.fastcheckNumber(), + code: this.fastcheckCode(), + amount: this.fastcheckAmount(), + currency: this.fastcheckCurrency() + }; + this.http.post(url, body).subscribe({ + next: () => { + this.popupOpen.set(true); + this.paid.set(true); + this.loginOnly.set(true); + }, + error: () => { + this.error.set(this.t('errors.payment_failed')); + } + }); + } + private openPopup(): void { this.popupOpen.set(true); this.popupError.set(''); @@ -342,6 +402,12 @@ export class FastcheckPage { if (res?.Status) { this.stopPolling(); 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); @@ -458,9 +524,4 @@ export class FastcheckPage { } }); } - - shareByTelegram(): void { - this.loginOnly.set(true); - this.openPopup(); - } }