cleaning
This commit is contained in:
291
payment.html
291
payment.html
@@ -1,291 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Оплата через СБП</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
||||||
<meta name="theme-color" content="#2563eb">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
<style>
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
html, body { height: 100%; }
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
background: #1e40af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
min-height: 100dvh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
background: linear-gradient(135deg, #1e40af 0%, #2563eb 40%, #0ea5e9 100%);
|
|
||||||
}
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.page { align-items: flex-end; padding: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: 0 24px 60px rgba(0,0,0,.18);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.card { border-radius: 24px 24px 0 0; max-width: 100%; box-shadow: 0 -8px 40px rgba(0,0,0,.15); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__header {
|
|
||||||
background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%);
|
|
||||||
padding: 32px 28px 28px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
@media (max-width: 480px) { .card__header { padding: 28px 24px 24px; } }
|
|
||||||
|
|
||||||
.card__title { color: #fff; font-size: 22px; font-weight: 700; margin: 14px 0 4px; letter-spacing: -.3px; }
|
|
||||||
.card__subtitle { color: rgba(255,255,255,.7); font-size: 13px; }
|
|
||||||
|
|
||||||
.card__body { padding: 28px 28px 20px; }
|
|
||||||
@media (max-width: 480px) { .card__body { padding: 24px 20px 16px; } }
|
|
||||||
|
|
||||||
.card__footer { padding: 0 28px 24px; display: flex; justify-content: center; }
|
|
||||||
@media (max-width: 480px) { .card__footer { padding: 0 20px 32px; } }
|
|
||||||
|
|
||||||
.sbp-logo {
|
|
||||||
display: inline-flex; align-items: center; justify-content: center;
|
|
||||||
background: rgba(255,255,255,.15); backdrop-filter: blur(8px);
|
|
||||||
border-radius: 16px; padding: 12px 20px;
|
|
||||||
border: 1px solid rgba(255,255,255,.25);
|
|
||||||
}
|
|
||||||
.sbp-logo img { height: 40px; display: block; }
|
|
||||||
@media (max-width: 480px) { .sbp-logo img { height: 34px; } }
|
|
||||||
|
|
||||||
.field { margin-bottom: 16px; }
|
|
||||||
.field__label {
|
|
||||||
display: block; font-size: 13px; font-weight: 600; color: #64748b;
|
|
||||||
margin-bottom: 8px; text-transform: uppercase; letter-spacing: .6px;
|
|
||||||
}
|
|
||||||
.field__error { display: block; margin-top: 6px; font-size: 13px; color: #ef4444; font-weight: 500; }
|
|
||||||
.field__error:empty { display: none; }
|
|
||||||
|
|
||||||
.input-wrap {
|
|
||||||
display: flex; align-items: center;
|
|
||||||
border: 2px solid #e2e8f0; border-radius: 14px;
|
|
||||||
background: #f8fafc;
|
|
||||||
transition: border-color .2s, box-shadow .2s, background .2s;
|
|
||||||
}
|
|
||||||
.input-wrap:focus-within {
|
|
||||||
border-color: #2563eb;
|
|
||||||
box-shadow: 0 0 0 4px rgba(37,99,235,.12);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.input-wrap--error { border-color: #ef4444; box-shadow: 0 0 0 4px rgba(239,68,68,.1); }
|
|
||||||
.input-wrap__prefix { padding: 0 4px 0 18px; font-size: 26px; font-weight: 700; color: #2563eb; user-select: none; line-height: 1; }
|
|
||||||
.input-wrap__input {
|
|
||||||
flex: 1; border: none; background: transparent;
|
|
||||||
padding: 16px 16px 16px 8px; font-size: 32px; font-weight: 700;
|
|
||||||
color: #0f172a; outline: none; min-width: 0; font-family: inherit;
|
|
||||||
appearance: textfield; -moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
.input-wrap__input::placeholder { color: #cbd5e1; }
|
|
||||||
.input-wrap__input::-webkit-outer-spin-button,
|
|
||||||
.input-wrap__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
||||||
@media (max-width: 480px) { .input-wrap__input { font-size: 28px; padding: 14px 14px 14px 6px; } }
|
|
||||||
|
|
||||||
.currency-badge {
|
|
||||||
display: flex; align-items: center; gap: 10px;
|
|
||||||
background: #f1f5f9; border-radius: 12px; padding: 12px 16px; margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.currency-badge__flag { font-size: 22px; line-height: 1; }
|
|
||||||
.currency-badge__code { font-size: 15px; font-weight: 700; color: #0f172a; }
|
|
||||||
.currency-badge__name { font-size: 13px; color: #64748b; margin-left: auto; }
|
|
||||||
|
|
||||||
.pay-btn {
|
|
||||||
width: 100%; display: flex; align-items: center; justify-content: center;
|
|
||||||
gap: 10px; padding: 17px 24px;
|
|
||||||
background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%);
|
|
||||||
color: #fff; border: none; border-radius: 14px;
|
|
||||||
font-size: 17px; font-weight: 700; letter-spacing: .2px;
|
|
||||||
cursor: pointer; font-family: inherit;
|
|
||||||
transition: opacity .15s, transform .1s, box-shadow .15s;
|
|
||||||
box-shadow: 0 6px 20px rgba(37,99,235,.38);
|
|
||||||
}
|
|
||||||
.pay-btn:hover { opacity: .92; box-shadow: 0 8px 28px rgba(37,99,235,.45); }
|
|
||||||
.pay-btn:active { transform: scale(.98); opacity: .88; }
|
|
||||||
.pay-btn:disabled { opacity: .6; cursor: not-allowed; transform: none; }
|
|
||||||
.pay-btn__icon { display: flex; align-items: center; }
|
|
||||||
@media (max-width: 480px) { .pay-btn { padding: 16px 24px; font-size: 16px; } }
|
|
||||||
|
|
||||||
.secure-badge {
|
|
||||||
display: inline-flex; align-items: center; gap: 6px;
|
|
||||||
font-size: 12px; color: #94a3b8; font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-input {
|
|
||||||
width: 100%; border: 2px solid #e2e8f0; border-radius: 14px;
|
|
||||||
background: #f8fafc; padding: 14px 16px;
|
|
||||||
font-size: 15px; font-weight: 500; color: #0f172a;
|
|
||||||
font-family: inherit; resize: vertical; outline: none;
|
|
||||||
transition: border-color .2s, box-shadow .2s, background .2s;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.note-input::placeholder { color: #cbd5e1; font-weight: 400; }
|
|
||||||
.note-input:focus {
|
|
||||||
border-color: #2563eb;
|
|
||||||
box-shadow: 0 0 0 4px rgba(37,99,235,.12);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
<div class="card">
|
|
||||||
|
|
||||||
<div class="card__header">
|
|
||||||
<div class="sbp-logo">
|
|
||||||
<img src="https://sbp.nspk.ru/storage/settings/common/logo/0645d335-8b62-43a1-9a33-0d4c9d1dc0e0.svg" alt="СБП" />
|
|
||||||
</div>
|
|
||||||
<h1 class="card__title">Оплата через СБП</h1>
|
|
||||||
<p class="card__subtitle">Система быстрых платежей</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card__body">
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="field__label" for="amount">Сумма платежа</label>
|
|
||||||
<div class="input-wrap" id="inputWrap">
|
|
||||||
<span class="input-wrap__prefix">₽</span>
|
|
||||||
<input
|
|
||||||
id="amount"
|
|
||||||
type="number"
|
|
||||||
class="input-wrap__input"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
inputmode="numeric"
|
|
||||||
placeholder="0"
|
|
||||||
autofocus
|
|
||||||
value="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span class="field__error" id="error"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="currency-badge">
|
|
||||||
<span class="currency-badge__flag">🇷🇺</span>
|
|
||||||
<span class="currency-badge__code">RUB</span>
|
|
||||||
<span class="currency-badge__name">Российский рубль</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="field__label" for="note">Примечание</label>
|
|
||||||
<textarea
|
|
||||||
id="note"
|
|
||||||
class="note-input"
|
|
||||||
placeholder="Причина платежа..."
|
|
||||||
rows="3"
|
|
||||||
maxlength="500"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="pay-btn" id="payBtn" onclick="goToPayment()">
|
|
||||||
<span class="pay-btn__icon">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/>
|
|
||||||
<line x1="1" y1="10" x2="23" y2="10"/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span id="btnText">Перейти к оплате</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card__footer">
|
|
||||||
<span class="secure-badge">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
||||||
</svg>
|
|
||||||
Защищённое соединение
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const API_URL = 'https://qr.vitanova.network:567/qr';
|
|
||||||
|
|
||||||
const amountInput = document.getElementById('amount');
|
|
||||||
const noteInput = document.getElementById('note');
|
|
||||||
const errorEl = document.getElementById('error');
|
|
||||||
const payBtn = document.getElementById('payBtn');
|
|
||||||
const btnText = document.getElementById('btnText');
|
|
||||||
const inputWrap = document.getElementById('inputWrap');
|
|
||||||
|
|
||||||
amountInput.addEventListener('input', function () {
|
|
||||||
if (Number(this.value) > 0) {
|
|
||||||
errorEl.textContent = '';
|
|
||||||
inputWrap.classList.remove('input-wrap--error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getPaymentId() {
|
|
||||||
return new URLSearchParams(window.location.search).get('id');
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLoading(loading) {
|
|
||||||
payBtn.disabled = loading;
|
|
||||||
btnText.textContent = loading ? 'Подождите...' : 'Перейти к оплате';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(msg) {
|
|
||||||
errorEl.textContent = msg;
|
|
||||||
inputWrap.classList.add('input-wrap--error');
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPayment() {
|
|
||||||
const amount = Number(amountInput.value);
|
|
||||||
|
|
||||||
if (!amount || amount <= 0) {
|
|
||||||
showError('Введите корректную сумму');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = getPaymentId();
|
|
||||||
if (!id) {
|
|
||||||
showError('Не указан идентификатор платежа (параметр id)');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorEl.textContent = '';
|
|
||||||
inputWrap.classList.remove('input-wrap--error');
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
fetch(API_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ payment: 'sbp', amount, currency: 'rub', id, note: noteInput.value.trim() })
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then(function (data) {
|
|
||||||
setLoading(false);
|
|
||||||
if (data && data.payload) {
|
|
||||||
window.location.href = data.payload;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
setLoading(false);
|
|
||||||
showError('Ошибка при создании платежа. Попробуйте ещё раз.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
{
|
{
|
||||||
"/proxy/legacy-qr": {
|
|
||||||
"target": "https://qr.vitanova.network:567",
|
|
||||||
"secure": false,
|
|
||||||
"changeOrigin": true,
|
|
||||||
"pathRewrite": { "^/proxy/legacy-qr": "" },
|
|
||||||
"logLevel": "debug"
|
|
||||||
},
|
|
||||||
"/proxy/fastcheck": {
|
"/proxy/fastcheck": {
|
||||||
"target": "https://api.fastcheck.store",
|
"target": "https://api.fastcheck.store",
|
||||||
"secure": true,
|
"secure": true,
|
||||||
|
|||||||
@@ -57,16 +57,6 @@
|
|||||||
|
|
||||||
<!-- Share row — always visible, enabled once amount is known -->
|
<!-- Share row — always visible, enabled once amount is known -->
|
||||||
<div class="share-row">
|
<div class="share-row">
|
||||||
<!-- <button type="button" class="share-btn share-btn--email" (click)="shareByEmail()"
|
|
||||||
[disabled]="fastcheckAmount() === null || amountLoading()"
|
|
||||||
[title]="'fastcheck.share_email' | translate">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
||||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
|
||||||
<path d="M2 7l10 7 10-7"/>
|
|
||||||
</svg>
|
|
||||||
{{ 'fastcheck.share_email' | translate }}
|
|
||||||
</button> -->
|
|
||||||
<button type="button" class="share-btn share-btn--tg" (click)="shareByTelegram()"
|
<button type="button" class="share-btn share-btn--tg" (click)="shareByTelegram()"
|
||||||
[disabled]="fastcheckAmount() === null || amountLoading()"
|
[disabled]="fastcheckAmount() === null || amountLoading()"
|
||||||
[title]="'fastcheck.share_tg' | translate">
|
[title]="'fastcheck.share_tg' | translate">
|
||||||
|
|||||||
@@ -28,12 +28,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .15s, border-color .15s;
|
transition: background .15s, border-color .15s;
|
||||||
|
|
||||||
&--email {
|
|
||||||
background: #f8fafc;
|
|
||||||
color: #475569;
|
|
||||||
&:hover { background: #e2e8f0; border-color: #cbd5e1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
&--tg {
|
&--tg {
|
||||||
background: #e7f3fe;
|
background: #e7f3fe;
|
||||||
color: #0088cc;
|
color: #0088cc;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Component, computed, inject, signal } from '@angular/core';
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { FastcheckService } from '../../fastcheck.service';
|
import { FastcheckService } from '../../fastcheck.service';
|
||||||
import { FASTCHECK_API } from '../../api';
|
import { FASTCHECK_API } from '../../api';
|
||||||
@@ -36,7 +35,6 @@ interface CheckFastcheckResponse {
|
|||||||
export class FastcheckPage {
|
export class FastcheckPage {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private store = inject(FastcheckService);
|
private store = inject(FastcheckService);
|
||||||
private router = inject(Router);
|
|
||||||
private i18n = inject(TranslationService);
|
private i18n = inject(TranslationService);
|
||||||
|
|
||||||
private t(key: string): string { return this.i18n.translate(key); }
|
private t(key: string): string { return this.i18n.translate(key); }
|
||||||
@@ -330,14 +328,6 @@ export class FastcheckPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shareByEmail(): void {
|
|
||||||
const num = this.fastcheckNumber();
|
|
||||||
const amount = this.fastcheckAmount();
|
|
||||||
const subject = encodeURIComponent('fastCHECK');
|
|
||||||
const body = encodeURIComponent(`Номер: ${num}\nСумма: ${amount} ₽\nhttps://qr.vitanova.network/`);
|
|
||||||
window.open(`mailto:?subject=${subject}&body=${body}`, '_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
shareByTelegram(): void {
|
shareByTelegram(): void {
|
||||||
this.loginOnly.set(true);
|
this.loginOnly.set(true);
|
||||||
this.openPopup();
|
this.openPopup();
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
<div class="page">
|
|
||||||
<div class="card">
|
|
||||||
|
|
||||||
<div class="card__header">
|
|
||||||
<div class="sbp-logo">
|
|
||||||
<img src="https://sbp.nspk.ru/storage/settings/common/logo/0645d335-8b62-43a1-9a33-0d4c9d1dc0e0.svg"
|
|
||||||
alt="СБП" />
|
|
||||||
</div>
|
|
||||||
<h1 class="card__title">{{ 'sbp.title' | translate }}</h1>
|
|
||||||
<p class="card__subtitle">{{ 'sbp.subtitle' | translate }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card__body">
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="field__label" for="amount">{{ 'sbp.amount_label' | translate }}</label>
|
|
||||||
<div class="input-wrap" [class.input-wrap--error]="error()">
|
|
||||||
<span class="input-wrap__prefix">₽</span>
|
|
||||||
<input
|
|
||||||
id="amount"
|
|
||||||
type="number"
|
|
||||||
class="input-wrap__input"
|
|
||||||
[ngModel]="amount()"
|
|
||||||
(ngModelChange)="onAmountChange($event)"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
inputmode="numeric"
|
|
||||||
placeholder="0"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
@if (error()) {
|
|
||||||
<span class="field__error">{{ error() }}</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="currency-badge">
|
|
||||||
<span class="currency-badge__flag">🇷🇺</span>
|
|
||||||
<span class="currency-badge__code">RUB</span>
|
|
||||||
<span class="currency-badge__name">{{ 'sbp.currency_name' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="field__label" for="note">{{ 'sbp.note_label' | translate }}</label>
|
|
||||||
<textarea
|
|
||||||
id="note"
|
|
||||||
class="note-input"
|
|
||||||
[ngModel]="note()"
|
|
||||||
(ngModelChange)="onNoteChange($event)"
|
|
||||||
[placeholder]="'sbp.note_placeholder' | translate"
|
|
||||||
rows="3"
|
|
||||||
maxlength="500"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (nspkUrl()) {
|
|
||||||
<div class="qr-pay">
|
|
||||||
<img
|
|
||||||
[src]="'https://api.qrserver.com/v1/create-qr-code/?size=240x240&margin=8&data=' + nspkUrl()"
|
|
||||||
width="240" height="240"
|
|
||||||
alt="SBP QR"
|
|
||||||
/>
|
|
||||||
<p class="qr-pay__hint">Отсканируйте QR-код в приложении вашего банка</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button class="pay-btn" type="button" (click)="pay()" [disabled]="loading() || !!nspkUrl()">
|
|
||||||
<span class="pay-btn__icon">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
||||||
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
|
|
||||||
<line x1="1" y1="10" x2="23" y2="10" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
@if (loading()) {
|
|
||||||
{{ 'sbp.pay_loading' | translate }}
|
|
||||||
} @else {
|
|
||||||
{{ 'sbp.pay_btn' | translate }}
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card__footer">
|
|
||||||
<span class="secure-badge">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
||||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
||||||
</svg>
|
|
||||||
{{ 'common.secure' | translate }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
@use './../../../shared' as *;
|
|
||||||
|
|
||||||
.sbp-logo {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
margin-bottom: 14px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 40px;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
height: 34px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency-badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
background: #f1f5f9;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
margin-bottom: 18px;
|
|
||||||
|
|
||||||
&__flag { font-size: 22px; line-height: 1; }
|
|
||||||
&__code { font-size: 15px; font-weight: 700; color: #0f172a; }
|
|
||||||
&__name { font-size: 13px; color: #64748b; margin-left: auto; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-pay {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__hint {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #64748b;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-input {
|
|
||||||
width: 100%;
|
|
||||||
border: 2px solid #e2e8f0;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: #f8fafc;
|
|
||||||
padding: 14px 16px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #0f172a;
|
|
||||||
font-family: inherit;
|
|
||||||
resize: vertical;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
&::placeholder { color: #cbd5e1; font-weight: 400; }
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #2563eb;
|
|
||||||
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import { Component, computed, inject, isDevMode, signal } from '@angular/core';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { TranslatePipe } from '../../translate/translate.pipe';
|
|
||||||
import { TranslationService } from '../../translate/translation.service';
|
|
||||||
|
|
||||||
interface LegacyPayResponse {
|
|
||||||
nspkurl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy SBP merchant payment flow.
|
|
||||||
* Activated when the root URL has `?id=<orderId>`.
|
|
||||||
* Mirrors public/payment.html behaviour:
|
|
||||||
* POST https://qr.vitanova.network:567/qr
|
|
||||||
* { payment, amount, currency, id, note } -> { payload: '<sbp-deep-link>' }
|
|
||||||
* then window.location.href = payload.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'app-legacy-pay-page',
|
|
||||||
imports: [FormsModule, TranslatePipe],
|
|
||||||
templateUrl: './legacy-pay-page.html',
|
|
||||||
styleUrl: './legacy-pay-page.scss'
|
|
||||||
})
|
|
||||||
export class LegacyPayPage {
|
|
||||||
private http = inject(HttpClient);
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private i18n = inject(TranslationService);
|
|
||||||
|
|
||||||
private t(key: string): string { return this.i18n.translate(key); }
|
|
||||||
|
|
||||||
private readonly LEGACY_API = isDevMode()
|
|
||||||
? '/proxy/legacy-qr/qr'
|
|
||||||
: 'https://qr.vitanova.network:567/qr';
|
|
||||||
|
|
||||||
amount = signal<number | null>(null);
|
|
||||||
note = signal<string>('');
|
|
||||||
error = signal<string>('');
|
|
||||||
loading = signal<boolean>(false);
|
|
||||||
nspkUrl = signal<string>('');
|
|
||||||
|
|
||||||
get isMobile(): boolean {
|
|
||||||
return window.innerWidth < 768;
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentId = signal<string>('');
|
|
||||||
|
|
||||||
canPay = computed(() => {
|
|
||||||
const a = this.amount();
|
|
||||||
return !!this.paymentId() && a !== null && a > 0 && !this.loading();
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const id = this.route.snapshot.queryParamMap.get('id') ?? '';
|
|
||||||
this.paymentId.set(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onAmountChange(value: number | null): void {
|
|
||||||
this.amount.set(value);
|
|
||||||
if (this.error()) this.error.set('');
|
|
||||||
}
|
|
||||||
|
|
||||||
onNoteChange(value: string): void {
|
|
||||||
this.note.set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pay(): void {
|
|
||||||
if (!this.canPay()) {
|
|
||||||
if (!this.paymentId()) {
|
|
||||||
this.error.set(this.t('errors.not_found'));
|
|
||||||
} else {
|
|
||||||
this.error.set(this.t('errors.invalid_amount'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.error.set('');
|
|
||||||
this.loading.set(true);
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
qrtype: 'QRDynamic',
|
|
||||||
amount: this.amount(),
|
|
||||||
currency: 'RUB',
|
|
||||||
partnerqrID: this.paymentId(),
|
|
||||||
qrDescription: this.note().trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.http.post<LegacyPayResponse>(this.LEGACY_API, body).subscribe({
|
|
||||||
next: (res) => {
|
|
||||||
this.loading.set(false);
|
|
||||||
if (res?.nspkurl) {
|
|
||||||
if (this.isMobile) {
|
|
||||||
window.location.href = res.nspkurl;
|
|
||||||
} else {
|
|
||||||
this.nspkUrl.set(res.nspkurl);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.error.set(this.t('errors.payment_failed'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
this.loading.set(false);
|
|
||||||
this.error.set(this.t('errors.lookup_failed'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user