Інтеграція з Coinbase Commerce
Coinbase Commerce — non-custodial платіжний процесинг: кошти надходять прямо на ваш гаманець, мінуючи Coinbase. Підтримує BTC, ETH, USDC, DAI, LTC, BCH та ряд інших активів. Ключова відмінність від custodial-процессингів: Coinbase не тримає ваші гроші, немає KYC для отримувача, немає ризику заморозки коштів со сторони процесора.
Що інтегруємо
Два основних об'єкти API:
- Charge — одноразовий платіжний запит з фіксованою сумою, привязаний до конкретного замовлення
- Checkout — переисползуємая платіжна сторінка (підходить для донатів, де сума довільна)
Для e-commerce використовуємо Charges.
Створення Charge через API
const axios = require("axios");
async function createCharge(orderId, amountUSD, description) {
const response = await axios.post(
"https://api.commerce.coinbase.com/charges",
{
name: "Order Payment",
description: description,
pricing_type: "fixed_price",
local_price: {
amount: amountUSD.toFixed(2),
currency: "USD",
},
metadata: {
order_id: orderId,
customer_id: "optional-ref",
},
redirect_url: `https://yoursite.com/orders/${orderId}/success`,
cancel_url: `https://yoursite.com/orders/${orderId}/cancel`,
},
{
headers: {
"X-CC-Api-Key": process.env.COINBASE_COMMERCE_API_KEY,
"X-CC-Version": "2018-03-22",
},
}
);
return response.data.data; // містить hosted_url, code, addresses
}
hosted_url — готова сторінка Coinbase Commerce, куди редиректимо користувача. Coinbase показує адреси в різних мережах на вибір користувача, QR, countdown таймер (15 хвилин по умовчанню для крипто-курсу).
Обробка webhook
Найважливіша частина. Coinbase Commerce шле события при кожній зміні статусу:
const crypto = require("crypto");
app.post("/webhooks/coinbase", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-cc-webhook-signature"];
const webhookSecret = process.env.COINBASE_COMMERCE_WEBHOOK_SECRET;
// Верифікація підпису — HMAC-SHA256 від raw body
const expectedSig = crypto
.createHmac("sha256", webhookSecret)
.update(req.body)
.digest("hex");
if (signature !== expectedSig) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body);
switch (event.type) {
case "charge:confirmed":
// Достатньо для товарів з низьким ризиком
await orderService.markConfirmed(event.data.metadata.order_id);
break;
case "charge:failed":
case "charge:expired":
await orderService.markFailed(event.data.metadata.order_id);
break;
case "charge:resolved":
// Фінальний успішний статус після underpayment-resolve або delayed payment
await orderService.markResolved(event.data.metadata.order_id);
break;
}
res.json({ received: true });
});
Важливо: req.body повинен бути raw Buffer при верифікації підпису — не парсити через express.json() до верифікації, інакше підпис не совпадет.
Статусна машина Charge
NEW → PENDING (отримана перша трансакція) → CONFIRMED → RESOLVED
→ EXPIRED (істік таймер)
→ FAILED (underpayment, timeout)
→ UNRESOLVED (потребує ручного розбору)
CONFIRMED наступає після достатньої кількості confirmations (залежить від мережи: Bitcoin — 3, Ethereum — 12). Для більшості товарів достатньо CONFIRMED. RESOLVED — фінальний статус, означає повну обробку включаючи overpayment-повернення.
Polling як fallback
Webhook може не дійти — настройте періодичну сверку. Coinbase Commerce API дозволяє отримати статус Charge по його коду:
// Запускати раз в 5 хвилин для pending charges
async function syncPendingCharges() {
const pending = await db.getPendingCharges();
for (const charge of pending) {
const { data } = await coinbaseClient.get(`/charges/${charge.code}`);
const timeline = data.data.timeline;
const latestStatus = timeline[timeline.length - 1].status;
if (["CONFIRMED", "RESOLVED"].includes(latestStatus)) {
await orderService.markPaid(charge.orderId);
}
}
}
Що потрібно реалізувати
- Endpoint створення Charge і редирект на
hosted_url - Webhook handler з верифікацією HMAC
- Збереження
charge.codeв базі для reconciliation - Fallback polling для pending charges
- UI-сторінка очікування з polling статусу (GET
/charges/:codeкожні 10 сек)







