Інтеграція платежів Lightning Network на сайт
Lightning Network вирішує реальну проблему Bitcoin як платіжного засобу: on-chain транзакції дорогі ($1–20 комісія при нагрузці) та повільні (10–60 хвилин до достатнього числа підтверджень). LN дає мгновенні мікроплатежі за долі центу. Для e-commerce це практично застосовно — якщо правильно інтегрувати.
Ключове рішення, яке потрібно прийняти одразу: управляти власною LN-нодою або використовувати custody сервіс. Власна нода — повний контроль, немає комісій посередника, але потрібно управляти каналами та ліквідністю. Custody (Strike, OpenNode, BTCPay Cloud) — простіше, але доверяєте третій стороні.
Архітектура: власна LN-нода
Для серйозного e-commerce — LND (Lightning Network Daemon) або Core Lightning (CLN). LND більш поширений, краще документований, активно підтримується Lightning Labs.
Інфраструктура:
- Bitcoin full node (Bitcoin Core) — обов'язково, LND вимагає синхронізованої ноди
- LND нода підключена до Bitcoin Core через ZMQ
- PostgreSQL або bbolt для LND стану (bbolt за замовчуванням, PostgreSQL для high-availability)
# Мінімальна lnd.conf для production
[Application Options]
alias=MyShop-LN
color=#FF6600
maxpendingchannels=5
[Bitcoin]
bitcoin.active=1
bitcoin.mainnet=1
bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpchost=localhost
bitcoind.rpcuser=bitcoinrpc
bitcoind.rpcpass=strongpassword
bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
[protocol]
protocol.wumbo-channels=true # канали > 0.16 BTC
Управління каналами та ліквідністю
Це головна операційна складність. Платіж проходить тільки якщо є маршрут з достатньою ліквідністю. Для отримання платежів потрібна inbound ліквідність (входяща). Для відправки — outbound.
Клієнт → [Lightning Network маршрут] → Ваша нода
↓
Отримати платіж — потрібна inbound ліквідність
Стратегії для отримання inbound ліквідності:
- Купити inbound через Lightning Pool (маркетплейс ліквідності від Lightning Labs)
- Відкрити канали з добре пов'язаними нодами (ACINQ, Kraken, Bitfinex) — вони відкриють зворотний канал
- Submarine swaps через Loop In (Lightning Labs Loop): відправляєте on-chain BTC, отримуєте inbound ліквідність
// Lightning Labs Loop In для поповнення inbound ліквідності
const loopdClient = new LoopClient(LOOPD_HOST)
async function increaseInboundLiquidity(amountSats: number): Promise<void> {
const quote = await loopdClient.loopInQuote({ amt: amountSats })
console.log(`Loop In quote: ${quote.swap_fee_sat} sats fee, ${quote.miner_fee_sat} sats miner fee`)
await loopdClient.loopIn({
amt: amountSats,
max_swap_routing_fee: quote.swap_fee_sat,
max_miner_fee: quote.miner_fee_sat,
external_htlc: false,
})
}
Створення інвойсів та обробка платежів
LND надає gRPC API. Для Node.js — @lightningpolar/lnd або прямо @grpc/grpc-js:
import { createLnRpc, createInvoicesRpc } from '@lightningpolar/lnd'
const lnRpc = await createLnRpc({
server: 'localhost:10009',
tls: fs.readFileSync('/home/bitcoin/.lnd/tls.cert'),
macaroon: fs.readFileSync('/home/bitcoin/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon'),
})
interface CreateInvoiceResult {
paymentRequest: string // bolt11 інвойс
paymentHash: string // для відслідковування статусу
expiresAt: Date
}
async function createInvoice(
amountSats: number,
description: string,
orderId: string,
expirySeconds = 3600
): Promise<CreateInvoiceResult> {
const invoice = await lnRpc.addInvoice({
value: amountSats,
memo: description,
expiry: expirySeconds,
r_preimage: generatePreimage(),
})
// Зберігаємо зв'язок payment_hash → order_id
await db('ln_invoices').insert({
order_id: orderId,
payment_hash: Buffer.from(invoice.r_hash).toString('hex'),
payment_request: invoice.payment_request,
amount_sats: amountSats,
expires_at: new Date(Date.now() + expirySeconds * 1000),
status: 'pending',
})
return {
paymentRequest: invoice.payment_request,
paymentHash: Buffer.from(invoice.r_hash).toString('hex'),
expiresAt: new Date(Date.now() + expirySeconds * 1000),
}
}
Відслідковування оплати: Subscribe Invoice Stream
Не потрібно поллити статус — LND надає streaming RPC:
async function watchInvoicePayment(paymentHash: string): Promise<void> {
const stream = lnRpc.subscribeInvoices({})
stream.on('data', async (invoice) => {
const hash = Buffer.from(invoice.r_hash).toString('hex')
if (hash !== paymentHash) return
if (invoice.state === 'SETTLED') {
await db('ln_invoices')
.where({ payment_hash: paymentHash })
.update({ status: 'paid', paid_at: new Date(), amount_paid_sats: invoice.amt_paid_sat })
const order = await db('ln_invoices')
.where({ payment_hash: paymentHash })
.select('order_id')
.first()
await fulfillOrder(order.order_id)
stream.destroy()
}
if (invoice.state === 'CANCELED') {
await db('ln_invoices')
.where({ payment_hash: paymentHash })
.update({ status: 'expired' })
stream.destroy()
}
})
}
Custody альтернативи: OpenNode та BTCPay Server
Якщо управління нодою та ліквідністю не входить у ваші плани — два шляхи:
OpenNode — custody LN платіжний провайдер. REST API, webhook на оплату, автоматичний settle у BTC або fiat. Бере 1% комісії.
async function createOpenNodeCharge(
amountUsd: number,
orderId: string,
callbackUrl: string
): Promise<string> {
const res = await fetch('https://api.opennode.com/v1/charges', {
method: 'POST',
headers: {
Authorization: process.env.OPENNODE_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: amountUsd,
currency: 'USD',
order_id: orderId,
callback_url: callbackUrl,
success_url: `${BASE_URL}/order/${orderId}/success`,
}),
})
const charge = await res.json()
return charge.data.lightning_invoice.payreq // bolt11
}
BTCPay Server — self-hosted, відкритий код. Управляє LND нодою за вас, надає Greenfield API, немає комісії посередника. Розгортається через Docker за 15 хвилин, потребує VPS з 2GB+ RAM.
UX: QR-код та WebSocket оновлення
На сторінці оплати: QR-код з lightning:BOLT11_INVOICE, таймер istechennya, WebSocket з'єднання для мгновенного оновлення статусу без перезагрузки.
// WebSocket endpoint для статусу платежу
wss.on('connection', (ws, req) => {
const paymentHash = new URLSearchParams(req.url?.split('?')[1]).get('hash')
const unsubscribe = subscribeToPaymentStatus(paymentHash, (status) => {
ws.send(JSON.stringify({ status }))
if (status === 'paid') {
ws.close()
unsubscribe()
}
})
ws.on('close', unsubscribe)
})
Повна інтеграція зі своєю LND нодою: 1 тиждень (настройка ноди, управління каналами, фронтенд). З BTCPay Server: 2-3 дні. З OpenNode API: 1-2 дні.







