Compare commits
19 Commits
14d9642568
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a52fd07273 | |||
| fe7fea151a | |||
| e9acbd4898 | |||
| 4841cdf90d | |||
| c2a0675c79 | |||
| e62afe07eb | |||
| 926afc5691 | |||
| 45769ca817 | |||
| 02a33e9b14 | |||
| 9c96370235 | |||
| 9cbb6660f8 | |||
| b1ffd577c5 | |||
| bee56afedc | |||
| ce2c9c42fe | |||
| 17dfad5eaa | |||
| abb4f7b849 | |||
| 097064281a | |||
| 0330e0a212 | |||
| 5147d05ea2 |
@@ -148,7 +148,7 @@
|
|||||||
|
|
||||||
<div class="card__header">
|
<div class="card__header">
|
||||||
<div class="sbp-logo">
|
<div class="sbp-logo">
|
||||||
<img src="https://sbp.nspk.ru/storage/settings/common/logo/0645d335-8b62-43a1-9a33-0d4c9d1dc0e0.svg" alt="СБП" />
|
<img src="public/sbp.svg" alt="СБП" />
|
||||||
</div>
|
</div>
|
||||||
<h1 class="card__title">Оплата через СБП</h1>
|
<h1 class="card__title">Оплата через СБП</h1>
|
||||||
<p class="card__subtitle">Система быстрых платежей</p>
|
<p class="card__subtitle">Система быстрых платежей</p>
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
|
|
||||||
<div class="card__header">
|
<div class="card__header">
|
||||||
<div class="sbp-logo">
|
<div class="sbp-logo">
|
||||||
<img src="https://sbp.nspk.ru/storage/settings/common/logo/0645d335-8b62-43a1-9a33-0d4c9d1dc0e0.svg" alt="СБП" />
|
<img src="sbp.svg" alt="СБП" />
|
||||||
</div>
|
</div>
|
||||||
<h1 class="card__title">Оплата через СБП</h1>
|
<h1 class="card__title">Оплата через СБП</h1>
|
||||||
<p class="card__subtitle">Система быстрых платежей</p>
|
<p class="card__subtitle">Система быстрых платежей</p>
|
||||||
|
|||||||
1
public/sbp.svg
Normal file
1
public/sbp.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 10 KiB |
@@ -1,11 +1,9 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { SiteHeader } from './site-header/site-header';
|
|
||||||
import { SiteFooter } from './site-footer/site-footer';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, SiteHeader, SiteFooter],
|
imports: [RouterOutlet],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss'
|
styleUrl: './app.scss'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<button type="button" class="method" [class.method--active]="payment() === 'sbp'"
|
<button type="button" class="method" [class.method--active]="payment() === 'sbp'"
|
||||||
(click)="selectPayment('sbp', true)" aria-label="СБП">
|
(click)="selectPayment('sbp', true)" aria-label="СБП">
|
||||||
<img class="method__logo"
|
<img class="method__logo"
|
||||||
src="https://sbp.nspk.ru/storage/settings/common/logo/0645d335-8b62-43a1-9a33-0d4c9d1dc0e0.svg"
|
src="/sbp.svg"
|
||||||
alt="СБП" />
|
alt="СБП" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="method method--disabled" disabled aria-label="WeChat Pay">
|
<button type="button" class="method method--disabled" disabled aria-label="WeChat Pay">
|
||||||
|
|||||||
@@ -234,11 +234,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 13px;
|
font-size: 11px !important;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 8px auto;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__img {
|
&__img {
|
||||||
|
|||||||
@@ -9,8 +9,19 @@ type PaymentMethod = 'sbp';
|
|||||||
type Currency = 'RUB';
|
type Currency = 'RUB';
|
||||||
|
|
||||||
interface SettingsResponse {
|
interface SettingsResponse {
|
||||||
|
sbp?: boolean;
|
||||||
|
wechat?: boolean;
|
||||||
|
visa?: boolean;
|
||||||
|
mastercard?: boolean;
|
||||||
|
alipay?: boolean;
|
||||||
|
rubles?: boolean;
|
||||||
|
usd?: boolean;
|
||||||
|
euro?: boolean;
|
||||||
|
cny?: boolean;
|
||||||
|
dram?: boolean;
|
||||||
minAmount?: number;
|
minAmount?: number;
|
||||||
maxAmount?: number;
|
maxAmount?: number;
|
||||||
|
qrTTL?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateQrResponse {
|
interface CreateQrResponse {
|
||||||
@@ -24,6 +35,7 @@ interface CreateQrResponse {
|
|||||||
|
|
||||||
interface QrStatusResponse {
|
interface QrStatusResponse {
|
||||||
status?: string; // "REGISTERED" | "NEW" | "APPROVED" | "REJECTED" | "COMPLETED"
|
status?: string; // "REGISTERED" | "NEW" | "APPROVED" | "REJECTED" | "COMPLETED"
|
||||||
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -35,6 +47,9 @@ interface QrStatusResponse {
|
|||||||
export class CreatePage {
|
export class CreatePage {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private i18n = inject(TranslationService);
|
private i18n = inject(TranslationService);
|
||||||
|
private readonly sites: Record<string, string> = {
|
||||||
|
'51': 'fastcheck.store'
|
||||||
|
};
|
||||||
|
|
||||||
private t(key: string): string { return this.i18n.translate(key); }
|
private t(key: string): string { return this.i18n.translate(key); }
|
||||||
|
|
||||||
@@ -80,6 +95,9 @@ export class CreatePage {
|
|||||||
private get partnerqrID(): string {
|
private get partnerqrID(): string {
|
||||||
return new URLSearchParams(window.location.search).get('id') ?? '';
|
return new URLSearchParams(window.location.search).get('id') ?? '';
|
||||||
}
|
}
|
||||||
|
private get fromSite(): string {
|
||||||
|
return new URLSearchParams(window.location.search).get('from') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
get isMobile(): boolean {
|
get isMobile(): boolean {
|
||||||
return window.innerWidth < 768;
|
return window.innerWidth < 768;
|
||||||
@@ -90,11 +108,8 @@ export class CreatePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private loadSettings(): void {
|
private loadSettings(): void {
|
||||||
// The `id` query param is the user's id. Fetch per-user amount limits.
|
// Fetch limits from /qr/settings. If the call fails, keep defaults.
|
||||||
// If the call fails or omits a value, keep current defaults.
|
const url = `${QR_VITANOVA_API}/qr/settings`;
|
||||||
const userId = this.partnerqrID;
|
|
||||||
if (!userId) return;
|
|
||||||
const url = `${QR_VITANOVA_API}/settings?id=${encodeURIComponent(userId)}`;
|
|
||||||
this.http.get<SettingsResponse>(url).subscribe({
|
this.http.get<SettingsResponse>(url).subscribe({
|
||||||
next: (s) => {
|
next: (s) => {
|
||||||
if (typeof s?.minAmount === 'number') this.minAmount.set(s.minAmount);
|
if (typeof s?.minAmount === 'number') this.minAmount.set(s.minAmount);
|
||||||
@@ -137,7 +152,8 @@ export class CreatePage {
|
|||||||
partnerqrID,
|
partnerqrID,
|
||||||
qrDescription: this.note().trim(),
|
qrDescription: this.note().trim(),
|
||||||
Userid: this.userId,
|
Userid: this.userId,
|
||||||
Reference: this.reference
|
Reference: this.reference,
|
||||||
|
RedirectUrl: `https://fastcheck.store?id=fast-c202-4062-bcfb-8b4c8cc59adc`
|
||||||
},
|
},
|
||||||
{ headers }
|
{ headers }
|
||||||
)
|
)
|
||||||
@@ -176,14 +192,13 @@ export class CreatePage {
|
|||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
this.qrPolling.set(true);
|
this.qrPolling.set(true);
|
||||||
this.pollHandle = setInterval(() => {
|
this.pollHandle = setInterval(() => {
|
||||||
this.http.get<QrStatusResponse>(`${QR_VITANOVA_API}/qr/dynamic/${qrId}`)
|
this.http.get<QrStatusResponse>(`${QR_VITANOVA_API}/qr/dynamic/${encodeURIComponent(this.partnerqrID)}/${qrId}`)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
const st = res?.status ?? '';
|
const st = res?.status ?? '';
|
||||||
this.qrStatus.set(st);
|
this.qrStatus.set(st);
|
||||||
if (st === 'COMPLETED' || st === 'APPROVED') {
|
if (st === 'COMPLETED' || st === 'APPROVED') {
|
||||||
this.stopPolling();
|
this.handlePaymentSuccess(res);
|
||||||
this.paymentDone.set(true);
|
|
||||||
} else if (st === 'REJECTED') {
|
} else if (st === 'REJECTED') {
|
||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
this.error.set(this.t('errors.payment_failed'));
|
this.error.set(this.t('errors.payment_failed'));
|
||||||
@@ -191,7 +206,10 @@ export class CreatePage {
|
|||||||
}
|
}
|
||||||
// REGISTERED / NEW / '' — keep polling
|
// REGISTERED / NEW / '' — keep polling
|
||||||
},
|
},
|
||||||
error: () => undefined
|
error: () => {
|
||||||
|
this.closeQr();
|
||||||
|
this.error.set('оплата не прошла');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
@@ -213,6 +231,48 @@ export class CreatePage {
|
|||||||
this.note.set(value);
|
this.note.set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handlePaymentSuccess(paidQr: QrStatusResponse): void {
|
||||||
|
this.stopPolling();
|
||||||
|
this.qrImageUrl.set(null);
|
||||||
|
this.qrStatus.set('');
|
||||||
|
this.paymentDone.set(true);
|
||||||
|
|
||||||
|
const id = this.partnerqrID;
|
||||||
|
if (!id) {
|
||||||
|
this.redirectToSource();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.http
|
||||||
|
.post(`https://fastcheck.store/api/fastcheck/settings/${encodeURIComponent(id)}`, paidQr)
|
||||||
|
.subscribe({
|
||||||
|
next: () => this.redirectToSource(id),
|
||||||
|
error: () => this.redirectToSource(id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private redirectToSource(id?: string): void {
|
||||||
|
const withId = (target: string): string => {
|
||||||
|
if (!id) return target;
|
||||||
|
|
||||||
|
const normalizedTarget = /^https?:\/\//i.test(target) ? target : `https://${target}`;
|
||||||
|
const url = new URL(normalizedTarget);
|
||||||
|
url.searchParams.set('id', id);
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const from = this.fromSite.trim();
|
||||||
|
const target = this.sites[from];
|
||||||
|
if (target) {
|
||||||
|
window.location.href = withId(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.history.length > 1) {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeQr(): void {
|
closeQr(): void {
|
||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
this.qrImageUrl.set(null);
|
this.qrImageUrl.set(null);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>fastCHECK</title>
|
<title>QR Vitanova</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<meta name="theme-color" content="#2563eb">
|
<meta name="theme-color" content="#2563eb">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
"outDir": "./out-tsc/app",
|
"outDir": "./out-tsc/app",
|
||||||
"types": []
|
"types": []
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user