Принимайте криптоплатежи в чате Telegram

Создайте платёж через API, отправьте клиенту ссылку telegramUrl в вашем боте и подтвердите оплату вебхуком.

Как это работает

  1. 1
    Создайте платёж через API. В ответе будет telegramUrl.
  2. 2
    Отправьте telegramUrl клиенту в вашем боте.
  3. 3
    Клиент платит в боте Qvard @QvardBot, а вы получаете вебхук со статусом.

Создайте платёж

Вызывайте API с вашего сервера и передавайте секретный ключ (qvard_sk_…) в заголовке Authorization: Bearer. В теле три поля:

  • price: сумма в валюте currency. По умолчанию USD.
  • currency: код валюты ISO-4217, например USD или INR.
  • id: ваш номер заказа, до 128 символов. Повторный запрос с тем же id вернёт тот же платёж.
curl -X POST https://api.qvard.com/api/v1/payment \
  -H 'Authorization: Bearer qvard_sk_YOUR_SECRET_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"price":10,"currency":"USD","id":"order-123"}'
{
  "data": {
    "id": "…",
    "redirectUrl": "https://my.qvard.com/pay/…",
    "telegramUrl": "https://t.me/QvardBot?start=pay_…"
  },
  "code": 201,
  "message": "success",
  "status": true
}

Ответ обёрнут в поле data. telegramUrl (https://t.me/QvardBot?start=pay_…) приходит, когда бот Qvard настроен; redirectUrl есть всегда.

Справочник API

Отправьте ссылку в вашем боте

Пример создаёт платёж по команде /buy, сохраняет ваш номер заказа и отправляет кнопку с telegramUrl. Токен бота и секретный ключ берите из переменных окружения.

import os
import httpx
from aiogram import Bot, Dispatcher
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message

bot = Bot(token=os.environ["BOT_TOKEN"])
dp = Dispatcher()

@dp.message(Command("buy"))
async def buy(message: Message):
    order_id = f"order-{message.message_id}"  # your own order ID
    async with httpx.AsyncClient(timeout=10) as http:
        r = await http.post(
            "https://api.qvard.com/api/v1/payment",
            headers={"Authorization": "Bearer " + os.environ["QVARD_SECRET_KEY"]},
            json={"price": 10, "currency": "USD", "id": order_id},
        )
    r.raise_for_status()
    telegram_url = r.json()["data"]["telegramUrl"]
    # save order_id and message.chat.id in your database here
    kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Pay", url=telegram_url)]])
    await message.answer("Pay the order in the Qvard bot:", reply_markup=kb)

if __name__ == "__main__":
    import asyncio
    asyncio.run(dp.start_polling(bot))

Подтвердите платёж

Qvard отправляет { id, status } на адрес вашего вебхука, подпись в заголовке X-Qvard-Signature. Сначала проверьте подпись по сырому телу, как описано на странице вебхука. Потом пометьте заказ оплаченным и напишите клиенту в вашем боте. Действуйте по статусу completed; partial значит, что пришла только часть суммы.

const crypto = require('crypto');
const express = require('express');

function verifyQvardSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(',').map((p) => p.trim());
  const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2));
  const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex');
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

// Use the RAW body: express.raw, not express.json
app.post('/qvard-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyQvardSignature(raw, req.get('X-Qvard-Signature') || '', process.env.QVARD_SECRET_KEY)) {
    return res.sendStatus(400);
  }
  const { id, status } = JSON.parse(raw); // id is your order ID
  if (status === 'completed') {
    // check the amount against your order, then mark it paid; markPaid is your own function
    const order = await markPaid(id); // returns { chatId } of the order
    await bot.api.sendMessage(order.chatId, 'Payment received. Thank you!'); // bot from the grammY example
  }
  res.sendStatus(200);
});

Как проверить подпись вебхука

Что видит клиент

  1. 1
    Клиент открывает ссылку telegramUrl. Бот показывает сумму и предлагает выбрать актив и сеть.
  2. 2
    После выбора бот показывает адрес (нажатием его можно скопировать), сумму, время до конца счёта и кнопки «QR», «Обновить статус» и «Открыть страницу оплаты».
  3. 3
    Статус обновляется в том же сообщении.
  4. 4
    При полной оплате бот присылает отдельное сообщение «Платёж получен» (оно даёт push на телефон), при частичной — отдельное сообщение «получено X, осталось Y».

Правила Telegram

Telegram требует оплату Telegram Stars за цифровые товары и услуги, которые продаются внутри ботов и Mini App, и не допускает для них криптовалюту. Бот Qvard подходит для физических товаров и для платежей, которые не являются цифровыми товарами внутри Telegram. Соблюдать правила Telegram должен продавец.

Ограничения

  • Бот Qvard @QvardBot общий для всех мерчантов. Подключить свой бот нельзя.
  • Telegram Stars и Mini App пока не поддерживаются.
  • Уведомления операторам в Telegram пока не реализованы.

Готовы принимать платежи в Telegram?

Создать аккаунт