292 lines
10 KiB
HTML
292 lines
10 KiB
HTML
|
|
<!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>
|