This commit is contained in:
2026-05-07 00:13:45 +04:00
parent e0df81c071
commit d37ca14f69
20 changed files with 773 additions and 1124 deletions

View File

@@ -0,0 +1,142 @@
eFastcheck.store
General Information
Information exchange with the Fastcheck server is realized via RESTful API. All requests to the server must be executed via HTTPS using GET||POST||PUT||DELETE requests to the given ROOT address. Body of requests must be in JSON format. All not public requests must be signed by the client and the public key must be sent to the server for client identification and sign checking.
Check if server is available
Client needs to periodically check if the server is available by sending “ping” to the client. On error corresponding message must be shown.
Protocol: https
Root Path: api.Fastcheck.store
Type GET
Path /ping
Request Parameters:
{
}
Response (OK):
{
"message": "pong",
}
________________
Create new websession
Creates a new websession for qr generation. By timeout a new websession must be requested, after the user shows some activity (click on qr).
Protocol: https
Root Path: api.Fastcheck.store
Type GET
Path /websession
Request Parameters:
{
}
Response (OK):
{
"sessionId": “1AF3781BF6B94604B771AEA1D44FA63A”
"userId" : "",
"expires" : "sessionId",
"userSessionId": "",
"Status": false
}
________________
Check websession status
Check if the user is already logged in. a new websession for qr generation. By timeout a new websession must be requested, after the user shows some activity (click on qr).
Protocol: https
Root Path: api.Fastcheck.store
Type GET
Path /websession/:webSessionID
Request Parameters:
{
}
Response (OK):
{
"sessionId": “1AF3781BF6B94604B771AEA1D44FA63A”,
"userId" : "kHaAe9roaC2uq63AKGE/8+Ti/t/iFro68QhEZ1dRGLo",
"expires" : "sessionId",
"userSessionId": "8A94EFEFD003426A9B456C48CAC99BE6",
"Status": true
}
________________
Delete websession status
Delete the session to log out from the system.
Protocol: https
Root Path: api.Fastcheck.store
Type DELETE
Path /websession/:webSessionID
Request Parameters:
{
"sessionId": “1AF3781BF6B94604B771AEA1D44FA63A”
}
Response (OK):
{
}
________________
Check Fastcheck status
Check if fastcheck exists and get the amount assigned to check.
Protocol: https
Root Path: api.Fastcheck.store
Type GET
Path /fastcheck
Request Parameters:
{
"fastcheck": “1234-5678-0001”,
}
Response (OK):
{
"fastcheck": "1234-5678-0001",
"expiration": 2021-07-07T09:08:18Z ,
"Status": true
}
________________
New Fastcheck
Create a fastcheck for a given amount. The Users must have a sufficient amount on the balance.
Protocol: https
Root Path: api.Fastcheck.store
Type POST
Path /fastcheck
HEADER: Authorization - {"sessionID": "1AF3781BF6B94604B771AEA1D44FA63A"}
Request Parameters:
{
"amount": 158000,
"currency": "RUB"
}
Response (OK):
{
"fastcheck": "1234-5678-0001",
"expiration": 2021-07-07T09:08:18Z ,
"code": "5864",
"Status": true
}
________________
Accept Fastcheck
Accept fastcheck to the user balance.
Protocol: https
Root Path: api.Fastcheck.store
Type POST
Path /fastcheck
HEADER: Authorization - {"sessionID": "1AF3781BF6B94604B771AEA1D44FA63A"}
Request Parameters:
{
"fastcheck": "1234-5678-0001",
"code": "5864"
}
Response (404-ERROR):
{
"message": "not authorized"
}
Response (200-OK):
{
"message": "ok"
}

262
public/SBP QR API.txt Normal file
View File

@@ -0,0 +1,262 @@
General Information
Information exchange with the SBP server is realized via RESTful API. All requests to the server must be executed via HTTPS using GET||POST||PUT||DELETE requests to the given ROOT address. Body of requests must be in JSON format. All not public requests must be signed by the client and the public key must be sent to the server for client identification and sign checking.
Header:
“Authorization”: {JSON WITH KEY AND PARTNERID}
Check if server is available
Client needs to periodically check if the server is available by sending “ping” to the client. On error corresponding message must be shown.
Protocol: https
Root Path: QR.VITANOVA.NETWORK
Type GET
Path /ping
Request Parameters:
{
}
Response (Error):
{
"message": "pong",
"status": "Wrong Header"
}
Response (OK):
{
"message": "pong",
"status": "Correct Header"
}
________________
Create New QR code
Create New QR for payment via SBP
Protocol: https
Root Path: QR.VITANOVA.NETWORK
Type POST
Path /qr
Request Parameters:
{
"amount": 10.00, //amount from 10Rub to 499.000 Rub
"qrDescription": "Item description",
"order": "540", //orderid at partners platform
"partnerID": 102 //same as in header
"Phonemask": 79xxxx66265 //User phone number mask, needed only for crypto based operations. Payment will be accepted only from phone numbers corresponding to the mask
"Namelastname": Hakxx Sargxxxx /Mask for User name, lastname in cyrilic, needed only for crypto based operations. Payment will be accepted only from the user corresponding to that mask.
}
Response !=200(Error):
{
"error": "wrong key"
}
Response =200(OK):
{
"qrId": "BD10002CI1V3JP1T8QR8TIQ8K35RBVQB",
"qrStatus": "NEW",
"qrExpirationDate": "2025-11-20T10:10:44Z",
"Payload": "https://qr.nspk.ru/BD10002CI1V3JP1T8QR8TIQ8K35RBVQB?type=02&bank=100000000007&sum=1000&cur=RUB&crc=8ACC",
"qrUrl": "https://e-commerce.raiffeisen.ru/api/sbp/v1/qr/BD10002CI1V3JP1T8QR8TIQ8K35RBVQB/image"
}
________________
Check Dynamic QR code
Check QR status
Protocol: https
Root Path: QR.VITANOVA.NETWORK
Type GET
Path /qr/dynamic/{qrId}
Request Parameters:
{
}
Response !=200(Error):
{
"error": "Error from the bank "
}
Response =200(OK):
{
`json:"nspkID"` //": "AD100060JFQF8FSB9Q28FFL88IH6SST0" `json:"amount"` // "1235"
`json:"currency" // "RUB"
`json:"order"` // "126" partner order id PaymentDetails
`json:"paymentDetails"` // "Назначение платежа 2",
`json:"qrType"` //"QRDynamic",
`json:"qrExpirationDate"` //: "2025-11-22T09:14:38+03:00" `json:"sbpBank"` // "raiffeisen"
`json:"sbpMerchant"` //"Dexar"
`json:"sbpMerchantId"` //"", uint64
`json:"sbpOperationId"` //0 Status
`json:"status"` //": "NEW", "APPROVED", "REJECTED", "COMPLETED"
`json:"nspkurl"` //"https://qr.nspk.ru/AD100060JFQF8FSB9Q28FFL88IH6SST0
`json:"statusurl"` // "https://partner.com/1234321/status" url for checking QR `json:"redirectUrl"` //"https://fastcheck.store/"
`json:"qrDescription"` //"QR для оплаты заказа"
`json:"additionalInfo"` // TTL
`json:"TTL"` //10 timeout in minutes
`json:"callbackUrl"` // https://partner.com/1234321 callback after QR get paid
`json:"retry"` //0 retry count for calling partner
`json:"partnerID"` //103 Partner created QR PartnerqrID `json:"partnerqrID"` //QR ID in partner system RequestIP
`json:"requestIP"` //IP address of client requested QR
}
________________
Check Static QR code
Get all qr-s paid by static QR for today, skipping already read qr codes
Protocol: https
Root Path: QR.VITANOVA.NETWORK
Type GET
Path /qr/static/{qrId}?skip=25
Request Parameters:
{
}
Response =200(OK):
[{
`json:"nspkID"` //": "AD100060JFQF8FSB9Q28FFL88IH6SST0" `json:"amount"` // "1235"
`json:"currency" // "RUB"
`json:"order"` // "126" partner order id PaymentDetails
`json:"paymentDetails"` // "Назначение платежа 2",
`json:"qrType"` //"QRDynamic",
`json:"qrExpirationDate"` //: "2025-11-22T09:14:38+03:00" `json:"sbpBank"` // "raiffeisen"
`json:"sbpMerchant"` //"Dexar"
`json:"sbpMerchantId"` //"", uint64
`json:"sbpOperationId"` //0 Status
`json:"status"` //": "NEW", "APPROVED", "REJECTED", "COMPLETED"
`json:"nspkurl"` //"https://qr.nspk.ru/AD100060JFQF8FSB9Q28FFL88IH6SST0
`json:"statusurl"` // "https://partner.com/1234321/status" url for checking QR `json:"redirectUrl"` //"https://fastcheck.store/"
`json:"qrDescription"` //"QR для оплаты заказа"
`json:"additionalInfo"` // TTL
`json:"TTL"` //10 timeout in minutes
`json:"callbackUrl"` // https://partner.com/1234321 callback after QR get paid
`json:"retry"` //0 retry count for calling partner
`json:"partnerID"` //103 Partner created QR PartnerqrID `json:"partnerqrID"` //QR ID in partner system RequestIP
`json:"requestIP"` //IP address of client requested QR
}]
________________
Delete QR
Delete unused QR. If QR is not paid until expiration time, it will be automatically deleted.
Protocol: https
Root Path: QR.VITANOVA.NETWORK
Type DELETE
Path /qr/{qrId}
Request Parameters:
{
}
Response !=200(Error):
{
"error": "Error from the bank "
}
Response =200(OK):
{
}
________________
Check Partner
Returns partner status, with balance and transactions. Each transaction id is QR code, which can be checked additionally.
Root Path: API.VITANOVA.NETWORK
Type Get
Path /partners/{partnerID}
Request Parameters:
{
}
Response !=200(Error):
{
"error": "Not authorized "
}
Response =200(OK):
{
"telegram_id": 8285633,
"username": "ZZZ",
"first_name": "АMAN",
"last_name": "",
"balance": 22,
"transaction": [
{
"additionalInfo": "Ручка",
"paymentPurpose": "Ручка",
"amount": 22,
"code": "SUCCESS",
"createDate": "2025-11-22T15:57:40.925104+03:00",
"currency": "RUB",
"order": "8285633735_301",
"paymentStatus": "SUCCESS",
"qrId": "AD10004C1K9N71MN907RD56UOA0BHIBR",
"transactionDate": "2025-11-22T15:58:14.814187+03:00",
"transactionId": 771515533,
"qrExpirationDate": "2025-11-22T16:12:40+03:00"
}
],
"inn": 0
}
________________
Withdraw
Get amount from balance and Creates fastcheck, which then can be for buying usdt, transferring to bank account and to bank card. Fastcheck can be checked on site or via API only by Id, but can be used only with code.
Root Path: QR.VITANOVA.NETWORK
Type POST
Path/partners/withdraw/{partnerID}
Request Parameters:
{
“amount”: 10600.00
“currency”: “RUB”
“partnerId: “1023454”
“wallet”: “TBia4uHnb3oSSZm5isP284cA7Np1v15Vhi”
“”
“rate”:79.50
}
Response !=200(Error):
{
"error": "Not enough amount on balance "
}
Response !=200(Error):
{
"error": "Rate is not correct "
}
Response =200(OK):
{
“trxID”:”T5Mv2v8n9L7jY4k1pW3QhUoZfE9R1X3s7rY6tB0pA2C4D6E8F5H”
}
________________
RATE
Get currency exchange rate.
Root Path: QR.VITANOVA.NETWORK
Type GET
Path/partners/rate
Request Parameters:
Response !=200(Error):
{
"error": "Not Authorized "
}
Response =200(OK):
{
"rate": 78.5
}

26
public/example.json Normal file
View File

@@ -0,0 +1,26 @@
{
"nspkID": "AD10001UFDAT5FDD9LVPEJVDHN75UHHK",
"amount": 100,
"currency": "RUB",
"order": "",
"paymentDetails": "",
"qrType": "QRDynamic",
"qrExpirationDate": "",
"qrExecutionDate": "",
"sbpBank": "",
"sbpMerchant": "",
"sbpMerchantId": "",
"sbpOperationId": 633730903,
"status": "",
"nspkurl": "https://qr.nspk.ru/AD10001UFDAT5FDD9LVPEJVDHN75UHHK",
"statusurl": "https://pay.payworld.online/webapi/SbpPaymentStatus?id=3426680585\u0026sbpOperationId=633730903\u0026sector=24819\u0026signature=NjZkZjMyZDBjZTI1Zjc1NWUyZjYxNDRkM2ZjN2ViZWQ2YzcwYTc0NWM1ZWRlMGE1YzQyZWNlOTVjNWY1ODUxYg%3D%3D",
"redirectUrl": "",
"qrDescription": "Payment for shop 00897f30-414f-4b89-b1af-25596b15411c",
"additionalInfo": "",
"TTL": 0,
"callbackUrl": "",
"retry": 0,
"partnerID": "",
"partnerqrID": "00897f30-414f-4b89-b1af-25596b15411c",
"requestIP": ""
}

View File

@@ -62,7 +62,8 @@
"create_btn": "Create",
"amount_hint": "Allowed amount:",
"qr_label": "Scan QR to pay",
"qr_waiting": "Waiting for payment confirmation…"
"qr_waiting": "Waiting for payment confirmation…",
"payment_done": "Payment completed successfully."
},
"sbp": {
"title": "Pay via SBP",

View File

@@ -62,7 +62,8 @@
"create_btn": "Ստեղծել",
"amount_hint": "Թույլատրելի գումար՝",
"qr_label": "Սկանավորեք QR-կոդը վճարելու համար",
"qr_waiting": "Սպասում ենք վճարման հաստատման…"
"qr_waiting": "Սպասում ենք վճարման հաստատման…",
"payment_done": "Վճարումը հաջողությամբ ավարտվեց."
},
"sbp": {
"title": "Վճարել SBP-ով",

View File

@@ -62,7 +62,8 @@
"create_btn": "Создать",
"amount_hint": "Допустимая сумма:",
"qr_label": "Отсканируйте QR для оплаты",
"qr_waiting": "Ожидаем подтверждения оплаты…"
"qr_waiting": "Ожидаем подтверждения оплаты…",
"payment_done": "Оплата успешно завершена."
},
"sbp": {
"title": "Оплата через СБП",

302
public/index (4).html Normal file
View File

@@ -0,0 +1,302 @@
<!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="50"
/>
</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');
}
async 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);
try {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
qrtype: 'QRDynamic',
amount: amount,
currency: 'RUB',
partnerqrID: id,
qrDescription: noteInput.value.trim()
})
});
const data = await response.json();
if (data.nspkurl) {
window.location.href = data.nspkurl;
} else {
showError("nspkurl не найден в ответе сервера");
setLoading(false);
}
} catch (error) {
console.error(error);
showError("Ошибка запроса");
setLoading(false);
}
}
</script>
</body>
</html>