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();
- }
}