Реализация приёма криптоплатежей на сайте
Приём криптоплатежей без посредников — это прямая работа с блокчейном: генерация адресов, мониторинг транзакций, подтверждение блоков. С посредниками (NOWPayments, CoinGate, BitPay) — API похож на классический платёжный шлюз, но с особенностями волатильности и необратимости. Оба подхода имеют смысл в разных ситуациях.
Реализация с посредником занимает 2–3 рабочих дня. Самостоятельный приём на одну сеть — 5–8 дней.
Через платёжный шлюз (рекомендовано для большинства)
NOWPayments, CoinGate, BitPay берут комиссию 0.5–1% и решают за вас: конвертацию, мониторинг сети, подтверждения, курсовые риски (если включена немедленная конвертация). Интеграция аналогична Stripe.
Подробнее: Интеграция CoinGate/NOWPayments/BitPay.
Прямой приём USDT/USDC (TRC-20, ERC-20)
Без посредников, но с курсовым риском только при работе со стейблкоинами. Для каждого заказа генерируется уникальный адрес:
use kornrunner\Ethereum\Ethereum;
class WalletService
{
private string $masterPrivateKey;
public function generateDepositAddress(int $orderId): string
{
// HD Wallet: мастер-ключ + путь m/44'/195'/0'/0/{orderId}
$derivedKey = $this->deriveKey($this->masterPrivateKey, $orderId);
$eth = new Ethereum();
return $eth->getAddress($derivedKey->getPublicKey());
}
}
Для TRC-20 (Tron) используется другая деривация, но концепция та же — HD Wallet с deterministic адресами. Зная мастер-ключ и index, всегда можно восстановить адрес и проверить баланс.
Мониторинг поступления средств
Blockchain-события не приходят в webhook — нужно опрашивать сеть. Для Ethereum/ERC-20:
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL); // Alchemy, Infura
// Подписаться на Transfer события ERC-20 контракта
const usdcContract = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, provider);
usdcContract.on('Transfer', async (from, to, amount, event) => {
const pendingOrder = await Order.findByDepositAddress(to);
if (!pendingOrder) return;
const decimals = 6; // USDC
const receivedUSDC = Number(ethers.formatUnits(amount, decimals));
if (receivedUSDC >= pendingOrder.amount_usd * 0.99) { // допуск 1% на комиссии
await confirmOrderPayment(pendingOrder, event.transactionHash, event.blockNumber);
}
});
Ждать не менее 6 подтверждений (блоков) для Ethereum, 20+ для Bitcoin, 19+ для Tron перед финальным подтверждением заказа.
Ожидание подтверждений
async function waitForConfirmations(
txHash: string,
requiredConfirmations: number = 6
): Promise<boolean> {
const tx = await provider.getTransaction(txHash);
if (!tx) return false;
const receipt = await provider.waitForTransaction(txHash, requiredConfirmations);
return receipt !== null && receipt.status === 1;
}
Хранение информации о платеже
CREATE TABLE crypto_payments (
id bigserial PRIMARY KEY,
order_id bigint NOT NULL REFERENCES orders(id),
network varchar(20) NOT NULL, -- 'eth', 'tron', 'btc'
token varchar(20) NOT NULL, -- 'USDT', 'USDC', 'BTC'
deposit_address varchar(100) NOT NULL UNIQUE,
expected_amount numeric(18,8) NOT NULL,
received_amount numeric(18,8),
tx_hash varchar(100),
confirmations int DEFAULT 0,
status varchar(20) NOT NULL DEFAULT 'awaiting',
expires_at timestamptz NOT NULL, -- обычно 30–60 минут
created_at timestamptz NOT NULL DEFAULT now()
);
Ограничение времени на оплату (expires_at) — обязательно. Курс зафиксирован на момент создания заказа. Если клиент не оплатил за 30 минут — заказ отменяется, адрес может быть переиспользован (хотя лучше не переиспользовать).
Курс и конвертация
При создании заказа фиксируется обменный курс из внешнего источника (CoinGecko, Binance API). Сумма в крипте рассчитывается из суммы в фиатной валюте:
$btcRate = $this->priceService->getRate('BTC', 'USD'); // например, 65000
$btcAmount = round($order->total_usd / $btcRate, 8);
// Отображать покупателю: заплатить 0.00023076 BTC
Допуск ±1–2% на комиссии сети и временной разрыв — стандартная практика.
Возвраты
Возврат криптоплатежа — ручной процесс. Автоматика невозможна без хранения приватных ключей в горячем кошельке, что создаёт риски безопасности. Стандартная практика: менеджер вручную отправляет транзакцию на кошелёк клиента. Адрес кошелька нужно запрашивать у клиента при инициации возврата.
Безопасность
Приватные ключи хранятся в HSM или HashiCorp Vault, не в БД и не в .env. Deposit-адреса — производные от мастер-ключа, не самостоятельные ключи. Доступ к мастер-ключу — только из серверного процесса мониторинга, не из веб-приложения.







