Перейти к основному содержимому

Интеграция с Ethereum и BNB Smart Chain

В этом руководстве описано, как принимать некастодиальные криптоплатежи в Ethereum и BNB Smart Chain через платёжные смарт-контракты Volet.com и ethers v6.

Клиенты подключают свои кошельки, подтверждают транзакции и платят через смарт-контракт напрямую на ваш кошелёк для приёма средств. Средства не зачисляются на аккаунт Volet.com.

Прежде чем продолжить, ознакомьтесь с обзором платёжного сценария в статье Интеграция со смарт-контрактами.

Интеграция в общих чертах​

  1. Подключите кошелёк клиента и проверьте выбранную блокчейн-сеть.
  2. Настройте платёжные контракты и свой кошелёк для приёма средств.
  3. Создайте заказ на бэкенде до начала оплаты.
  4. Вызовите previewPayment(), чтобы рассчитать полную сумму платежа.
  5. Вызовите transfer(), чтобы инициировать платёж. Для USDT проверьте разрешение (allowance) на списание токенов и при необходимости запросите одобрение.
  6. Сохраните хеш платёжной транзакции (txid) в заказе.
  7. Проверьте транзакцию на бэкенде, прежде чем помечать заказ как оплаченный.

Перед началом​

Вам понадобятся:

  • Кошелёк клиента: MetaMask или другой кошелёк с поддержкой EIP-1193 для платежей в браузере.
  • Библиотека: ethers версии 6 для работы с кошельками и смарт-контрактами.
  • Кошелёк мерчанта: адрес кошелька в формате Ethereum под вашим контролем для приёма платежей.
  • Бэкенд и база данных: чтобы создавать заказы, хранить данные транзакций и проверять платежи.
  • RPC-провайдер: чтобы получать транзакции из блокчейна и проверять их выполнение.

Для подключения мобильных кошельков также понадобится Project ID Reown/WalletConnect.

Поддерживаемые сети и активы​

СетьChain IDПоддерживаемые активы
Ethereum Mainnet1ETH, USDT (ERC-20)
BNB Smart Chain56BNB, 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 и могут потребовать дополнительной транзакции одобрения.

Перед платежом приложение должно:

  1. Проверить баланс USDT клиента с помощью balanceOf().
  2. Проверить разрешение процессора на списание с помощью allowance().
  3. Если разрешения недостаточно, запросить одобрение через approve().
  4. Вызвать 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)

После того как клиент отправил платёж:

  1. Получите хеш платёжной транзакции (txid), который вернул кошелёк.
  2. Отправьте хеш на бэкенд.
  3. Сохраните его вместе с идентификатором заказа и chain ID.
  4. Запустите проверку платежа на сервере.

Каждая платёжная транзакция может быть связана не более чем с одним заказом. Обеспечьте это ограничением уникальности (chainId, txid) в базе данных.

Функция transfer() смарт-контракта не принимает идентификатор заказа. Связь между заказом и его платёжной транзакцией должно поддерживать ваше приложение.

Если клиент повторяет платёж, сохраняйте попытки оплаты отдельно и не перезаписывайте записи о транзакциях, которые ещё могут быть в ожидании.

Шаг 6. Проверьте платёж​

Проверка платежа обязательна перед завершением заказа.

Полученный хеш транзакции означает лишь, что транзакция отправлена. Он не доказывает, что мерчант получил правильный платёж.

Ваш бэкенд должен самостоятельно:

  1. Получить транзакцию и квитанцию о выполнении из блокчейна.
  2. Подтвердить успешное выполнение и требуемую финальность в блокчейне.
  3. Проверить, что транзакция прошла в ожидаемой сети и через ожидаемый платёжный процессор.
  4. Сверить актив, кошелёк мерчанта, сумму и вызов контракта с заказом.
  5. Подтвердить, что мерчант получил ожидаемые средства.
  6. Убедиться, что транзакция ещё не использовалась для другого заказа.
  7. Пометить заказ как оплаченный и выполнить его только один раз.

Одной успешной квитанции транзакции недостаточно. Транзакция может успешно выполниться, но заплатить не тому мерчанту или не ту сумму.

Реализация на бэкенде, жизненный цикл заказа, требования к финальности и чек-лист для запуска описаны в статье Проверка платежей и отслеживание заказов.

Дополнительно: поддержка мобильных кошельков​

Чтобы поддержать мобильные кошельки через 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, который он выведет.

Попробуйте пример​

  1. Откройте живой пример или скачанный файл через локальный сервер в браузере с установленным MetaMask.
  2. Выберите Ethereum Mainnet или BNB Smart Chain и нажмите Connect Wallet (MetaMask). Для мобильных кошельков введите WalletConnect Project ID и выберите WalletConnect (QR).
  3. Выберите способ оплаты: ETH, BNB или USDT.
  4. Введите кошелёк для приёма средств в поле Seller address.
  5. Введите сумму платежа и выберите, кто платит комиссию за обработку.
  6. Нажмите Preview, чтобы увидеть комиссию за обработку и полную сумму платежа. Затем нажмите Pay и подтвердите транзакцию в кошельке. Для платежа в USDT сначала может понадобиться отдельное одобрение.
  7. Заказ и хеш транзакции появятся в таблице Orders. Демо отслеживает до 15 подтверждений в блокчейне.

Перед оплатой:

  • Используйте два разных кошелька. Адрес продавца должен отличаться от кошелька, с которого вы платите. Иначе контракт отклонит платёж с ошибкой SellerIsBuyer.
  • Транзакции идут с реальными средствами. В примере заранее указано 0.001 ETH или BNB. Перед подтверждением проверьте сумму, комиссию за обработку и комиссию сети.

Ограничения демо: пример хранит историю транзакций в localStorage браузера и не является рабочей страницей оплаты. Отслеживание 15 подтверждений не обеспечивает финальность Ethereum и не заменяет проверку платежей на бэкенде. Прежде чем принимать заказы клиентов, реализуйте полный процесс проверки платежей.