Разработка системы возврата криптоплатежей
Возврат в крипте — нетривиальная задача, и не потому что технически сложно отправить транзакцию обратно. Проблема в том, что курс мог измениться, исходный адрес мог быть биржевым депозитом (т.е. у пользователя нет к нему доступа), а в некоторых юрисдикциях возврат криптой создаёт налоговые события. Без продуманной архитектуры система либо теряет деньги на курсовой разнице, либо возвраты зависают навсегда.
Фундаментальная проблема: адрес отправителя ≠ адрес получателя
На Ethereum tx.origin и msg.sender — это адрес, с которого ушла транзакция. Но если пользователь отправлял с Binance, Coinbase или любой биржи — этот адрес принадлежит бирже, не пользователю. Возврат на биржевой депозитный адрес:
- Лучший случай: биржа зачислит средства пользователю если memo/tag совпадает
- Реальный случай: биржа зачислит на собственный кошелёк, пользователь откроет dispute месяцами
- Худший случай: транзакция будет отклонена (особенно для токенов), средства потеряны
Поэтому возврат "по адресу отправителя" — это не решение. Правильное решение — собирать адрес для возврата явно, на этапе оплаты или создания заявки на возврат.
Архитектура системы
Смарт-контрактный вариант (EVM сети)
Для on-chain логики — escrow контракт с состояниями:
enum PaymentStatus { Pending, Confirmed, Refunded, Disputed }
struct Payment {
address payer;
address refundAddress; // явно указанный адрес возврата
uint256 amount;
uint256 confirmedAt;
PaymentStatus status;
uint256 refundDeadline; // до какого момента возврат возможен
}
Функция возврата с защитами:
function refund(bytes32 paymentId) external onlyOperator {
Payment storage p = payments[paymentId];
require(p.status == PaymentStatus.Confirmed, "Not refundable");
require(block.timestamp <= p.refundDeadline, "Deadline passed");
p.status = PaymentStatus.Refunded;
// CEI паттерн: статус изменён до отправки
(bool success, ) = p.refundAddress.call{value: p.amount}("");
require(success, "Transfer failed");
emit PaymentRefunded(paymentId, p.refundAddress, p.amount);
}
Для ERC-20 токенов — SafeERC20.safeTransfer. USDT на Ethereum с нестандартным интерфейсом — отдельная обработка.
Off-chain вариант (Bitcoin, Dogecoin, UTXO сети)
Здесь смарт-контрактов нет. Логика полностью в бэкенде:
- При создании заказа — собираем
refund_addressу пользователя - Храним историю: какая транзакция, сколько, с какого/на какой адрес
- При возврате — строим UTXO транзакцию из sweep-кошелька на
refund_address - Сумма возврата: оригинальная сумма минус комиссия сети (рассчитывается в момент возврата)
Курсовая разница: политика и реализация
Это бизнес-решение, но оно влияет на архитектуру:
| Политика | Реализация | Риск |
|---|---|---|
| Возврат в той же криптовалюте | Просто, честно | Курс вырос — пользователь получает меньше в фиате |
| Возврат в USD-эквиваленте на момент оплаты | Нужен stablecoin или конвертация | Курс упал — вы доплачиваете разницу |
| Возврат по текущему курсу | Просто | Курс упал — пользователь теряет |
Самый распространённый подход для e-commerce: возврат в той же криптовалюте, сумма = оригинальная минус процент обработки. Политика прописывается в ToS и явно показывается пользователю.
Заявки на возврат: пользовательский флоу
Пользователь → Создаёт заявку → Указывает refund_address →
Оператор проверяет (или автоматически) → Транзакция возврата →
Пользователь получает txHash для верификации
Автоматические возвраты — для сумм ниже порога (например, < $200), если бизнес-логика однозначна (отменён заказ до отправки). Транзакция инициируется без участия оператора.
Ручная проверка — для крупных сумм, спорных ситуаций, когда refund_address выглядит подозрительно (тот же адрес что принимает платежи — red flag для фрода).
Мультивалютность
Если система принимает несколько валют — возврат в той же валюте требует хранить достаточный баланс каждой. Альтернатива — конвертация через DEX (Uniswap, 1inch) с slippage tolerance, но тогда точная сумма возврата неизвестна заранее.
Для DEX-конвертации нужна дополнительная логика: pre-quote, проверка liquidity, защита от MEV (deadline + минимальный output). Это отдельная задача с отдельными рисками.
Что реализуется за 3–5 дней
Escrow смарт-контракт или off-chain логика для вашей валюты, база данных платёжных записей с историей возвратов, сбор refund_address от пользователей, автоматические и ручные сценарии возврата, admin интерфейс для операторов, уведомления пользователям с txHash, базовое логирование и аудит-трейл.







