Розробка шлюзу прийняття криптоплатежів
Типова постановка задачі: "хочемо приймати крипту як звичайний Stripe, тільки без посередників". На практиці це означає розв'язати кілька нетривіальних проблем одночасно — детекцію вхідних платежів без polling кожні кілька секунд, волатильність при конвертації, часткові платежі, порогові значення підтвердженнь для різних мереж та ідемпотентність під час мережевих збоїв.
Архітектура: з чого складається шлюз
Мінімально жизнездатний payment gateway складається з чотирьох компонентів:
[Клієнт] → [API Gateway] → [Payment Service]
↓
[Blockchain Listener]
↓
[Event Queue (Redis/Kafka)]
↓
[Settlement Service] → [ERP/CRM]
Payment Service — створює замовлення, генерує унікальну адресу (або payment ID), повертає клієнту дані для оплати. Stateful — зберігає mapping address → order.
Blockchain Listener — моніторить вхідні транзакції. Це найбільш критичний компонент з точки зору надійності. Два підходи:
-
WebSocket підписка (
eth_subscribe("logs", filter)абоeth_subscribe("newHeads")) — низька латентність, але з'єднання рвуться, потрібен reconnect з backoff та replay пропущених блоків. -
Polling + cursor — менш елегантно, але передбачувано. Зберігаємо останній оброблений блок, опитуємо
eth_getLogsз фільтром по адресах. Стійкіше до мережевих збоїв.
Для production: гібридний підхід — WebSocket для низької латентності, polling як fallback з cursor-based recovery.
Event Queue — буфер між listener та settlement. Kafka для високих навантажень, Redis Streams для середніх. Ключовий момент: listener публікує TransactionDetected event, settlement підписується. Це розв'язує компоненти та гарантує обробку при тимчасовому падінні settlement service.
Settlement Service — перевіряє confirmations, конвертує суму, оновлює статус замовлення, сповіщає upstream систему (webhook).
Детекція платежів: нюанси по мережах
EVM-мережі (ETH, BNB, Polygon, Arbitrum...)
Нативні переводи ETH: моніторимо через eth_subscribe("newHeads") + eth_getBlockByNumber та фільтруємо транзакції по to адресі.
ERC-20 токени (USDT, USDC, DAI): моніторимо подію Transfer(address indexed from, address indexed to, uint256 value) через eth_getLogs з фільтром:
const filter = {
fromBlock: 'latest',
topics: [
ethers.id('Transfer(address,address,uint256)'),
null, // from: будь-хто
ethers.zeroPadValue(paymentAddress, 32), // to: наша адреса
],
};
Важливо для USDT (Tether): він має нестандартний ERC-20 — функція transfer не повертає bool. Виклик через стандартний інтерфейс падає. Користуємо safeTransfer або низькорівневий call з перевіркою returndata.
Bitcoin та UTXO-модель
Для BTC немає поняття "адреса → транзакція" на рівні ноди. Використовуємо або:
- Electrum Server (Electrs) — індексує UTXO по адресам, дозволяє підписатися на адресу
- BlockCypher / Blockcypher WebHook API — hosted рішення, але залежність від третьої сторони
-
Bitcoin Core з
importaddress— додаємо адресу в wallet node, отримуємо сповіщення через ZMQ
Мінімальні confirmations для BTC: 1 для малих сум (<$100), 3 для середніх, 6 для великих. Для Ethereum достатньо 12–20 блоків (EIP-3607 finality не гарантована без Casper finalization).
TON
TON транзакції асинхронні: вхідний transfer — це bounce-able сообщение, ви повинні перевірити, що це саме transfer, а не bounce. Використовуємо TonAPI або TON Center API з webhook на адресу.
Порог підтвердження та захист від double-spend
Не можна вважати платіж завершеним після першого виявлення транзакції в mempool — це pending стан, не підтверджений. Мінімальні пороги:
| Мережа | Поріг | Обґрунтування |
|---|---|---|
| Ethereum | 12 блоків (~2.5 хв) | Після merge finality через checkpoint, але 12 блоків — практичний стандарт |
| BNB Chain | 15 блоків (~45 сек) | Централізована, але реорги трапляються |
| Polygon PoS | 128 блоків (~4 хв) | Checkpoint на Ethereum кожні ~30 хв, до цього реорги можливі |
| Bitcoin | 3–6 блоків (30–60 хв) | Класика, для великих сум |
| Arbitrum/Optimism | 1 блок (L2 finality) | Реорги на L2 практично неможливі |
Часткові платежі та надплати
Реальні користувачі іноді платять не точну суму — біржі знімають комісії, люди помиляються. Потрібна політика:
-
Недоплата: якщо отримано 99–100% суми — вважаємо оплаченим (толерантність 1%). Якщо менше —
partially_paid, чекаємо X хвилин на доплату, потімexpired. - Надплата: автоматично приймаємо, повертаємо різницю (потрібен refund flow) або зараховуємо як кредит.
Волатильність: курс на момент створення замовлення
Стандартна схема: фіксуємо курс криптовалюти до USD на момент створення invoice, даємо 15–30 хвилин на оплату. Джерело курсу — Chainlink Price Feed (on-chain) або CoinGecko/CoinMarketCap API (off-chain). Chainlink краще — менше vendor lock-in, але потребує web3 RPC.
При закінченні часу: статус expired, генерування нового invoice з актуальним курсом за запитом клієнта.
Webhook та ідемпотентність
Сповіщення upstream системи через webhook повинне бути ідемпотентним — можливі повторні доставки при retry. У payload включаємо payment_id (унікальний) + tx_hash + status. Upstream система повинна перевірити, чи вже обробила вона цей payment_id.
Retry політика: експоненціальний backoff, 5–10 спроб, потім dead letter queue для ручного розбору.
Стек
- Node.js + TypeScript або Go для listener та API — хороша підтримка web3 бібліотек
- ethers.js v6 або viem для EVM взаємодії
- PostgreSQL для зберігання платежів (ACID, транзакційність при оновленні статусів)
- Redis для rate limiting та кешування курсів
- Kafka або Redis Streams для event queue
- Grafana + Prometheus — моніторинг: lag listener vs chain head, швидкість обробки, помилки
Кастомний шлюз має сенс при обсязі >500 платежів/день або при спеціальних вимогах до приватності та контролю. Для менших обсягів — NOWPayments, CoinGate чи аналоги вирішують задачу дешевше.







