Настройка приема платежей в Bitcoin

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Настройка приема платежей в Bitcoin
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Настройка приёма платежей в Bitcoin

Самая частая ошибка при интеграции Bitcoin-платежей — использование единого адреса для всех пользователей и отслеживание сумм. Это не работает: два пользователя могут прислать одинаковую сумму в одну транзакцию, можно получить несколько неполных платежей (UTXO), которые вместе составляют нужную сумму, или пользователь пришлёт с биржи через батчинг. Правильный подход — генерация уникального адреса на каждый платёж.

Архитектура: HD-кошельки и деривация адресов

Стандарт BIP-32/BIP-44 позволяет из одного master seed генерировать бесконечное дерево адресов детерминированно. Для приёма платежей используется xpub (extended public key) — публичная часть, которую сервер хранит открыто и из которой генерирует адреса. Приватный ключ хранится отдельно (cold storage, hardware wallet) и нужен только для вывода средств.

Master Seed → xpub (m/44'/0'/0')
             ↓
  index=0: 1A1zP1... (payment #1)
  index=1: 1B2zP2... (payment #2)
  index=N: ...       (payment #N)

Путь деривации по BIP-44 для Bitcoin mainnet: m/44'/0'/account'/change/index. Для приёма — change=0, index инкрементируем.

Типы адресов

Тип Формат SegWit Рекомендуется
P2PKH (Legacy) 1... Нет Нет (высокие комиссии)
P2SH-P2WPKH (Wrapped SegWit) 3... Да Для совместимости
P2WPKH (Native SegWit) bc1q... Да Да
P2TR (Taproot) bc1p... Да Да (новые проекты)

Рекомендую Native SegWit (bc1q): комиссии на 30–40% ниже Legacy, поддерживается всеми современными кошельками и биржами. Taproot — если нужны Schnorr-подписи и скрипты в будущем.

Реализация: Node.js + bitcoinjs-lib

npm install bitcoinjs-lib bip32 bip39 tiny-secp256k1
import * as bitcoin from 'bitcoinjs-lib'
import { BIP32Factory } from 'bip32'
import * as ecc from 'tiny-secp256k1'

bitcoin.initEccLib(ecc)
const bip32 = BIP32Factory(ecc)

const NETWORK = bitcoin.networks.bitcoin // или networks.testnet

// Один раз: генерация xpub из seed (выполняется в cold storage)
// const seed = bip39.mnemonicToSeedSync(mnemonic)
// const root = bip32.fromSeed(seed, NETWORK)
// const account = root.derivePath("m/84'/0'/0'") // BIP-84 для Native SegWit
// const xpub = account.neutered().toBase58()
// console.log(xpub) // хранить в .env как BITCOIN_XPUB

// На сервере: генерация адреса по индексу
function getPaymentAddress(xpub: string, index: number): string {
  const node = bip32.fromBase58(xpub, NETWORK)
  const child = node.derive(0).derive(index) // external chain, index N
  const { address } = bitcoin.payments.p2wpkh({
    pubkey: Buffer.from(child.publicKey),
    network: NETWORK,
  })
  if (!address) throw new Error('Failed to derive address')
  return address
}

База данных: схема платежей

CREATE TABLE bitcoin_payments (
  id          BIGSERIAL PRIMARY KEY,
  order_id    UUID NOT NULL REFERENCES orders(id),
  address     VARCHAR(62) NOT NULL UNIQUE,
  hd_index    INTEGER NOT NULL UNIQUE,
  amount_sat  BIGINT NOT NULL,           -- сумма в сатоши
  status      VARCHAR(20) DEFAULT 'pending', -- pending/underpaid/confirmed/expired
  created_at  TIMESTAMPTZ DEFAULT NOW(),
  expires_at  TIMESTAMPTZ NOT NULL,
  confirmed_at TIMESTAMPTZ,
  tx_hash     VARCHAR(64)
);

CREATE INDEX ON bitcoin_payments(address);
CREATE INDEX ON bitcoin_payments(status) WHERE status = 'pending';

Мониторинг транзакций

Вариант 1: Electrum/Electrs через WebSocket

Самый дешёвый способ — поднять electrs (Rust реализация Electrum Server) поверх своей Bitcoin ноды и слушать события:

import ElectrumClient from 'electrum-client'

const client = new ElectrumClient(50002, 'your-electrs-host', 'tls')
await client.connect('payment-monitor', '1.4')

// Подписка на адрес
async function watchAddress(address: string, onPayment: (tx: any) => void) {
  const scriptHash = addressToScriptHash(address) // sha256 reversedLE
  await client.subscribe.on('blockchain.scripthash.subscribe', async (updates) => {
    const [scripthash, status] = updates
    if (scripthash === scriptHash && status !== null) {
      const history = await client.blockchainScripthash_getHistory(scriptHash)
      onPayment(history)
    }
  })
  await client.blockchainScripthash_subscribe(scriptHash)
}

Вариант 2: Polling через публичный API

Для MVP или низкой нагрузки — Blockstream Esplora API (публичный, без ключа):

async function checkPayment(address: string, expectedSat: bigint): Promise<'pending' | 'underpaid' | 'confirmed'> {
  const res = await fetch(`https://blockstream.info/api/address/${address}/utxo`)
  const utxos: Array<{ txid: string; value: number; status: { confirmed: boolean } }> = await res.json()

  const confirmedSat = utxos
    .filter(u => u.status.confirmed)
    .reduce((sum, u) => sum + BigInt(u.value), 0n)

  if (confirmedSat === 0n) return 'pending'
  if (confirmedSat < expectedSat) return 'underpaid'
  return 'confirmed'
}

Для production используйте собственную ноду + electrs. Зависимость от публичных API в платёжном сервисе — не ок.

Количество подтверждений

Сумма Рекомендуемые подтверждения
< $100 1 (первое включение в блок)
$100 – $1 000 3
$1 000 – $10 000 6
> $10 000 6+ или ждать Finalized по логике бизнеса

0-conf (unconfirmed) приемлем только для физических точек продаж с небольшими суммами и при RBF=false. В e-commerce — только с подтверждениями.

Обработка edge cases

Overpayment — пользователь прислал больше. Политика: зачислить как полную оплату, сохранить разницу на балансе, предложить возврат или зачёт на следующий заказ. Автовозврат — только если есть адрес для возврата от пользователя.

Underpayment — пришло меньше нужного. Два варианта: заморозить платёж и попросить доплатить на тот же адрес (в окне времени); или отменить и попросить новый платёж. Не зачислять частичную оплату как полную.

Expiry — адрес истёк (обычно через 15–60 минут), но транзакция всё же пришла. Стратегия: хранить "expired" адреса активными ещё 24 часа, но не показывать пользователю для новых платежей.

Transaction Replacement (RBF) — транзакция может быть заменена с более высокой комиссией. Не доверяйте unconfirmed транзакциям с BIP125-opt-in-RBF=true флагом.

Вывод средств

Для вывода накопленных UTXO нужна логика coin selection. Используйте bitcoinjs-lib PSBT:

// Нужен доступ к приватным ключам — выполнять только в изолированном сервисе
const psbt = new bitcoin.Psbt({ network: NETWORK })
// ... добавление inputs/outputs с правильным fee calculation
// Размер транзакции в vbytes зависит от количества inputs/outputs
// fee = feeRate (sat/vByte) * vsize

Рекомендую делать sweep периодически (раз в день) через скрипт, а не автоматически на каждый платёж — это сокращает количество транзакций и комиссии.