Интеграция с Ethereum и BNB Smart Chain
В этом руководстве описано, как принимать некастодиальные криптоплатежи в Ethereum и BNB Smart Chain через платёжные смарт-контракты Volet.com и ethers v6.
Клиенты подключают свои кошельки, подтверждают транзакции и платят через смарт-контракт напрямую на ваш кошелёк для приёма средств. Средства не зачисляются на аккаунт Volet.com.
Прежде чем продолжить, ознакомьтесь с обзором платёжного сценария в статье Интеграция со смарт-контрактами.
Интеграция в общих чертах
- Подключите кошелёк клиента и проверьте выбранную блокчейн-сеть.
- Настройте платёжные контракты и свой кошелёк для приёма средств.
- Создайте заказ на бэкенде до начала оплаты.
- Вызовите
previewPayment(), чтобы рассчитать полную сумму платежа. - Вызовите
transfer(), чтобы инициировать платёж. Для USDT проверьте разре шение (allowance) на списание токенов и при необходимости запросите одобрение. - Сохраните хеш платёжной транзакции (
txid) в заказе. - Проверьте транзакцию на бэкенде, прежде чем помечать заказ как оплаченный.
Перед началом
Вам понадобятся:
- Кошелёк клиента: MetaMask или другой кошелёк с поддержкой EIP-1193 для платежей в браузере.
- Библиотека:
ethersверсии 6 для работы с кошельками и смарт-контрактами. - Кошелёк мерчанта: адрес кошелька в формате Ethereum под вашим контролем для приёма платежей.
- Бэкенд и база данных: чтобы создавать заказы, хранить данные транзакций и проверять платежи.
- RPC-провайдер: чтобы получать транзакции из блокчейна и проверять их выполнение.
Для подключения мобильных кошельков также понадобится Project ID Reown/WalletConnect.
Подд ерживаемые сети и активы
| Сеть | Chain ID | Поддерживаемые активы |
|---|---|---|
| Ethereum Mainnet | 1 | ETH, USDT (ERC-20) |
| BNB Smart Chain | 56 | BNB, USDT (BEP-20) |
Для каждого актива используется свой контракт-процессор. В примерах кода ниже указаны адреса процессоров и токенов из конфигурации интеграции.
Адреса контрактов, decimals токенов и параметры функций приведены в справочнике по смарт-контрактам.
Прежде чем использовать примеры в рабочей среде, сверьте адреса развёрнутых процессоров и интерфейс контракта с Volet.com.
Шаг 1. Подключите кошелёк клиента
Подключите браузерный кошелёк клиента с помощью ethers v6.
<script src="https://cdn.jsdelivr.net/npm/ethers@6/dist/ethers.umd.min.js"></script>
<button id="connect-wallet">Connect wallet</button>
<script>
let provider;
let signer;
document.getElementById('connect-wallet').addEventListener('click', async () => {
if (!window.ethereum) {
alert('Install a compatible wallet, such as MetaMask.');
return;
}
provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
signer = await provider.getSigner();
document.getElementById('connect-wallet').textContent =
await signer.getAddress();
});
</script>
Клиент должен подключиться к сети, выбранной на вашей странице оплаты:
- Ethereum Mainnet (
chainId: 1) - BNB Smart Chain (
chainId: 56)
Если кошелёк подключён к другой сети, запросите её переключение или попросите клиента переключить сеть вручную.
Всегда проверяйте активный chain ID непосредственно перед отправкой платежа: клиент может сменить сеть в кошельке.
Шаг 2. Настройте платёжные контракты
Добавьте в приложение ABI платёжного процессора и функции токена.
const PROCESSOR_ABI = [
'function previewPayment(uint256 amount, bool buyerPaysCommission) view returns (uint256 commission, uint256 totalPay)',
'function transfer(address seller, uint256 amount, bool buyerPaysCommission, address optionalCommissionWallet) payable',
];
const ERC20_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function allowance(address owner, address spender) view returns (uint256)',
'function approve(address spender, uint256 value) returns (bool)',
];
const PROCESSORS = {
native: '0x2fceE245109Cf3c535489a6F52fe11b3BBd644FF',
usdt: '0xe4083ED0A1101615b9092E8cD2164e2f42B4571E',
};
const USDT_BY_CHAIN = {
1: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
decimals: 6,
},
56: {
address: '0x55d398326f99059fF775485246999027B3197955',
decimals: 18,
},
};
У платёжного процессора две основные функции:
previewPayment(amount, buyerPaysCommission)рассчитывает комиссию за обработку и полную сумму, которую должен заплатить клиент.transfer(seller, amount, buyerPaysCommission, optionalCommissionWallet)выполняет платёж и распределяет средства по настройкам комиссии контракта.
Параметр amount указывается в минимальных единицах актива. Для ETH и BNB используйте ethers.parseEther(), для USDT — ethers.parseUnits() с правильным числом decimals токена.
Параметр buyerPaysCommission определяет, кто платит комиссию за обработку:
true: комиссия добавляется к платежу клиента.false: комиссия вычитается из суммы, которую получает мерчант.
Если партнёрского кошелька для комиссии нет, передайте ethers.ZeroAddress в optionalCommissionWallet.
Всегда берите полную сумму платежа из previewPayment(). Не рассчитывайте комиссию за обработку вручную.
Полное описание параметров — в справочнике по смарт-контрактам.
Шаг 3. Приём ETH или BNB
Для платежа в нативных ETH и BNB нужна одна транзакция в кошельке.
Одна и та же функция работает в обеих сетях, если клиент подключён к нужному блокчейну.
async function payNative(signer, seller, humanAmount, buyerPaysCommission = true) {
if (!ethers.isAddress(seller)) {
throw new Error('Invalid merchant wallet');
}
const network = await signer.provider.getNetwork();
if (![1n, 56n].includes(network.chainId)) {
throw new Error('Unsupported network');
}
const processor = new ethers.Contract(
PROCESSORS.native,
PROCESSOR_ABI,
signer
);
const amount = ethers.parseEther(humanAmount);
const [, totalPay] = await processor.previewPayment(
amount,
buyerPaysCommission
);
const tx = await processor.transfer(
seller,
amount,
buyerPaysCommission,
ethers.ZeroAddress,
{ value: totalPay }
);
return tx.hash;
}
value транзакции должен быть равен totalPay, который вернул previewPayment().
У клиента также должно быть достаточно ETH или BNB для оплаты комиссии сети.
Функция возвращает хеш платёжной транзакции (txid). Сохраните его в существующем заказе и проверьте платёж, прежде чем выполнять заказ.
Шаг 4. Приём USDT
Платежи в USDT проходят через контракт токена ERC-20 или BEP-20 и могут потребовать дополнительной транзакции одобрения.
Перед платежом приложение должно:
- Проверить баланс USDT клиента с помощью
balanceOf(). - Проверить разрешение процессора на списание с помощью
allowance(). - Если разрешения недостаточно, запросить одобрение через
approve(). - Вызвать
transfer(), чтобы выполнить платёж.
async function payUsdt(signer, seller, humanAmount, buyerPaysCommission = true) {
if (!ethers.isAddress(seller)) {
throw new Error('Invalid merchant wallet');
}
const chainId = Number((await signer.provider.getNetwork()).chainId);
const tokenConfig = USDT_BY_CHAIN[chainId];
if (!tokenConfig) {
throw new Error('Unsupported network');
}
const processor = new ethers.Contract(
PROCESSORS.usdt,
PROCESSOR_ABI,
signer
);
const token = new ethers.Contract(
tokenConfig.address,
ERC20_ABI,
signer
);
const amount = ethers.parseUnits(humanAmount, tokenConfig.decimals);
const [, totalPay] = await processor.previewPayment(
amount,
buyerPaysCommission
);
const buyer = await signer.getAddress();
const balance = await token.balanceOf(buyer);
if (balance < totalPay) {
throw new Error('Insufficient USDT');
}
const currentAllowance = await token.allowance(
buyer,
PROCESSORS.usdt
);
if (currentAllowance < totalPay) {
// Ethereum USDT may require resetting an existing allowance.
if (chainId === 1 && currentAllowance > 0n) {
const resetTx = await token.approve(PROCESSORS.usdt, 0n);
await resetTx.wait();
}
const approvalTx = await token.approve(
PROCESSORS.usdt,
totalPay
);
await approvalTx.wait();
}
const tx = await processor.transfer(
seller,
amount,
buyerPaysCommission,
ethers.ZeroAddress
);
return tx.hash;
}
Требования к одобрению USDT
В зависимости от текущего разрешения для платежа в USDT может понадобиться:
| Текущее разрешение | Нужные транзакции |
|---|---|
| Достаточное | Только платёж |
| Нулевое или недостаточное | Одобрение + платёж |
| Ненулевое разрешение для USDT в Ethereum | Сброс разрешения до нуля + одобрение + платёж |
USDT в Ethereum может отклонить прямое изменение ненулевого разрешения на другое ненулевое значение. Пример решает это так: при необходимости сначала сбрасывает разрешение.
Каждое одобрение должно быть подтверждено, прежде чем переходить к следующей транзакции.
Важно:
- Транзакция
approve()— это не платёж. - Клиенту нужно достаточно ETH или BNB для оплаты комиссии сети, даже если он платит в USDT.
- Используйте целочисленные суммы токенов, рассчитанные через
ethers.parseUnits(). Не используйте арифметику с плавающей точкой для финансовых сумм. - Отслеживайте транзакции одобрения отдельно от итоговой платёжной транзакции.
Шаг 5. Сохраните платёжную транзакцию
Ваш бэкенд должен создать заказ до того, как клиент отправит платёж.
В каждом заказе должны быть ожидаемые:
- Идентификатор заказа
- Блокчейн-сеть и chain ID
- Актив и сумма
- Кошелёк мерчанта для приёма средств (
seller) - Утверждённый контракт-процессор
- Кошелёк покупателя, если этого требует ваша страница оплаты
- Настройка комиссии за обработку (
buyerPaysCommission)
После того как клиент отправил платёж:
- Получите хеш платёжной транзакции (
txid), который вернул кошелёк. - Отправьте хеш на бэкенд.
- Сохраните его вместе с идентификатором заказа и chain ID.
- Запустите проверку платежа на сервере.
Каждая платёжная транзакция может быть связана не более чем с одним заказом. Обеспечьте это ограничением уникальности (chainId, txid) в базе данных.
Функция transfer() смарт-контракта не принимает идентификатор заказа. Связь между заказом и его платёж ной транзакцией должно поддерживать ваше приложение.
Если клиент повторяет платёж, сохраняйте попытки оплаты отдельно и не перезаписывайте записи о транзакциях, которые ещё могут быть в ожидании.
Шаг 6. Проверьте платёж
Проверка платежа обязательна перед завершением заказа.
Полученный хеш транзакции означает лишь, что транзакция отправлена. Он не доказывает, что мерчант получил правильный платёж.
Ваш бэкенд должен самостоятельно:
- Получить транзакцию и квитанцию о выполнении из блокчейна.
- Подтвердить успешное выполнение и требуемую финальность в блокчейне.
- Проверить, что транзакция прошла в ожидаемой сети и через ожидаемый платёжный процессор.
- Сверить актив, кошелёк мерчанта, сумму и вызов контракта с заказом.
- Подтвердить, что мерчант получил ожидаемые средства.
- Убедиться, что транзакция ещё не использовалась для другого заказа.
- Пометить заказ как оплаченный и выполнить его только один раз.
Одной успешной квитанции транзакции недостаточно. Транзакция может успешно выполниться, но заплатить не тому мерчанту или не ту сумму.
Реализация на бэкенде, жизненный цикл заказа, требования к финальности и чек-лист для запуска описаны в статье Проверка платежей и отслеживание заказов.
Дополнительно: поддержка мобильных кошельков
Чтобы поддержать мобильные кошельки через WalletConnect, подключите @walletconnect/ethereum-provider и настройте его с Project ID из Reown Cloud.
HTML-пример для EVM показывает интеграцию WalletConnect, включая выбор и переключение сети.
WalletConnect Project ID идентифицирует ваше приложение. Это не приватный ключ. Если есть такая возможность, ограничьте разрешённые домены или источники (origins) приложения.
Пример интеграции
Пример example-evm.html показывает:
- Подключение MetaMask и кошельков с поддержкой WalletConnect.
- Выбор Ethereum или BNB Smart Chain.
- Расчёт суммы платежа через
previewPayment(). - Отправку платежей в ETH, BNB и USDT.
- Вывод хешей транзакций и отслеживание подтверждений в блокчейне.
Пример можно открыть в браузере или скачать HTML-файл, чтобы изучить и доработать код:
- Открыть живой пример — открывает интерактивное демо. Оно работает в основной сети Ethereum и BNB Smart Chain, поэтому подтверждённый платёж переводит реальные средства.
- Скачать example-evm.html — скачивает HTML-файл, чтобы запустить его локально или адаптировать для вашего приложения.
Браузерные расширения кошельков, например MetaMask, могут некорректно работ ать со страницами, открытыми прямо с диска (file://). Вместо этого откройте скачанный файл через локальный веб-сервер. Например, запустите npx serve в папке с файлом и откройте адрес localhost, который он выведет.
Попробуйте пример
- Откройте живой пример или скачанный файл через локальный сервер в браузере с установленным MetaMask.
- Выберите Ethereum Mainnet или BNB Smart Chain и нажмите Connect Wallet (MetaMask). Для мобильных кошельков введите WalletConnect Project ID и выберите WalletConnect (QR).
- Выберите способ оплаты: ETH, BNB или USDT.
- Введите кошелёк для приёма средств в поле Seller address.
- Введите сумму платежа и выберите, кто платит комиссию за обработку.
- Нажмите Preview, чтобы увидеть комиссию за обработку и полную сумму платежа. Затем нажмите Pay и подтвердите транзакцию в кошельке. Для платежа в USDT сначала может понадобиться отдельное одобрение.
- Заказ и хеш транзакции появятся в таблице Orders. Демо отслеживает до 15 подтверждений в блокчейне.
- Используйте два разных кошелька. Адрес продавца должен отличаться от кошелька, с которого вы платите. Иначе контракт отклонит платёж с ошибкой
SellerIsBuyer. - Транзакции идут с реальными средствами. В примере заранее указано
0.001ETH или BNB. Перед подтверждением проверьте сумму, комиссию за обработку и комиссию сети. - Ограничения демо. Пример хранит историю транзакций в
localStorageбраузера и не является рабочей страницей оплаты. Отслеживание 15 подтверждений не обеспечивает финальность Ethereum и не заменяет проверку платежей на бэкенде. Прежде чем принимать заказы клиентов, реализуйте полный проце сс проверки платежей.
См. также
- Интеграция со смарт-контрактами — обзор некастодиального платёжного сценария
- Интеграция с TRON — приём TRX и USDT TRC-20
- Справочник по смарт-контрактам — адреса процессоров, decimals токенов и параметры контрактов
- Устранение неполадок — частые ошибки кошельков и транзакций