Разработка платёжного виджета для криптовалют
Типичная постановка: "хотим принимать крипту на сайте, как PayPal но для USDT". На практике это означает решить несколько нетривиальных задач одновременно: генерировать уникальные адреса под каждый платёж, детектировать входящие транзакции, обрабатывать разные сети и токены, корректно работать с подтверждениями и reorg'ами. Готовые решения вроде Coinbase Commerce или NOWPayments берут 0.5–1% комиссии и имеют ограниченную кастомизацию. Собственный виджет оправдан при объёме от нескольких тысяч долларов в месяц или когда нужна глубокая интеграция с бизнес-логикой.
Архитектура: что внутри виджета
Виджет — это только UI-часть. Реальная работа происходит на бэкенде:
Frontend Widget
│ создать заказ / показать адрес и QR
▼
Backend API
│ генерация адреса → запись в БД → polling/webhook
▼
Blockchain Monitoring Service
│ отслеживает транзакции на адресах
▼
Payment Processor
│ подтверждение → коллбэк в приложение
Генерация адресов: HD Wallet
Для каждого платежа нужен уникальный адрес — иначе невозможно сопоставить входящий платёж с конкретным заказом. Стандартный подход — BIP-44 HD Wallet:
import { ethers } from 'ethers';
const masterWallet = ethers.HDNodeWallet.fromMnemonic(
ethers.Mnemonic.fromPhrase(process.env.PAYMENT_MNEMONIC!)
);
function derivePaymentAddress(orderId: number): string {
// path: m/44'/60'/0'/0/{orderId}
const child = masterWallet.derivePath(`m/44'/60'/0'/0/${orderId}`);
return child.address;
}
Мнемоника хранится в HSM или Vault, приватные ключи никогда не материализуются на сервере в plain text. Для мультивалютности: разные coin types по BIP-44 (60 для Ethereum/EVM, 0 для Bitcoin, 195 для Tron).
Для EVM-сетей с одинаковыми адресами (Ethereum, BNB Chain, Polygon, Arbitrum) один адрес работает во всех сетях — но мониторить нужно каждую сеть отдельно.
Мониторинг транзакций
Два подхода: polling RPC и webhook-подписки.
Polling — проще, но создаёт нагрузку:
async function pollAddress(address: string, network: string) {
const provider = getProvider(network);
// Для ERC-20 токенов слушаем Transfer events
const usdtContract = new ethers.Contract(USDT_ADDRESS, ERC20_ABI, provider);
const filter = usdtContract.filters.Transfer(null, address);
const latestBlock = await provider.getBlockNumber();
// Проверяем последние N блоков
const events = await usdtContract.queryFilter(filter, latestBlock - 10, latestBlock);
for (const event of events) {
await processIncomingPayment({
txHash: event.transactionHash,
amount: event.args.value,
token: 'USDT',
network,
});
}
}
Webhooks — через Alchemy, QuickNode или Moralis. Подписываетесь на события адреса, получаете push при каждой транзакции. Надёжнее polling, но зависите от провайдера:
// Alchemy Notify webhook
const webhook = await alchemy.notify.createWebhook(
'https://your-api.com/webhook/payment',
WebhookType.ADDRESS_ACTIVITY,
{ addresses: [paymentAddress] }
);
Подтверждения и защита от double-spend
Разные активы требуют разного числа подтверждений:
| Актив/сеть | Рекомендуемые подтверждения | Время |
|---|---|---|
| ETH / ERC-20 (Ethereum) | 12–20 блоков | ~3–4 мин |
| BNB Chain | 15–20 блоков | ~1 мин |
| Polygon | 100–150 блоков | ~4–6 мин |
| TRON TRC-20 | 20 блоков | ~1 мин |
| Bitcoin | 3–6 блоков | ~30–60 мин |
Polygon требует больше подтверждений из-за более высокой вероятности reorg. Не засчитывайте платёж как финальный до достижения нужного порога.
Для stablecoins: дополнительно проверяйте, что контракт токена — официальный. Пользователь может прислать поддельный токен с именем "USDT". Whitelist адресов контрактов обязателен.
UI/UX компоненты виджета
Минимальный набор для конверсии:
┌─────────────────────────────────────┐
│ Оплатить: 47.50 USDT │
│ │
│ Сеть: [Ethereum ▼] [BNB Chain ▼] │
│ │
│ [QR-код] 0x7f3a...b2c4 │
│ [Копировать] │
│ │
│ ⏱ Ожидание оплаты: 14:32 │
│ ● Ожидаем транзакцию... │
└─────────────────────────────────────┘
Критично: таймер сессии (обычно 15–30 минут), после которого адрес освобождается и курс пересчитывается. Статус обновляется через WebSocket или SSE — polling каждые 5 секунд раздражает и создаёт нагрузку.
Конвертация фиатной суммы в крипто: используйте Chainlink Price Feeds или CoinGecko API с кешированием. Добавляйте 1–2% буфер к курсу с учётом волатильности за время ожидания.
Обработка underpayment и overpayment
Реальные пользователи часто платят неточную сумму:
- Underpayment (прислали меньше): или блокируете заказ до доплаты, или принимаете с пометкой "частичная оплата" — зависит от бизнес-логики
- Overpayment (прислали больше): зачисляете как кредит пользователя, либо возвращаете разницу автоматически
- Комиссия сети: для нативных монет (ETH, BNB) пользователь должен иметь её на кошельке отдельно — UX-момент, который важно объяснить
Безопасность
- Мнемоника в Vault (HashiCorp Vault или AWS Secrets Manager), никогда в
.env - Rate limiting на endpoint генерации адресов
- Идемпотентность: одинаковый
orderIdвсегда возвращает тот же адрес - Audit log всех транзакций с подтверждениями
- Периодическая сверка: сумма подтверждённых платежей в БД должна сходиться с on-chain балансами







