Интеграция с BitPay
BitPay — один из старейших криптоплатёжных процессоров, запущен в 2011 году. Сегодня поддерживает BTC, ETH, USDC, USDT, несколько сетей (Ethereum, Polygon, Arbitrum, Base), автоматическую конвертацию в фиат. Для бизнеса который хочет принимать крипту без собственной инфраструктуры и с готовой юридической документацией — разумный выбор.
API работает через счёт-фактуры (invoices): ваш backend создаёт инвойс на BitPay, получает URL для перенаправления пользователя, BitPay принимает оплату и уведомляет ваш webhook.
API аутентификация
BitPay использует нестандартную схему аутентификации на основе ECDSA-подписей. Клиент генерирует ECDSA keypair, публичный ключ регистрируется как «токен» на BitPay. Каждый запрос подписывается приватным ключом.
const BitPaySDK = require('bitpay-sdk');
const fs = require('fs');
// Генерация ключей и получение токена (один раз)
async function setupBitPay() {
const client = new BitPaySDK.Client(
null, // конфиг файл
BitPaySDK.Env.Prod, // или Env.Test для testnet
fs.readFileSync('./private.key', 'utf8') // ECDSA приватный ключ
);
// Токен из BitPay Dashboard → API Tokens
await client.authorizeClient('your-pairing-code');
return client;
}
Для большинства интеграций проще использовать официальный BitPay SDK (Node.js, PHP, Python, Ruby, Java) — он инкапсулирует подпись запросов.
Создание инвойса
const BitPaySDK = require('bitpay-sdk');
async function createInvoice(orderId, amount, currency = 'USD') {
const invoice = new BitPaySDK.Models.Invoice(amount, currency);
invoice.orderId = orderId;
invoice.notificationUrl = `https://yourapp.com/webhooks/bitpay`;
invoice.redirectUrl = `https://yourapp.com/orders/${orderId}/success`;
invoice.closeUrl = `https://yourapp.com/orders/${orderId}/cancel`;
// Метаданные для reconciliation
invoice.buyer = new BitPaySDK.Models.Buyer();
invoice.buyer.email = customerEmail;
// Опционально: принимать только конкретную монету
// invoice.paymentCurrencies = ['BTC', 'USDC'];
const created = await client.createInvoice(invoice);
return {
invoiceId: created.id,
paymentUrl: created.url, // редирект пользователя
expirationTime: created.expirationTime
};
}
Инвойс действителен 15 минут (по умолчанию) — пользователь должен оплатить в этот период. Сумма в USD фиксируется по курсу BitPay на момент создания инвойса.
Webhook обработка
BitPay отправляет IPN (Instant Payment Notification) на notificationUrl. Критично: проверять статус инвойса через API, а не просто доверять webhook-данным.
const express = require('express');
const router = express.Router();
router.post('/webhooks/bitpay', async (req, res) => {
const { id: invoiceId, status } = req.body.data || {};
if (!invoiceId) {
return res.status(400).json({ error: 'Missing invoice ID' });
}
// ВАЖНО: верифицируем через API, не доверяем телу webhook
const invoice = await client.getInvoice(invoiceId);
switch (invoice.status) {
case 'paid':
// Оплачен, но ждём подтверждений
await updateOrderStatus(invoice.orderId, 'paid_unconfirmed');
break;
case 'confirmed':
// Достаточно подтверждений (обычно 1 для большинства монет)
await updateOrderStatus(invoice.orderId, 'confirmed');
break;
case 'complete':
// Все подтверждения получены, средства зачислены
await fulfillOrder(invoice.orderId);
break;
case 'expired':
await updateOrderStatus(invoice.orderId, 'expired');
break;
case 'invalid':
// Underpayment или другая ошибка
await handleInvalidPayment(invoice.orderId, invoice);
break;
}
res.json({ success: true });
});
Цепочка статусов: new → paid → confirmed → complete. Для фулфилмента используйте confirmed или complete в зависимости от tolerance к риску. complete — самый безопасный, но задержка больше.
Возвраты (Refunds)
BitPay требует адрес для возврата — его нужно запросить у пользователя в момент оплаты или при инициации возврата.
async function createRefund(invoiceId, amount, currency) {
const refund = new BitPaySDK.Models.Refund();
refund.invoiceId = invoiceId;
refund.amount = amount;
refund.currency = currency; // валюта возврата
const created = await client.createRefund(refund);
// BitPay отправит email пользователю с запросом адреса
return created;
}
Типичные проблемы при интеграции
Webhook не приходит. BitPay требует HTTPS с валидным сертификатом на notificationUrl. Localhost недоступен — для разработки используйте ngrok или BitPay Testnet с публичным URL.
Дублирующиеся webhook-и. BitPay может отправить несколько уведомлений об одном статусе (retry при таймауте). Используйте invoiceId как idempotency key: INSERT ... ON CONFLICT (invoice_id, status) DO NOTHING.
Partial payment. Если пользователь оплатил меньше — статус invalid. BitPay автоматически возвращает underpayment при наличии email покупателя.
Timezone в expirationTime. Поле возвращается как Unix timestamp в миллисекундах. new Date(invoice.expirationTime) — не забудьте про мс, не секунды.
Тестирование
BitPay предоставляет Testnet окружение (BitPaySDK.Env.Test) с тестовым Bitcoin. Создаёте инвойс, платите с testnet кошелька — весь flow без реальных денег. Паринг-код для тестового окружения создаётся отдельно в dashboard.
Процесс интеграции
Регистрация → создание API токена → интеграция SDK в backend → webhook endpoint → тестирование на Testnet → production паринг.
Срок 2-3 дня: 1 день на SDK интеграцию и создание инвойсов, 1 день на webhook + статус-машину, 1 день на тестирование и edge cases (истёкший инвойс, partial payment, webhook retry).







