Бот для продажи пельменей
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
В этой статье мы создадим простого Telegram-бота для приема платежей в TON.
🦄 Как это выглядит
По окончанию урока вы напишете красивого бота, который сможет принимать платежи за ваш товар прямо в TON.
Бот будет выглядеть следующим образом:
📖 Чему вы научитесь
Вы узнаете, как:
- Создать Telegram-бота в NodeJS с помощью grammY
- Работать с открытым TON Center API
Почему мы используем grammY? Потому что grammY - это современный, молодой, высокоуровневый фреймворк для удобной и быстрой разработки Telegram-ботов на JS/TS/Deno. Кроме того, у grammY отличная [документация] (https://grammy.dev) и активное сообщество, которое всегда сможет вам помочь.
✍️ Что нужно для начала работы
Установите NodeJS, если вы этого еще не сделали.
Также вам понадобятся эти библиотеки:
- grammy
- ton
- dotenv
Вы можете установить их одной командой в терминале.
- npm
- Yarn
- pnpm
npm install ton dotenv grammy @grammyjs/conversations
yarn add ton dotenv grammy @grammyjs/conversations
pnpm add ton dotenv grammy @grammyjs/conversations
🚀 Давайте начнем!
Структура нашего проекта будет выглядеть следующим образом:
src
├── bot
├── start.js
├── payment.js
├── services
├── ton.js
├── app.js
.env
bot/start.js
иbot/payment.js
- файлы с обработчиками для Telegram-ботаsrc/ton.js
- файл с business logic, связанной с TONapp.js
- файл для инициализации и запуска бота
Теперь давайте начнем писать код!
Конфигурация
Давайте начнем с .env
. Нам просто нужно задать в нем несколько параметров.
.env
BOT_TOKEN=
TONCENTER_TOKEN=
NETWORK=
OWNER_WALLET=
Здесь вам нужно заполнить значения в первых четырех строках:
BOT_TOKEN
- это ваш токен Telegram-бота, который вы можете получить после [создания бота] (https://t.me/BotFather).OWNER_WALLET
- это адрес кошелька вашего проекта, который будет принимать все платежи. Вы можете просто создать новый кошелек TON и скопировать его адрес.API_KEY
- это ваш API-ключ от TON Center, который вы можете получить от @tonapibot/@tontestnetapibot для основной и тестовой сетей, соответственно.NETWORK
- это информация о том, в какой сети будет работать ваш бот - тестовой или основной.
Это все, что касается файла конфигурации, так что мы можем двигаться дальше!
TON Center API
В файле src/services/ton.py
мы будем декларировать функции для проверки существования транзакции и генерирования ссылок для быстрого перехода к приложению кошелька для оплаты
Получение последних транзакций по кошельку
Наша задача - проверить доступность нужной нам транзакции с определенного кошелька.
Мы решим эту задачу следующим образом:
- Мы получим последние транзакции, поступившие на наш кошелек. Почему именно на наш? В этом случае нам не нужно беспокоиться об адресе кошелька пользователя, нам не нужно подтверждать, что это его кошелек, нам не нужно нигде хранить этот кошелек где-либо.
- Отсортируйте и оставьте только входящие транзакции
- Давайте пройдемся по всем транзакциям, и каждый раз будем проверять, равны ли комментарий и сумма тем данным, которые у нас есть
- Празднуем решение нашей проблемы🎉
Получение последних транзакций
Если мы используем TON Center API, то мы можем обратиться к их документации и найти метод, который идеально решает нашу проблему - getTransactions.
Для получения транзакций нам достаточно одного параметра - адреса кошелька для приема платежей, но мы также будем использовать параметр limit, чтобы ограничить выдачу транзакций до 100 штук.
Давайте попробуем вызвать тестовый запрос для адреса EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N
(кстати, это адрес TON Foundation)
curl -X 'GET' \
'https://toncenter.com/api/v2/getTransactions?address=EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N&limit=100' \
-H 'accept: application/json'
Отлично, теперь у нас есть список транзакций в ["result"], теперь давайте рассмотрим подробнее 1 транзакцию
{
"@type": "raw.transaction",
"utime": 1667148685,
"data": "*data here*",
"transaction_id": {
"@type": "internal.transactionId",
"lt": "32450206000003",
"hash": "rBHOq/T3SoqWta8IXL8THxYqTi2tOkBB8+9NK0uKWok="
},
"fee": "106508",
"storage_fee": "6508",
"other_fee": "100000",
"in_msg": {
"@type": "raw.message",
"source": "EQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBXwtG",
"destination": "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N",
"value": "1000000",
"fwd_fee": "666672",
"ihr_fee": "0",
"created_lt": "32450206000002",
"body_hash": "Fl+2CzHNgilBE4RKhfysLU8VL8ZxYWciCRDva2E19QQ=",
"msg_data": {
"@type": "msg.dataText",
"text": "aGVsbG8g8J+Riw=="
},
"message": "hello 👋"
},
"out_msgs": []
}
Из этого json-файла мы можем понять некоторую информацию, которая может быть нам полезна:
- Это входящая транзакция, поскольку поле "out_msgs" пустое.
- Мы также можем получить комментарий к транзакции, ее отправителю и сумме транзакции.
Теперь мы готовы создать средство проверки транзакций
Работа с TON
Давайте начнем с импорта необходимой библиотеки TON
import { HttpApi, fromNano, toNano } from "ton";
Давайте подумаем, как проверить, отправил ли пользователь нужную нам транзакцию.
Все элементарно просто. Мы можем просто отсортировать только входящие транзакции в наш кошелек, а затем просмотреть последние 100 транзакций, и если найдется транзакция с таким же комментарием и суммой, значит, мы нашли нужную нам транзакцию!
Давайте начнем с инициализации http-клиента, для удобства работы с TON
export async function verifyTransactionExistance(toWallet, amount, comment) {
const endpoint =
process.env.NETWORK === "mainnet"
? "https://toncenter.com/api/v2/jsonRPC"
: "https://testnet.toncenter.com/api/v2/jsonRPC";
const httpClient = new HttpApi(
endpoint,
{},
{ apiKey: process.env.TONCENTER_TOKEN }
);
Здесь мы просто генерируем url ко нечной точки в зависимости от того, какая сеть выбрана в конфигурации. После этого мы инициализируем http-клиент.
Итак, теперь мы можем получить последние 100 транзакций из кошелька владельца
const transactions = await httpClient.getTransactions(toWallet, {
limit: 100,
});
и отфильтруйте, оставив только входящие транзакции (если out_msgs транзакции пуст, мы оставляем его)
let incomingTransactions = transactions.filter(
(tx) => Object.keys(tx.out_msgs).length === 0
);
Теперь нам остается пройтись по всем транзакциям, и если комментарий и значение транзакции совпадают, мы вернем true
for (let i = 0; i < incomingTransactions.length; i++) {
let tx = incomingTransactions[i];
// Skip the transaction if there is no comment in it
if (!tx.in_msg.msg_data.text) {
continue;
}
// Convert transaction value from nano
let txValue = fromNano(tx.in_msg.value);
// Get transaction comment
let txComment = tx.in_msg.message
if (txComment === comment && txValue === value.toString()) {
return true;
}
}
return false;
Обратите внимание, что по умолчанию значение дается в нанотонах, поэтому нам нужно разделить его на 1 миллиард, или же мы можем просто использовать метод fromNano
из библиотеки TON.
Вот и все для функции verifyTransactionExistance
!
Теперь мы можем создать функци ю для генерации ссылки для быстрого перехода к приложению кошелька для оплаты
export function generatePaymentLink(toWallet, amount, comment, app) {
if (app === "tonhub") {
return `https://tonhub.com/transfer/${toWallet}?amount=${toNano(
amount
)}&text=${comment}`;
}
return `https://app.tonkeeper.com/transfer/${toWallet}?amount=${toNano(
amount
)}&text=${comment}`;
}
Все, что нам нужно - это просто подставить параметры транзакции в URL. Не забыв при этом передать значение транзакции в nano.
Telegram-бот
Инициализация
Откройте файл app.js
и импортируйте все необходимые нам обработчики и модули.
import dotenv from "dotenv";
import { Bot, session } from "grammy";
import { conversations, createConversation } from "@grammyjs/conversations";
import {
startPaymentProcess,
checkTransaction,
} from "./bot/handlers/payment.js";
import handleStart from "./bot/handlers/start.js";
Давайте настроим модуль dotenv для удобной работы с переменными окружения, которые мы задали в файле .env
dotenv.config();
После этого мы создаем функцию, которая будет запускать наш проект. Чтобы наш бот не останавливался при возникновении ошибок, мы добавляем следующий код
async function runApp() {
console.log("Starting app...");
// Handler of all errors, in order to prevent the bot from stopping
process.on("uncaughtException", function (exception) {
console.log(exception);
});
Теперь инициализируйте бота и необходимые плагины
// Initialize the bot
const bot = new Bot(process.env.BOT_TOKEN);
// Set the initial data of our session
bot.use(session({ initial: () => ({ amount: 0, comment: "" }) }));
// Install the conversation plugin
bot.use(conversations());
bot.use(createConversation(startPaymentProcess));
Здесь мы используем BOT_TOKEN
из нашего файла конфигурации, который мы сделали в начале урока.
Мы инициализировали бота, но он все еще пуст. Мы должны добавить несколько функций для взаимодействия с пользователем.
// Register all handelrs
bot.command("start", handleStart);
bot.callbackQuery("buy", async (ctx) => {
await ctx.conversation.enter("startPaymentProcess");
});
bot.callbackQuery("check_transaction", checkTransaction);
В ответ на команду /start, будет выполнена функция handleStart. Если пользователь нажмет на кнопку с параметром callback_data, равным "buy", мы начнем наш "conversation", который мы зарегистрировали чуть выше. А когда мы нажмем на кнопку с callback_data, равным "check_transaction", мы выполним функцию checkTransaction.
И все, что нам остается, это запустить нашего бота и вывести журнал об успешном запуске
// Start bot
await bot.init();
bot.start();
console.info(`Bot @${bot.botInfo.username} is up and running`);
Обработчики сообщений
Команда /start
Давайте начнем с обработчика команды /start
. Эта функция будет вызвана, когда пользователь запустит бота в первый раз или перезапустит его
import { InlineKeyboard } from "grammy";
export default async function handleStart(ctx) {
const menu = new InlineKeyboard()
.text("Buy dumplings🥟", "buy")
.row()
.url("Article with a detailed explanation of the bot's work", "docs.ton.org/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js");
await ctx.reply(
`Hello stranger!
Welcome to the best Dumplings Shop in the world <tg-spoiler>and concurrently an example of accepting payments in TON</tg-spoiler>`,
{ reply_markup: menu, parse_mode: "HTML" }
);
}
Здесь мы сначала импортируем InlineKeyboard из модуля grammy. После этого мы создаем инлайн-клавиатуру в обработчике с предложением купить пельмени и ссылкой на эту статью (здесь немного рекурсии😁). .row() - означает перенос следующей кнопки на новую строку. После этого мы отправляем приветственное сообщение с текстом (важно - я использую html-разметку в сообщении, чтобы украсить его) вместе с созданной клавиатурой. Приветственное сообщение может быть любым, каким вы захотите.
Процесс оплаты
Как обычно, мы начнем наш файл с необходимых импортов
import { InlineKeyboard } from "grammy";
import {
generatePaymentLink,
verifyTransactionExistance,
} from "../../services/ton.js";