Разработка крипто-инвойсинга
Крипто-инвойсинг — это не "принимать оплату в крипте". Это создать систему, в которой клиент получает счёт на определённую фиатную сумму (или крипто-сумму), оплачивает её в одной из поддерживаемых валют, а продавец получает подтверждение с корректно сопоставленным инвойсом. Основная инженерная проблема — волатильность: если клиент видит инвойс на $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 недель.







