Интеграции

Кнопка оплаты криптой для сайта

Как добавить на сайт кнопку «Оплатить криптой»: почему кнопка не может обращаться к API платежей напрямую, схема из трёх шагов, рабочий код фронтенда и сервера и что нужно защитить.

Кнопку «Оплатить криптой» добавить просто, но это не может быть код для копирования: для создания платежа нужен ваш секретный API-ключ, который не должен попадать в браузер. Поэтому кнопка обязана обращаться к вашему серверу, который создаёт платёж и возвращает адрес страницы оплаты. В материале показана эта схема с рабочим кодом и теми пунктами, что делают её безопасной.

Почему готовой кнопки для копирования пока нет

Многие платёжные провайдеры дают встраиваемую кнопку или виджет. Встроенный чекаут у Qvard готов: это виджет для вашего сайта. Кнопки без кода пока нет: платёжные ссылки в разработке. SDK для Node.js, PHP, Python, Go и Java готовы. Сегодня интеграция идёт через API, а API аутентифицируется секретным ключом в заголовке Authorization: Bearer.

Этим ключом можно создавать платежи вашего проекта, поэтому он должен оставаться на сервере. Кнопка, несущая ключ во фронтенд-коде, отдала бы его каждому посетителю. Это не ограничение, которое нужно обходить: именно поэтому в схеме ниже есть серверный шаг.

Схема: кнопка, ваша точка, страница оплаты

Рабочую кнопку складывают три шага:

  1. Кнопка отправляет запрос на точку вашего сервера, сообщая, что посетитель хочет купить (товар, тариф), а не сколько это стоит.
  2. Ваша точка находит цену, создаёт собственную запись о заказе и обращается к Qvard: один POST на https://api.qvard.com/api/v1/payment с ключом в заголовке Authorization: Bearer и JSON, где номер заказа в id, а сумма в price в долларах США. Ответ 201 с redirectUrl.
  3. Браузер перенаправляют на этот redirectUrl, страницу оплаты Qvard, где посетитель выбирает актив и сеть и платит из кошелька.

Ничего секретного не попадает на страницу, а сама страница оплаты размещена у Qvard, так что собственный криптовый чекаут вам строить не нужно.

Фронтенд: кнопка и перенаправление

Кнопке нужно около пятнадцати строк. Она отключает себя на время запроса, отправляет идентификатор товара и при успехе отправляет браузер по полученному адресу. При ошибке она снова включается и показывает сообщение, чтобы посетитель мог повторить.

Держите в уме одно правило: браузер отправляет, что покупается, и никогда сколько это стоит. Если бы сумма бралась со страницы, любой мог бы открыть инструменты разработчика и превратить тариф за 49 долларов в платёж на один доллар.

HTML + JSpay-button.html
<button id="pay-crypto" data-product="pro-plan">Pay with crypto</button>

<script>
  document.getElementById('pay-crypto').addEventListener('click', async (event) => {
    const button = event.currentTarget;
    button.disabled = true;
    try {
      // Send only WHAT is being bought; the server decides the price.
      const res = await fetch('/api/crypto-checkout', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ product: button.dataset.product })
      });
      if (!res.ok) throw new Error('checkout failed');
      const { redirectUrl } = await res.json();
      window.location.assign(redirectUrl);   // the Qvard hosted payment page
    } catch (e) {
      button.disabled = false;
      alert('Could not start the payment. Please try again.');
    }
  });
</script>

Сервер: цена, заказ, платёж

Точка в примере делает четыре вещи:

  • Находит цену в таблице на сервере и отклоняет неизвестные товары.
  • Создаёт ваш заказ и использует его номер как id платежа. По нему webhook потом находит заказ.
  • Обращается к Qvard и возвращает браузеру только redirectUrl. Ответ 400 («Error validation» с сообщением по каждому полю) значит, что тело неверно; 401 значит, что неверен ключ. Ни то ни другое не повторяют без изменений.
  • Безопасна при повторах. Отправка того же id для того же способа оплаты возвращает уже созданный платёж, поэтому посетитель, нажавший дважды, не создаёт два.

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

Node.jscrypto-checkout.js
import express from 'express';
import crypto from 'node:crypto';

const app = express();
const PRICES_USD = { 'pro-plan': 49 };          // prices live on the server, never in the browser

app.post('/api/crypto-checkout', express.json(), async (req, res) => {
  const price = PRICES_USD[req.body.product];
  if (!price) return res.sendStatus(400);

  const orderId = await db.orders.create({ product: req.body.product, amountUsd: price }); // your own order number

  const response = await fetch('https://api.qvard.com/api/v1/payment', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.QVARD_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ id: String(orderId), price })
  });
  if (response.status !== 201) return res.sendStatus(502);

  const { redirectUrl } = await response.json();
  res.json({ redirectUrl });
});

После клика решает webhook

Перенаправление посетителя на страницу оплаты начинает платёж, но не завершает его. Когда платёж завершён, Qvard обращается к callback URL, сохранённому в вашем проекте, с вашим id и статусом completed. Только после этого открывайте доступ или помечайте заказ оплаченным. Точка должна отвечать ровно HTTP 200 и быть безопасной при двойном запуске; см. как защитить webhook криптоплатежей.

Возврата по ссылке после оплаты пока нет, поэтому посетитель возвращается на сайт сам. Дайте ему страницу с реальным статусом заказа, которая обновляется, пока идёт ожидание, а не страницу «спасибо», которая считает оплату успешной.

Фиксированные цены, произвольные суммы и что дальше

  • Фиксированные цены (тарифы, товары) самый простой случай: их хранит таблица на сервере.
  • Произвольные суммы (пожертвования, пополнения) требуют проверки на сервере: минимум, максимум и проверка числа. Сумма ниже минимума выбранного способа оплаты отклоняется с 400.
  • Несколько кнопок используют одну и ту же точку; отличается только идентификатор товара.

Полный разбор в материале как принимать криптоплатежи на сайте, а сам API в API для криптоплатежей. Демо-счёт показывает страницу, на которую ведёт кнопка; коммерческие условия на странице тарифов.

FAQ

Можно ли положить API-ключ Qvard в код кнопки?

Нет. Ключ создаёт платежи вашего проекта и должен оставаться на сервере. Кнопка должна вызывать вашу точку, а та обращается к Qvard.

Есть ли встраиваемый виджет или кнопка без кода?

Встроенный чекаут готов: это виджет для вашего сайта. Кнопки без кода пока нет: платёжные ссылки в разработке. SDK готовы. Сегодня интеграция идёт через API с небольшой серверной точкой.

Откуда брать цену?

С вашего сервера, никогда из браузера. Кнопка отправляет только то, что покупается, а цену в долларах США решает сервер.

Как узнать, что клиент заплатил?

Из webhook, который Qvard отправляет, когда платёж завершён. Возвращение посетителя не доказательство оплаты.

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

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