К содержимому

РазработчикамГайд

Как принимать криптовалюту в Node.js

Приём Bitcoin и USDT в приложении на Node.js: подпись запросов через node:crypto, создание платежа, переход на страницу оплаты и проверка вебхука в Express.

Приложение на Node.js принимает криптовалюту через CryptoLike в три шага: подписать каждый запрос к API через node:crypto, создать платёж по заказу и проверить подписанный вебхук, прежде чем отметить заказ оплаченным. Хватает Node.js 18 и новее — fetch и crypto встроены, так что интеграции не нужны зависимости, кроме вашего веб-фреймворка.

Для магазина на CMS писать код не нужно: смотрите плагин для WooCommerce и другие интеграции. Это руководство — для собственного бэкенда: SaaS, маркетплейса или приложения.

Что понадобится

Аккаунт CryptoLike, сервер с Node.js 18+ и публичный адрес https:// для вебхуков. Секреты держите в окружении: в примерах это CRYPTOLIKE_API_KEY_ID, CRYPTOLIKE_API_SECRET и CRYPTOLIKE_WEBHOOK_SECRET, а также CRYPTOLIKE_API_URL для тестового адреса API (без него — рабочий). Разрабатывайте и проверяйте сначала с тестовыми ключами в тестовых сетях.

1. Создайте тестовый API-ключ

В кабинете откройте Разработчикам → API-ключи → Создать ключ с правами create_invoice и read. Ключ ck_test_ работает с тестовыми сетями, где монеты ничего не стоят. Тестовые ключи выдаёт тестовая среда: поддержка CryptoLike даст доступ к её кабинету, где вы создадите такой же ключ, и адрес её API (CRYPTOLIKE_API_URL в примерах). Рабочий кабинет выпускает только боевые ключи, а в основных сетях тестовый ключ получает 401 auth.test_key. Секрет показывается один раз; храните его в менеджере секретов или окружении, но не во фронтенд-коде.

2. Подписывайте запросы через node:crypto

Подпись — это HMAC-SHA256 от четырёх строк: времени Unix в секундах, метода, пути с query и SHA-256 тела, с ключом — API-секретом. Этот помощник подписывает и отправляет запрос:

ts
import { createHash, createHmac } from "node:crypto";

export async function cryptolike(method: string, path: string, payload?: unknown, idempotencyKey?: string) {
  const body = payload === undefined ? "" : JSON.stringify(payload);
  const ts = Math.floor(Date.now() / 1000).toString();
  const canonical = [ts, method.toUpperCase(), path, createHash("sha256").update(body).digest("hex")].join("\n");
  const res = await fetch(`${process.env.CRYPTOLIKE_API_URL ?? "https://api.cryptolike.net"}${path}`, {
    method,
    headers: {
      "X-API-Key": process.env.CRYPTOLIKE_API_KEY_ID!,
      "X-Timestamp": ts,
      "X-Signature": createHmac("sha256", process.env.CRYPTOLIKE_API_SECRET!).update(canonical).digest("hex"),
      "Content-Type": "application/json",
      ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
    },
    body: body || undefined,
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.error?.code ?? res.status}: ${data.error?.message ?? ""}`);
  return data;
}

Сериализуйте тело один раз и отправляйте ту же строку: если хешировать одну строку, а отправить другую, получится 401 auth.invalid_signature. Сверьте помощник с эталонным вектором из раздела аутентификация API.

3. Создайте платёж по заказу

Создавайте платёж на сервере, когда покупатель оформляет заказ. Укажите цену в USD или EUR и дайте выбрать монету на странице или передайте coin_id из GET /v1/currencies. Без coin_id страница покажет монеты, доступные для инвойсов, с ориентировочной суммой ≈ у каждой; allowed_coin_ids (id с invoice_enabled: true из подписанного GET /v1/currencies) ограничивает список для этого заказа. Номер заказа в Idempotency-Key вернёт тот же платёж при повторе:

ts
const invoice = await cryptolike("POST", "/v1/invoices", {
  amount: "49.90",
  currency: "USD",
  order_id: order.id,
  idempotency_by_order_id: true,
  return_url: `https://example.com/orders/${order.id}`,
}, `order-${order.id}`);

await orders.saveInvoiceId(order.id, invoice.id);

Держите суммы строками на всём пути — "49.90", а не числом JavaScript.

4. Отправьте покупателя на страницу оплаты

Перенаправьте браузер на invoice.checkout_url, например res.redirect(303, invoice.checkout_url) в Express. На странице оплаты видны сумма, сеть, адрес с QR-кодом, оставшееся время и статус в реальном времени. Чтобы покупатель остался в вашем оформлении заказа, встройте страницу виджетом оплаты; браузер видит только ID инвойса.

5. Проверьте вебхук в Express

Добавьте вебхук-эндпоинт в разделе Разработчикам → Вебхуки с событиями invoice.*. Читайте тело «сырым» — express.json() пересериализует его и сломает подпись, — проверяйте, отсеивайте повторы по event_id и отвечайте быстро:

ts
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

app.post("/webhooks/cryptolike", express.raw({ type: "application/json" }), async (req, res) => {
  const ts = String(req.header("X-Timestamp") ?? "");
  const got = Buffer.from(String(req.header("X-Signature") ?? ""));
  const secret = process.env.CRYPTOLIKE_WEBHOOK_SECRET ?? "";
  const want = Buffer.from("sha256=" + createHmac("sha256", secret).update(`${ts}.`).update(req.body).digest("hex"));
  const fresh = /^\d+$/.test(ts) && Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!secret || !fresh || got.length !== want.length || !timingSafeEqual(got, want)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  if (await events.firstTime(event.event_id)) {
    if (event.type === "invoice.paid") await orders.markPaid(event.data.invoice.order_id);
  }
  res.sendStatus(200);
});

Доставка — «хотя бы один раз», и порядок может нарушаться: верьте статусу внутри data, а не порядку прихода. Полная процедура — в руководстве по проверке подписи вебхуков.

6. Проверьте в тестовой сети и запускайте

Создайте платёж тестовым ключом и оплатите его тестовыми монетами в той сети, что названа на странице. Придёт invoice.pending, затем invoice.paid. Обработайте invoice.underpaid (покупатель может доплатить на тот же адрес до срока), invoice.expired (предложите новый платёж) и invoice.reverted (приостановите выполнение заказа). Затем перейдите на рабочий ключ и пройдите чек-лист запуска из руководства разработчика.

Частые ошибки

Ошибка Что произошло Что сделать
401 auth.invalid_signature Подписанная строка не совпадает с запросом Хешируйте ровно отправляемую строку тела; время — в секундах, а не миллисекунды Date.now()
401 auth.replay Два одинаковых запроса в одну секунду Дождитесь следующей секунды и подпишите заново
401 на вашем вебхуке Тело разобрано до проверки Используйте express.raw() только на маршруте вебхука
409 conflict У заказа уже есть платёж на другую сумму Возьмите details.invoice_id; второй платёж не создавайте
422 no_allowed_coins Ни одна монета из allowed_coin_ids не доступна для инвойсов в этой валюте Передайте другие id или не передавайте поле, чтобы предложить все монеты
429 rate_limited Превышен лимит запросов ключа (по умолчанию 600 в минуту) Подождите Retry-After секунд

Удобнее с SDK?

Официальный TypeScript SDK для Node.js 18+ оборачивает эти вызовы: типизированные ресурсы, повторы при 429 и 5xx, Idempotency-Key на каждом POST и verifyWebhook(). У него нет зависимостей, и он отказывается работать в браузере — секрет не попадёт во фронтенд.

Вопросы и ответы

Можно ли создавать платежи CryptoLike из браузера?

Нет. API-секрет подписывает запросы от имени вашего аккаунта, поэтому он должен оставаться на сервере. Фронтенд просит бэкенд создать платёж и получает только ID инвойса или checkout_url. Поэтому официальный TypeScript SDK выдаёт ошибку, если обнаружит браузер, а виджет оплаты читает только публичные данные платежа.

Почему в Express не проходит проверка подписи вебхука?

Почти всегда потому, что express.json() разобрал тело до проверки: подпись считается по сырым байтам, а пересериализованный JSON отличается. Подключите express.raw({ type: "application/json" }) на маршруте вебхука, сначала проверяйте, потом разбирайте. Ещё проверьте, что секрет — от этого эндпоинта, а часы сервера синхронизированы.

Нужно ли опрашивать статус платежа?

Нет. Вебхуки сообщают о каждой смене статуса, подписаны и повторяются примерно 31 час, если сервер не ответил. Опрос GET /v1/invoices/{id} раз в несколько секунд тоже работает, например на странице, куда возвращается покупатель, но выполняйте заказ по проверенному вебхуку invoice.paid, а не по событию в браузере.

Термины на странице

Читать дальше

Начните с тестового платежа

Создайте аккаунт, попросите у поддержки доступ к тестовой среде и посмотрите, как платёж проходит путь от pending до paid в тестовой сети.