Приём криптоплатежей

Как принимать криптоплатежи на сайте

Пошагово: создать платёж с сервера, отправить клиента на страницу оплаты, обработать webhook и проверить. С рабочим кодом на Node.js.

Чтобы принимать крипту на сайте, нужны две вещи на вашем сервере: запрос, который создаёт платёж под заказ, и точка, которая принимает результат. В материале проходим обе с рабочим кодом, а затем разбираем тестирование, нестандартные случаи и ошибки, которые стоят мерчантам денег.

Что нужно до начала

Запускать ноду блокчейна и хранить кошельки клиентов не нужно. Нужно:

  • аккаунт в шлюзе с проектом, API-ключом и, если вы хотите сами задать способ оплаты, идентификатором этого способа из проекта;
  • публичная HTTPS-точка на вашем сервере для webhook, сохранённая в проекте как callback URL;
  • место в системе заказов, где хранится состояние платежа, сопоставленное по вашему номеру заказа.

Заранее решите одно: выбирает ли клиент актив и сеть на странице оплаты или вы задаёте их заранее. Выбор клиентом проще и подходит большинству магазинов. Если вы передаёте способ оплаты, страница откроется с уже выбранным.

Как всё складывается в один процесс

Путь одного заказа от вашей кассы до статуса «оплачен»:

  1. Клиент выбирает на кассе «Оплатить криптой», и ваш сервер создаёт платёж под этот заказ.
  2. Шлюз возвращает ссылку на свою страницу оплаты.
  3. Вы перенаправляете клиента на неё. Он выбирает актив и сеть и отправляет точную сумму со своего кошелька.
  4. Шлюз следит за блокчейном и, когда платёж финальный, вызывает ваш webhook.
  5. Webhook помечает заказ оплаченным и запускает выдачу.

Обратите внимание, чего на вашей стороне не происходит: вы не создаёте адреса, не сканируете блоки и не сравниваете суммы. Ваш код делает две небольшие задачи: создаёт платёж и реагирует на финальный статус.

Шаг 1. Создайте платёж с вашего сервера

Вызывайте шлюз из бэкенда, а не из браузера: запрос содержит ваш секретный API-ключ. В Qvard это один POST на https://api.qvard.com/api/v1/payment с ключом в заголовке x-api-key и JSON-телом:

  • id: ваш номер заказа в виде строки. Он вернётся вам позже в webhook, поэтому берите то, что можно найти у себя.
  • price: сумма в долларах США. Клиент платит эквивалент в выбранном активе.
  • paymentSystemId (необязательно): идентификатор способа оплаты из вашего проекта, чтобы заранее выбрать актив и сеть.

В случае успеха ответ 201 с идентификатором платежа id и redirectUrl. Повторный запрос с тем же id и тем же способом оплаты возвращает уже созданный платёж, а не создаёт дубль, поэтому после таймаута его безопасно повторить.

Если запрос неверный, придёт 400 с текстом Error validation и сообщением по каждому полю; сумма ниже минимальной для выбранного способа отклоняется так же. Отсутствующий или неверный ключ даёт 401.

Node.jscreate-payment.js
// Called from your checkout handler when the customer chooses "Pay with crypto".
async function createQvardPayment(order) {
  const res = await fetch('https://api.qvard.com/api/v1/payment', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.QVARD_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      id: String(order.id),        // your order number, also used to match the webhook
      price: order.totalUsd        // amount in US dollars
    })
  });

  if (!res.ok) throw new Error('Qvard ' + res.status + ' ' + (await res.text()));

  const { redirectUrl } = await res.json();
  return redirectUrl;              // send the customer to this page
}

Шаг 2. Отправьте клиента на страницу оплаты

Перенаправьте клиента на redirectUrl, который выглядит как https://my.qvard.com/pay/<идентификатор платежа>. На этой странице он выбирает актив и сеть, видит точную сумму и адрес и платит со своего кошелька. Курс закреплён на один час, поэтому сумма не меняется, пока клиент платит.

О чём стоит позаботиться:

  • Возврат клиента. Возврат по ссылке после оплаты пока недоступен, поэтому клиент возвращается на ваш сайт сам. Сделайте страницу заказа с текущим статусом, которая обновляется, когда приходит ваш webhook.
  • Не считайте возврат оплатой. Клиент, вернувшийся на сайт, не обязательно заплатил. Деньги пришли, только когда это сообщил webhook.

Шаг 3. Примите webhook

Когда платёж завершён, Qvard отправляет POST на callback URL вашего проекта с небольшим JSON: id, который вы передали, и status платежа. В заголовке x-api-key придёт ваш API-ключ. Это заголовок аутентификации, а не криптографическая подпись: сравните его с вашим ключом и отклоняйте всё остальное.

От того, как устроена доставка, зависит ваш обработчик:

  • Qvard считает webhook доставленным, только когда ваша точка отвечает ровно HTTP 200. Любой другой статус или таймаут считается неудачей.
  • Неудачная доставка повторяется: всего пять попыток примерно за шесть минут.
  • Редиректы не выполняются, поэтому ответ 3xx тоже неудача.
  • Уведомление по платежу можно отправить повторно из кабинета.

Из-за повторов обработчик должен быть идемпотентным: повторное событие не должно отправить заказ дважды. Пример ниже проверяет ключ, находит заказ по вашему id и помечает оплаченным, только если он ещё не оплачен.

Node.jswebhook.js
import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/webhooks/qvard', express.json(), async (req, res) => {
  // Qvard sends your API key in the x-api-key header: compare it with yours.
  const received = Buffer.from(String(req.get('x-api-key') || ''));
  const expected = Buffer.from(process.env.QVARD_API_KEY);
  const valid = received.length === expected.length && crypto.timingSafeEqual(received, expected);
  if (!valid) return res.sendStatus(401);

  const { id, status } = req.body;          // id is the order number you sent
  const order = await db.orders.findById(id);
  if (!order) return res.sendStatus(200);   // unknown order: nothing to retry

  if (status === 'completed' && order.state !== 'paid') {
    await markOrderPaid(order);             // safe to run twice: the check above skips repeats
  }

  return res.sendStatus(200);               // must be exactly 200, otherwise Qvard retries
});

Обработайте нестандартные платежи

У платежа один из четырёх статусов:

  • wait: создан, ждёт клиента.
  • partial: клиент отправил меньше нужного.
  • completed: пришла полная сумма; именно он запускает webhook.
  • rejected: платёж отклонён.

Заранее решите, что магазин делает для каждого. Для partial обычный выбор: оставить заказ открытым и попросить клиента доплатить либо вернуть деньги. Логика проста: помечайте заказ оплаченным только по completed, а для остальных состояний показывайте клиенту понятное сообщение.

Проверьте перед запуском

Короткий план, который ловит большинство проблем:

  1. Откройте демо-счёт, чтобы увидеть страницу оплаты глазами клиента.
  2. Создайте платёж с сервера и убедитесь, что получили redirectUrl.
  3. Оплатите небольшую реальную сумму и проверьте, что webhook пришёл и заказ стал оплаченным.
  4. Один раз сделайте так, чтобы ваша точка вернула ошибку, убедитесь, что Qvard повторяет доставку, а обработчик не проводит заказ дважды.
  5. Выведите небольшую сумму с баланса на кошелёк, чтобы убедиться, что весь путь работает.

Отдельной песочницы в Qvard пока нет, sandbox в планах, поэтому для шагов 3-5 используйте небольшие реальные суммы.

Чек-лист безопасности

  • Храните API-ключ только на сервере. Никогда не кладите его во фронтенд-код или публичный репозиторий.
  • Сравнивайте заголовок x-api-key в каждом webhook и отклоняйте несовпадения, используя сравнение за постоянное время.
  • Отдавайте точку по HTTPS и держите её быстрой; тяжёлую работу делайте после ответа.
  • Сопоставляйте платежи с заказами по вашему id и игнорируйте события по неизвестным заказам.
  • Логируйте каждый webhook со статусом, чтобы потом разбирать споры.

Подробнее по теме: как защитить webhook криптоплатежей.

А как же WooCommerce, Shopify и другие платформы

Применимы те же два вызова: код вашей платформы создаёт платёж и принимает webhook. Готового плагина у Qvard пока нет, поэтому на такой платформе, как WooCommerce, интеграция это небольшой кусок собственного кода или час работы разработчика, а не установка в один клик. Что с этим сейчас, смотрите на странице интеграций, а справочник API на странице разработчикам. Если сначала нужна общая картина, начните с материала что такое криптоплатёжный шлюз.

FAQ

Нужно ли знать разработку под блокчейн?

Нет. Вы делаете один HTTP-запрос для создания платежа и обрабатываете один webhook. Адреса, мониторинг сети и подтверждения берёт на себя шлюз.

Можно ли принимать крипту без написания кода?

В Qvard пока нет. Платёжные ссылки и другие no-code способы в разработке; сегодня интеграция идёт через API, для неё нужен разработчик.

Что если мой сервер недоступен, когда платёж завершился?

Уведомление повторяется пять раз примерно за шесть минут. Если сервер был недоступен дольше, отправьте уведомление по этому платежу повторно из кабинета и сверьте заказы.

В какой валюте указывать цену?

В долларах США. Клиент платит эквивалент в активе и сети, которые он выбирает на странице оплаты, по курсу, закреплённому на один час.

Похожие статьи

Похожие статьи