1. Общ процес
Сайтът на търговеца не комуникира директно с банката и не получава картови данни. Той заявява плащане към KCC Digital Payments, отваря получения банков адрес и периодично проверява резултата.
https://pay.kirovevents.eu/api. Браузърът трябва да извиква локален Worker/backend endpoint, а не този адрес директно.2. Архитектура и API token
Production интеграцията има три отделни части. HTML/JavaScript формата работи в браузъра. Малък backend или serverless Worker пази API token-а. KCC Digital Payments комуникира с банката и пази подробния платежен запис.
Браузър (HTML/JS)
↓ заявка към същия домейн
Backend / Cloudflare Worker
↓ Authorization: Bearer SECRET_TOKEN
https://pay.kirovevents.eu/api
↓ защитена server-to-server заявка
Банка ДСКЗащо е необходим backend/Worker?
Vanilla HTML/JS няма защитено място за секрет. Всичко в JavaScript, HTML, browser storage или Network панела е видимо за посетителя. Затова token-ът не може да се поставя във frontend кода.
Достъп на сайт
- KCC Digital създава API клиент през admin портала.
- За клиента се задават име,
client_id, активен статус и позволени HTTPS origin-и. - Генерира се случаен Bearer token, който се показва само веднъж.
- В базата се пази само SHA-256 хешът му.
- При регенериране старият token спира незабавно.
- При спиране на клиента всички нови create заявки се отказват.
2. Предварителна подготовка
Преди интеграцията трябва да бъдат уточнени:
- публичният HTTPS домейн на сайта;
- страницата, която стартира плащането;
- адресът за връщане след плащане;
- начинът, по който се генерира уникалният номер на поръчката;
- сумата и описанието на плащането.
Домейнът трябва предварително да бъде добавен в allowlist-а на KCC Digital Payments. Заявка от непозволен домейн се отхвърля.
3. Минимален HTML формуляр
Формулярът може да бъде поставен във всяка обикновена HTML страница. Имената и дизайнът са свободни; JavaScript трябва да може да прочете номера на поръчката и сумата.
HTML
<form id="payment-form">
<label>
Номер на поръчка
<input id="order-number" required maxlength="36">
</label>
<label>
Сума в евро
<input id="amount" type="number"
min="0.01" step="0.01" required>
</label>
<button id="pay-button" type="submit">Плати</button>
<p id="payment-message" aria-live="polite"></p>
</form>| Поле | Изискване | Пример |
|---|---|---|
order_number задължително | Уникално за всяко плащане; латински букви, цифри, точка, тире или долна черта; максимум 64 знака. | ORDER-2026-1001 |
amount задължително | Сума в EUR, минимум 0.01, с максимум два знака след десетичната точка. | 49.90 |
description задължително | Кратко описание, максимум 255 знака. Да не съдържа лични или картови данни. | Регистрация ORDER-2026-1001 |
return_url задължително | HTTPS адрес от предварително разрешен домейн. | https://example.com/payment-result |
4. Създаване на плащане
Изпраща се JSON чрез POST към /api?action=create.
Примерна заявка
const response = await fetch(
'https://pay.kirovevents.eu/api?action=create',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.KCC_PAYMENT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_number: 'ORDER-2026-1001',
amount: '49.90',
description: 'Регистрация ORDER-2026-1001',
return_url: 'https://example.com/payment-result'
})
}
);
const payment = await response.json();
if (!response.ok) throw new Error(payment.message);При успех API връща HTTP 201:
{
"payment_id": "9d76c0b5a4174c9ba2a647cb2db53c40",
"form_url": "https://...bank-payment-page..."
}payment_idсе пази от сайта и служи за проверка на статуса.form_urlе банковата страница, която трябва да се отвори за клиента.
Cloudflare Worker настройка
Изтеглете готовия Cloudflare Worker adapter и vanilla frontend модула. Token-ът се добавя само като Worker secret:
npx wrangler secret put KCC_PAYMENT_TOKENВ съществуващия Worker добавете:
import { handlePaymentApi } from './cloudflare-payment-api.js';
export default {
async fetch(request, env, ctx) {
const paymentResponse = await handlePaymentApi(request, env);
if (paymentResponse) return paymentResponse;
return handleExistingSite(request, env, ctx);
}
};Adapter-ът обслужва POST /api/payments/create и GET /api/payments/status. Другите маршрути на сайта остават непроменени.
Публикуване:
npx wrangler deployFrontend: нов таб и автоматична проверка
Празният таб се отваря директно при клика, преди асинхронната заявка. Това предотвратява блокирането му като popup.
Готов JavaScript пример
const CREATE_API = '/api/payments/create';
const STATUS_API = '/api/payments/status';
const form = document.querySelector('#payment-form');
const message = document.querySelector('#payment-message');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const paymentTab = window.open('about:blank', 'bank-payment');
const orderNumber = document.querySelector('#order-number').value;
try {
const response = await fetch(CREATE_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_number: orderNumber,
amount: document.querySelector('#amount').value,
description: `Поръчка ${orderNumber}`,
return_url: `${location.origin}${location.pathname}`
})
});
const data = await response.json();
if (!response.ok) throw new Error(data.message);
if (paymentTab) paymentTab.location = data.form_url;
else location.href = data.form_url;
checkStatus(data.payment_id);
} catch (error) {
if (paymentTab) paymentTab.close();
message.textContent = error.message;
}
});
async function checkStatus(paymentId) {
const response = await fetch(
`${STATUS_API}?payment_id=${encodeURIComponent(paymentId)}`
);
const data = await response.json();
if (['paid', 'declined', 'cancelled', 'refunded'].includes(data.status)) {
message.textContent = data.status === 'paid'
? 'Плащането е успешно.'
: `Плащането приключи със статус: ${data.status}`;
return;
}
setTimeout(() => checkStatus(paymentId), 3000);
}
window.addEventListener('message', (event) => {
if (event.origin !== 'https://pay.kirovevents.eu') return;
if (event.data?.type !== 'kirov-payment-return') return;
checkStatus(event.data.payment_id);
});Препоръчителният интервал е 3 секунди през първата минута и 10 секунди след това, до максимум 20 минути. Polling-ът трябва да спира при окончателен статус.
6. Проверка и статуси
Статусът се получава чрез:
GET https://pay.kirovevents.eu/api?action=status&payment_id=PAYMENT_IDПримерен публичен отговор:
{
"payment_id": "9d76c0b5a4174c9ba2a647cb2db53c40",
"order_number": "ORDER-2026-1001",
"status": "paid",
"amount": "49.90",
"currency": "EUR",
"paid_at": "2026-08-30T13:20:15+00:00",
"message": ""
}| Статус | Значение | Окончателен |
|---|---|---|
pending | Очаква плащане или банкова обработка. | Не |
authorized | Авторизирано, но все още незавършено плащане. | Не |
partially_paid | Частично завършване. | Не |
paid | Плащането е успешно завършено. | Да |
declined | Плащането е отказано. | Да |
cancelled | Авторизацията е отменена. | Да |
refunded | Сумата е възстановена. | Да |
7. Обработка на грешки
| HTTP код | Причина | Действие |
|---|---|---|
403 | Домейнът не е разрешен. | Свържете се с KCC Digital за добавяне в allowlist. |
405 | Неправилен HTTP метод. | Използвайте POST за create и GET за status. |
422 | Невалидно поле или return URL. | Покажете върнатото message и коригирайте данните. |
502 | Временен проблем с платежната услуга или банката. | Не създавайте автоматично дубликат; уведомете клиента и позволете нов опит. |
Винаги проверявайте response.ok преди използване на върнатите данни.
8. Задължителни правила за сигурност
- Никога не поставяйте банкови credentials или други секрети във frontend JavaScript.
- Не приемайте успех от URL параметър или от текст, показан в браузъра.
- Всеки
order_numberтрябва да бъде уникален. - Не поставяйте имена, ЕГН, картови номера или други лични данни в
description. - Използвайте само HTTPS.
- Сверявайте върнатите
order_number,amountиcurrencyс очакваната поръчка. - Не позволявайте повторно предоставяне на услуга за вече обработено плащане.
9. Финален checklist
- Домейнът е добавен в allowlist.
- Формулярът генерира уникален номер на поръчката.
- Сумата е в EUR и е с максимум два десетични знака.
return_urlе HTTPS адрес от разрешения домейн.- Новият таб се отваря директно при клика.
payment_idсе запазва до окончателния резултат.- Статусът се проверява периодично.
- Услугата се предоставя само при
status: paid. - Тествани са успешно плащане, отказ, затворен банков таб и popup blocker.
След изпълнение на checklist-а интеграцията е готова за общ приемателен тест с KCC Digital.
Подробен API договор и граници
Create заявка — само от backend/Worker
POST https://pay.kirovevents.eu/api?action=create
Authorization: Bearer KCC_PAYMENT_TOKEN
Content-Type: application/json| Поле | Формат | Правила |
|---|---|---|
order_number | string, 1–36 | Уникално в рамките на API клиента. Позволени са A–Z, a–z, 0–9, точка, тире и долна черта. Повторение се отказва. |
amount | decimal string | EUR от 0.01 до 999999.99; десетична точка; максимум два знака след нея. Не използвайте floating-point изчисления за бизнес логика. |
description | string, 1–255 | Кратко техническо описание без лични, картови или чувствителни данни. |
return_url | HTTPS URL | Трябва да принадлежи на origin, разрешен за конкретния token. Външен домейн, HTTP, credentials в URL или непозволен поддомейн се отказват. |
Create отговор
HTTP 201
{
"payment_id": "128-bit opaque identifier",
"form_url": "https://bank.example/payment/..."
}form_url се използва единствено за пренасочване към банката. payment_id е capability идентификатор и не трябва да се публикува, индексира или изпраща на трети страни.
Status заявка
GET https://pay.kirovevents.eu/api?action=status&payment_id=PAYMENT_IDBackend proxy може да препрати тази заявка като локален /api/payments/status. Не е необходим Bearer token, защото случайният payment_id е непрозрачен 128-битов идентификатор.
Какво се връща към сайта
payment_idи клиентскиятorder_number;- нормализиран
status; amountкато стойност с два знака;currency— EUR;paid_at, когато е налично;- безопасно съобщение от обработката.
Какво остава само в KCC Digital Payments
- банковият
orderId, пълните банкови отговори и техническите кодове; - историята на промените и API журналът;
- наличните маскирани картови/авторизационни полета;
- terminal, approval и authorization референции;
- банковите credentials и клиентските token хешове.
API никога не връща пълен картов номер, CVV/CVC, банкова парола или Bearer token.
Разрешено и забранено
| Може | Не може |
|---|---|
| Един token да се използва от регистрирания backend/Worker. | Token да бъде поставен в HTML, frontend bundle, URL или Git. |
| Да се използват няколко позволени origin-а за един клиент. | Да се подаде произволен return URL или HTTP адрес. |
| Статусът да се проверява периодично до 20 минути. | Връщането от банката само по себе си да се приема за успех. |
При paid услугата да се маркира еднократно като платена. | Услуга да се предостави при pending, authorized или по текст от браузъра. |
| Token да се регенерира при съмнение за компрометиране. | Стар и нов token да работят едновременно след регенериране. |
| Клиентът да бъде спрян от admin портала. | Спрян клиент да създава нови плащания. |
HTTP отговори
201— плащането е регистрирано;401— липсващ, невалиден, регенериран token или спрян клиент;403— origin-ът не принадлежи на клиента;405— неправилен HTTP метод;422— невалидни полета или return URL;502— временна грешка при платежната услуга/банката.
Вариант за стандартен PHP хостинг
При сайт с PHP не е необходим Cloudflare Worker. Изтеглете готовия PHP proxy, запазете го като payment-proxy.php и настройте следните environment стойности:
KCC_PAYMENT_TOKEN=полученият-секретен-token
KCC_SITE_ORIGIN=https://example.comFrontend-ът използва същата форма и логика, но извиква локалния PHP файл:
POST /payment-proxy.php?action=create
GET /payment-proxy.php?action=status&payment_id=...- Token-ът не се поставя във frontend JavaScript.
- Най-добре environment стойностите да се зададат от hosting control panel.
- Ако се използва PHP config, той трябва да бъде извън public web root и извън Git.
- PHP трябва да има активирани cURL и проверка на HTTPS сертификати.
- При динамична цена PHP трябва да изчисли сумата server-side.
getenv().