Разработка системы подтверждения платежей
Представьте: клиент оплатил заказ в USDT на Polygon, но через минуту сеть перестроилась — транзакция исчезла. Вы уже сформировали отгрузку, а деньги не пришли. Надёжная система подтверждения платежей — это не просто проверка хэша, а конечный автомат с явными переходами между состояниями и защитой ото всех граничных случаев. Наша реализация использует отдельные мониторы для каждой сети, tolerance-окно для обработки флуктуаций сумм и идемпотентность на уровне txHash. Например, на Ethereum PoS мы выставляем 12 подтверждений (в среднем 12 секунд на блок), что обеспечивает надёжность, сравнимую с банковским клирингом, но в 10 раз быстрее.
Мы пишем такие системы с нуля или встраиваем их в существующую инфраструктуру. Мы опираемся на спецификации EIP-1559 и Ethereum JSON-RPC API для корректной обработки транзакций. Снижение операционных издержек на обработку платежей может достигать $2,000 в месяц. Система окупается в среднем за 3–4 месяца. Под ключ за 2–4 недели. Свяжитесь, чтобы обсудить ваш сценарий.
Какую проблему решаем?
Наивная реализация: получили хэш -> проверили сумму -> засчитали. Ломается при первом же reorg, двойной трате или когда пользователь отправляет платёж спустя час после истечения сессии. Основные болевые точки:
- Reorg: блок отменяется, транзакция пропадает. Без отката статуса вы зачислите несуществующие средства.
- Floating point: конвертация через wei порождает погрешности, пользователь платит 47.50 USDT, а в системе — 47.499999.
- Комиссии обменников: сумма перевода меньше ожидаемой на 1–2%.
- Таймауты сессии: платёж пришёл после истечения лимита времени, а адрес уже недействителен.
Каждая из этих проблем решается в рамках единой конечной модели состояний.
Как система защищает от reorg?
Reorg — реорганизация цепи, когда принятый блок заменяется другим. На Ethereum PoS это маловероятно (глубина 1–2 блока), на Polygon — чаще. Наш поход: при каждой проверке подтверждений запрашивается свежий receipt транзакции. Если receipt пропал — статус откатывается на DETECTED, счётчик обнуляется, монитор начинает поиск заново.
async function processConfirmations(paymentId: string) { const payment = await db.findPayment(paymentId); const currentBlock = await provider.getBlockNumber(); const receipt = await provider.getTransactionReceipt(payment.txHash); if (!receipt) { await db.updatePayment(paymentId, { status: 'DETECTED', confirmations: 0, reorgDetected: true, }); return; } const confirmations = currentBlock - receipt.blockNumber + 1; const isConfirmed = confirmations >= payment.requiredConfirmations; await db.updatePayment(paymentId, { confirmations, status: isConfirmed ? 'CONFIRMED' : 'CONFIRMING', confirmedAt: isConfirmed ? new Date() : null, }); } Платежи в статусе CONFIRMING перепроверяются каждые N блоков — мы не доверяем старым данным.
Что делать, если пользователь прислал меньше или больше?
Из-за комиссий и floating point сумма в транзакции редко совпадает с ожидаемой. Разумное tolerance-окно решает эту проблему. Код проверки:
function isAmountSufficient( received: bigint, expected: bigint, toleranceBps: number = 50 ): 'exact' | 'underpaid' | 'overpaid' { const tolerance = expected * BigInt(toleranceBps) / 10000n; const min = expected - tolerance; const max = expected + expected / 10n; if (received >= min && received <= max) return 'exact'; if (received < min) return 'underpaid'; return 'overpaid'; } При underpaid система уведомляет оператора, при overpaid (до 10%) принимает платёж и зачисляет излишек на баланс пользователя или генерирует возврат.
Модель состояний платежа
Каждый платёж проходит строго определённые состояния: PENDING → DETECTED → CONFIRMING → CONFIRMED → SETTLED ↓ ↓ EXPIRED UNDERPAID / OVERPAID ↓ REFUNDED
| Состояние | Описание |
|---|---|
| PENDING | Адрес выдан, ждём транзакцию |
| DETECTED | Транзакция в mempool (0 подтверждений) |
| CONFIRMING | 1+ подтверждений, ещё не финально |
| CONFIRMED | Достигнут порог подтверждений, сумма корректна |
| SETTLED | Бизнес-логика выполнена (заказ создан, подписка активирована) |
| EXPIRED | Таймер вышел, транзакция не пришла |
| UNDERPAID | Транзакция есть, но сумма меньше ожидаемой |
Архитектура монитора блокчейна
Монолитный мониторинг всех сетей в одном процессе — плохая идея. Мы используем отдельный worker на каждую сеть с независимым retry-механизмом. Реализация для EVM-сетей:
Код базового монитора (EVM)
interface ChainMonitor { network: string; start(): Promise<void>; stop(): void; onTransaction(handler: (tx: IncomingTransaction) => Promise<void>): void; } class EvmChainMonitor implements ChainMonitor { private provider: ethers.JsonRpcProvider; private watchedAddresses = new Set<string>(); async start() { const activePayments = await db.query( "SELECT address FROM payments WHERE status IN ('PENDING', 'DETECTING', 'CONFIRMING')" ); activePayments.rows.forEach(p => this.watchedAddresses.add(p.address)); this.provider.on('block', async (blockNumber) => { await this.processBlock(blockNumber); }); } private async processBlock(blockNumber: number) { const block = await this.provider.getBlock(blockNumber, true); for (const tx of block.transactions) { if (tx.to && this.watchedAddresses.has(tx.to.toLowerCase())) { await this.handleNativeTransfer(tx, blockNumber); } } await this.scanErc20Transfers(blockNumber); } } Требования к подтверждениям для разных сетей
| Сеть | Рекомендуемое число подтверждений | Среднее время блока |
|---|---|---|
| Ethereum (L1) | 12 | ~12 с |
| Polygon (PoS) | 64 | ~60 с |
| BNB Chain | 15 | ~3 с |
| Arbitrum | 12 | ~0,5 с |
| Base | 12 | ~2 с |
Идемпотентность и защита от дублей
Один txHash должен засчитываться ровно один раз. Используем INSERT с ON CONFLICT DO NOTHING: если такой хэш уже обработан, возвращается пустой результат.
INSERT INTO payment_transactions (payment_id, tx_hash, amount, block_number) VALUES ($1, $2, $3, $4) ON CONFLICT (tx_hash) DO NOTHING RETURNING id; Уведомления и webhook'и
После перехода в CONFIRMED — немедленное уведомление во внешние системы через очередь (Bull/BullMQ) с exponential backoff. Прямой HTTP-вызов в обработчике блока — потеря событий при сбоях.
async function dispatchPaymentConfirmed(payment: Payment) { await eventBus.emit('payment.confirmed', { paymentId: payment.id, orderId: payment.orderId, amount: payment.receivedAmount, txHash: payment.txHash, }); if (payment.webhookUrl) { await webhookQueue.add('payment-webhook', { url: payment.webhookUrl, payload: { event: 'payment.confirmed', data: payment }, }, { attempts: 5, backoff: { type: 'exponential', delay: 2000 }, }); } } Процесс работы и что входит
- Аналитика — разбираем ваши бизнес-требования, количество сетей, токенов, сценарии возвратов.
- Проектирование конечного автомата — уточняем переходы, tolerance, пороги подтверждений.
- Реализация — пишем код мониторов, обработчиков, webhook'ов, интеграционных тестов.
- Тестирование — покрываем граничные случаи: reorg, underpaid, timeout, double-spend.
- Деплой и мониторинг — развёртываем в вашей инфраструктуре, настраиваем алерты.
Отметим: что входит в результат:
- Исходный код репозитория с инструкцией по запуску.
- Документация API и архитектуры.
- Миграции базы данных.
- Нагрузочные тесты и скрипты симуляции.
- Поддержка в течение 2 недель после запуска (по желанию — расширенная).
Закажите разработку системы под ваш проект — мы подготовим детальную смету за 1 день.
Сроки и гарантии
Типовые сроки — от 2 до 4 недель в зависимости от числа сетей и сложности бизнес-логики. Стоимость рассчитывается индивидуально, но мы гарантируем прозрачное ценообразование. Мы работаем с блокчейн-проектами более 5 лет и реализовали десятки подобных систем. Гарантируем стабильную работу под нагрузкой до 10 000 транзакций в час.
Получите консультацию: напишите нам, и мы оценим ваш проект бесплатно.







