Розробка крипто-інвойсингу
Крипто-інвойсинг — це не "приймати оплату в крипте". Це створити систему, в якій клієнт отримує рахунок на визначену фіатну суму (або крипто-суму), оплачує її в одній з підтримуваних валют, а продавець отримує підтвердження з коректно сопоставленим інвойсом. Основна інженерна проблема — волатильність: якщо клієнт бачить рахунок на $500, а ETH зсунувся на 3% поки він переводив, потрібно вирішити, чия це проблема та як це обробляти.
Моделі ціноутворення інвойсів
Фіксована крипто-сума: рахунок на 0.5 ETH. Клієнт платить рівно 0.5 ETH. Без волатильності — волатильність фіатного еквівалента цілком на продавці. Підходить для crypto-native B2B розрахунків.
Фіксована фіатна сума з lock-in: рахунок на $500, система конвертує в крипто-суму по курсу на момент створення та фіксує її на 15–30 хвилин. Клієнт повинен оплатити фіксовану крипто-суму в вікні. Якщо вікно скінчилося — перераховуємо курс. Це найпоширеніша модель.
Floating з tolerance: приймаємо оплату в діапазоні ±1–2% від очікуваної суми. Дрібні розходження через комісії мережі або незначне рухання курсу не блокують платіж. Underpayment policy: кредитуємо на оплачену суму або вимагаємо доплату — налаштовується мерчантом.
Архітектура системи
Життєвий цикл інвойса
DRAFT → PENDING_PAYMENT (адреса призначена, таймер запущений)
→ PARTIALLY_PAID (отримана частина суми)
→ PAID (отримана повна сума, очікування confirmations)
→ CONFIRMED (N підтверджень)
→ EXPIRED (скінчилася без оплати)
→ OVERPAID (отримано більше — вимагає ручного рішення)
interface Invoice {
id: string;
merchantId: string;
fiatAmount: Decimal;
fiatCurrency: 'USD' | 'EUR' | 'GBP';
cryptoAmount: Decimal;
cryptoCurrency: 'ETH' | 'USDT' | 'USDC' | 'BTC';
depositAddress: string;
exchangeRateLockedAt: Date;
expiresAt: Date;
status: InvoiceStatus;
paidAmount: Decimal;
txHashes: string[];
}
Генерація адрес
Для кожного інвойса — унікальна адреса деривації з HD wallet xpub. Це дозволяє однозначно сопоставити вхідний платіж з інвойсом без memo/тегів:
function deriveInvoiceAddress(
xpub: string,
invoiceIndex: number,
network: Network
): string {
const node = HDNodeWallet.fromExtendedKey(xpub);
// path: m/44'/60'/0'/0/{invoiceIndex} для EVM
return node.deriveChild(invoiceIndex).address;
}
Для Bitcoin — нативні SegWit (bech32) адреси через BIP84 деривація. Для TRON USDT — та сама логіка, окремий xpub для TRC-20 простору.
Моніторинг входящих платежів
EVM-мережі: підписка через WebSocket eth_subscribe("logs") на Transfer подій для ERC-20 токенів, фільтр по списку активних deposit адрес. Для нативного ETH — моніторинг через eth_subscribe("newHeads") + eth_getTransactionReceipt.
const monitorERC20Transfers = async (
activeAddresses: Set<string>,
provider: WebSocketProvider
) => {
const filter = {
topics: [
ethers.id("Transfer(address,address,uint256)"),
null,
[...activeAddresses].map(addr => ethers.zeroPadValue(addr, 32))
]
};
provider.on(filter, async (log) => {
const invoiceAddress = ethers.getAddress('0x' + log.topics[2].slice(26));
const amount = BigInt(log.data);
await handleIncomingPayment(invoiceAddress, amount, log.transactionHash);
});
};
Мультивалютність та курси
Для inbound конвертації курсів — агрегація з кількох джерел з захистом від аномалій:
class PriceAggregator:
SOURCES = ['binance', 'coinbase', 'kraken']
MAX_DEVIATION_PCT = 1.0 # якщо один джерело відхиляється > 1% від медіани
async def get_price(self, base: str, quote: str) -> Decimal:
prices = await asyncio.gather(*[
self.fetch_price(source, base, quote)
for source in self.SOURCES
])
valid_prices = [p for p in prices if p is not None]
median = statistics.median(valid_prices)
# Відсіюємо аномальні значення
filtered = [
p for p in valid_prices
if abs(p - median) / median * 100 < self.MAX_DEVIATION_PCT
]
return Decimal(str(statistics.mean(filtered)))
Webhooks та інтеграція мерчантів
Мерчанти отримують сповіщення про статуси інвойсів через signed webhooks:
function signWebhookPayload(payload: object, secret: string): string {
const body = JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000);
const signature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return `t=${timestamp},v1=${signature}`;
}
Отримувач webhook верифікує підпис та timestamp (захист від replay attacks — відхиляти подій старше 5 хвилин). Це та сама схема, що використовує Stripe.
Retry політика для неуспішних доставок: експоненціальний backoff (1хв → 5хв → 30хв → 2год → 24год), після 5 неудачних спроб — алерт в admin panel.
Бухгалтерія та звітність
Для B2B застосувань важлива автоматична генерація:
- PDF-інвойса з фіатною суммою, крипто-суммою, exchange rate на момент створення, txHash
- CSV-експорту транзакцій з фіатним еквівалентом для бухгалтера
- Інтеграція з accounting API (Xero, QuickBooks) через їх REST API
Фіатний еквівалент для податкової звітності розраховується по курсу на момент підтвердження транзакції — фіксується та зберігається з інвойсом незмінно.
Стек та розгортання
- Backend: Node.js/TypeScript або Go, REST API + WebSocket для статусів
- Queue: BullMQ (Redis) для воркерів підтверджень та webhook доставки
- DB: PostgreSQL (інвойси, транзакції) + Redis (кеш курсів, активні адреси)
- Ноди: Alchemy/QuickNode з failover або власні ноди для високих обсягів
MVP з підтримкою ETH, USDT (ERC-20), USDC та базовим мерчант-порталом — 3–4 тижні. Повна система з мультивалютністю, звітністю та API для партнерів — 8–10 тижнів.







