Налаштування прийому платежів у Bitcoin

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Налаштування прийому платежів у Bitcoin
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Налаштування прийому платежів у Bitcoin

Найчастіша помилка при інтеграції Bitcoin-платежів — використання єдиної адреси для всіх користувачів та відстеження сум. Це не працює: два користувачі можуть надіслати однакову суму в одній транзакції, ви можете отримати кілька неповних платежів (UTXO), які разом становлять потрібну суму, або користувач відправить з біржі через batching. Правильний підхід — генерація унікальної адреси на кожен платіж.

Архітектура: 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... (платіж #1)
  index=1: 1B2zP2... (платіж #2)
  index=N: ...       (платіж #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 потрібна логіка вибору монет. Використовуйте bitcoinjs-lib PSBT:

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

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