Ръководство за разработчици

Интеграция на електронно плащане

Практическа инструкция за добавяне на плащане към стандартен уебсайт без CMS или framework.

1. Общ процес

Сайтът на търговеца не комуникира директно с банката и не получава картови данни. Той заявява плащане към KCC Digital Payments, отваря получения банков адрес и периодично проверява резултата.

01 · СъздаванеСайтът изпраща поръчка и сума към API.
02 · ПлащанеКлиентът се пренасочва към защитената банкова страница.
03 · ПроверкаKCC Digital проверява резултата директно в банката.
04 · РезултатСайтът получава нормализиран статус.
Backend API адрес: 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 заявки се отказват.
Token-ът е еквивалент на парола. Той се пази само като Cloudflare secret, environment variable или защитена server configuration. Не се поставя в HTML, JavaScript, Git или публичен конфигурационен файл.

2. Предварителна подготовка

Преди интеграцията трябва да бъдат уточнени:

  • публичният HTTPS домейн на сайта;
  • страницата, която стартира плащането;
  • адресът за връщане след плащане;
  • начинът, по който се генерира уникалният номер на поръчката;
  • сумата и описанието на плащането.

Домейнът трябва предварително да бъде добавен в allowlist-а на KCC Digital Payments. Заявка от непозволен домейн се отхвърля.

Важно: сайтът трябва да работи през HTTPS. Адреси с HTTP или външни, нерегистрирани домейни не могат да бъдат използвани за връщане.

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. Другите маршрути на сайта остават непроменени.

Сумата трябва да бъде надеждна. При динамични цени Worker-ът трябва да изчисли или прочете сумата от server-side източник и да замени стойността от браузъра.

Публикуване:

npx wrangler deploy

Frontend: нов таб и автоматична проверка

Празният таб се отваря директно при клика, преди асинхронната заявка. Това предотвратява блокирането му като 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Сумата е възстановена.Да
Източник на истината: резултатът от status endpoint-а. Самото връщане на клиента от банката не доказва успешно плащане.

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 с очакваната поръчка.
  • Не позволявайте повторно предоставяне на услуга за вече обработено плащане.
За динамични цени: когато сумата се определя от продукти, отстъпки или потребителски права, тя трябва да бъде изчислена и потвърдена от backend/Worker, а не да се приема безусловно от HTML поле.

9. Финален checklist

  1. Домейнът е добавен в allowlist.
  2. Формулярът генерира уникален номер на поръчката.
  3. Сумата е в EUR и е с максимум два десетични знака.
  4. return_url е HTTPS адрес от разрешения домейн.
  5. Новият таб се отваря директно при клика.
  6. payment_id се запазва до окончателния резултат.
  7. Статусът се проверява периодично.
  8. Услугата се предоставя само при status: paid.
  9. Тествани са успешно плащане, отказ, затворен банков таб и 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_numberstring, 1–36Уникално в рамките на API клиента. Позволени са A–Z, a–z, 0–9, точка, тире и долна черта. Повторение се отказва.
amountdecimal stringEUR от 0.01 до 999999.99; десетична точка; максимум два знака след нея. Не използвайте floating-point изчисления за бизнес логика.
descriptionstring, 1–255Кратко техническо описание без лични, картови или чувствителни данни.
return_urlHTTPS 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_ID

Backend 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 — временна грешка при платежната услуга/банката.
Повторен опит: не изпращайте безкрайно същия create request. Проверете дали поръчката вече съществува и при нужда генерирайте нов уникален номер само след ясно действие от потребителя.

Вариант за стандартен PHP хостинг

При сайт с PHP не е необходим Cloudflare Worker. Изтеглете готовия PHP proxy, запазете го като payment-proxy.php и настройте следните environment стойности:

KCC_PAYMENT_TOKEN=полученият-секретен-token
KCC_SITE_ORIGIN=https://example.com

Frontend-ът използва същата форма и логика, но извиква локалния 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.
Не записвайте token-а директно в proxy файла. Готовият пример го чете чрез getenv().