Бот-витрина магазина с оплатой в TON
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
В этой статье мы расскажем о том, как принимать платежи в боте Telegram.
📖 Чему вы научитесь
В этой статье вы узнаете, как:
- создать Telegram-бота с помощью Python + Aiogram
- работать с публичным API TON (TON Center)
- работать с базой данных SQlite
И наконец: как принимать платежи в Telegram-боте, используя знания из предыдущих шагов.
📚 Прежде чем мы начнем
Убедитесь, что у вас установлена последняя версия Python и установлены следующие пакеты:
- aiogram
- requests
- sqlite3
🚀 Давайте начнем!
Мы будем действовать по нижеприведенному порядку:
- Работа с базой данных SQlite
- Работа с публичным API TON (TON Center)
- Создание Telegram-бота с помощью Python + Aiogram
- Получаем прибыль!
Давайте создадим следующие четыре файла в директории нашего проекта:
telegram-bot
├── config.json
├── main.py
├── api.py
└── db.py
Конфигурация
В config.json
мы сохраним токен нашего бота и наш публичный ключ TON API.
{
"BOT_TOKEN": "Your bot token",
"MAINNET_API_TOKEN": "Your mainnet api token",
"TESTNET_API_TOKEN": "Your testnet api token",
"MAINNET_WALLET": "Your mainnet wallet",
"TESTNET_WALLET": "Your testnet wallet",
"WORK_MODE": "testnet"
}
В config.json
мы решаем, какую сеть мы будем использовать: testnet
или mainnet
.
База данных
Создаем базу данных
В этом примере используется локальная база данных Sqlite.
Создайте db.py
.
Чтобы начать работу с базой данных, нам нужно импортировать модуль sqlite3 и несколько модулей для работы со временем.
import sqlite3
import datetime
import pytz
Sqlite3
-модуль для работы с базой данных sqlitedatetime
- модуль для работы со временемpytz
- модуль для работы с часовыми поясами
Далее нам нужно создать соединение с базой данных и курсор для работы с ней:
locCon = sqlite3.connect('local.db', check_same_thread=False)
cur = locCon.cursor()
Если база данных не существует, она будет создана автоматически.
Теперь мы можем создать таблицы. У нас их две.
Транзакции:
CREATE TABLE transactions (
source VARCHAR (48) NOT NULL,
hash VARCHAR (50) UNIQUE
NOT NULL,
value INTEGER NOT NULL,
comment VARCHAR (50)
);
source
- адрес кошелька плательщикаhash
- хэш транзакцииvalue
- значение транзакцииcomment
- комментарий к транзакции
Пользователи:
CREATE TABLE users (
id INTEGER UNIQUE
NOT NULL,
username VARCHAR (33),
first_name VARCHAR (300),
wallet VARCHAR (50) DEFAULT none
);
id
- ID пользователя Telegramusername
- имя пользователя Telegramfirst_name
- имя пользователя Telegramwallet
- адрес кошелька пользователя
В таблице users
мы храним пользователей :) их Telegram ID, @логин,
имя и кошелек. Кошелек добавляется в базу данных при первом
успешном платеже.
В таблице transactions
хранятся проверенные транзакции.
Чтобы проверить транзакцию, нам нужны хеш, источник, значение и комментарий.
Чтобы создать эти таблицы, нам нужно выполнить следующую функцию:
cur.execute('''CREATE TABLE IF NOT EXISTS transactions (
source VARCHAR (48) NOT NULL,
hash VARCHAR (50) UNIQUE
NOT NULL,
value INTEGER NOT NULL,
comment VARCHAR (50)
)''')
locCon.commit()
cur.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER UNIQUE
NOT NULL,
username VARCHAR (33),
first_name VARCHAR (300),
wallet VARCHAR (50) DEFAULT none
)''')
locCon.commit()
Этот код создаст таблицы, если они еще не созданы.
Работа с базой данных
Давайте проанализируем ситуацию. Пользователь совершил транзакцию. Как ее подтвердить? Как сделать так, чтобы одна и та же транзакция не была подтверждена дважды?
В транзакциях есть body_hash, с помощью которого мы можем легко понять, есть ли транзакция в базе данных или нет.
Мы добавляем транзакции в базу данных, в которых мы уверены. Функция check_transaction
проверяет, есть ли найденная транзакция в базе данных или нет.
add_v_transaction
добавляет транзакцию в таблицу транзакций.
def add_v_transaction(source, hash, value, comment):
cur.execute("INSERT INTO transactions (source, hash, value, comment) VALUES (?, ?, ?, ?)",
(source, hash, value, comment))
locCon.commit()
def check_transaction(hash):
cur.execute(f"SELECT hash FROM transactions WHERE hash = '{hash}'")
result = cur.fetchone()
if result:
return True
return False
check_user
проверяет, есть ли пользователь в базе данных, и добавляет его, если нет.
def check_user(user_id, username, first_name):
cur.execute(f"SELECT id FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
if not result:
cur.execute("INSERT INTO users (id, username, first_name) VALUES (?, ?, ?)",
(user_id, username, first_name))
locCon.commit()
return False
return True
Пользователь может сохранить кошелек в таблице. Он добавляется при первой успешной покупке. Функция v_wallet
проверяет, есть ли у пользователя связанный с ним кошелек. Если есть, то возвращает его. Если нет, то добавляет.
def v_wallet(user_id, wallet):
cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
if result[0] == "none":
cur.execute(
f"UPDATE users SET wallet = '{wallet}' WHERE id = '{user_id}'")
locCon.commit()
return True
else:
return result[0]
get_user_wallet
просто возвращает кошелек пользователя.
def get_user_wallet(user_id):
cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
return result[0]
get_user_payments
возвращает список платежей пользователя.
Эта функция проверяет, есть ли у пользователя кошелек. Если есть, то она возвращает список платежей.
def get_user_payments(user_id):
wallet = get_user_wallet(user_id)
if wallet == "none":
return "You have no wallet"
else:
cur.execute(f"SELECT * FROM transactions WHERE source = '{wallet}'")
result = cur.fetchall()
tdict = {}
tlist = []
try:
for transaction in result:
tdict = {
"value": transaction[2],
"comment": transaction[3],
}
tlist.append(tdict)
return tlist
except:
return False
API
У нас есть возможность взаимодействовать с блокчейном, используя сторонние API, предоставляемые некоторыми участниками сети. С помощью этих сервисов разработчики могут пропустить этап запуска собственного узла и настройки API.
Необходимые запросы
Фактически, что нам нужно, чтобы подтвердить, что пользователь перевел нам требуемую сумму?
Нам просто нужно просмотреть последние входящие переводы на наш кошелек и найти среди них транзакцию с нужного адреса с нужной суммой (и, возможно, уникальным комментарием).
Для всего этого в TON Center есть метод getTransactions
.
getTransactions
По умолчанию, если мы применим эту функцию, мы получим 10 последних транзакций. Однако мы также можем указать, что нам нужно больше, но это несколько увеличит время ответа. И, скорее всего, вам не нужно так много.
Если вам нужно больше, то у каждой транзакции есть lt
и hash
. Вы можете просмотреть, например, 30 транзакций, и если среди них не найдется нужной, то взять lt
и hash
из последней и добавить их в запрос.
Таким образом, вы получаете следующие 30 транзакций и так далее.
Например, в тестовой сети есть кошелек EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5
, в нем есть несколько транзакций:
Используя запрос, мы получим ответ, содержащий две транзакции (часть информации, которая сейчас не нужна, была скрыта, полный ответ вы можете увидеть по ссылке выше).
{
"ok": true,
"result": [
{
"transaction_id": {
"lt": "1944556000003",
"hash": "swpaG6pTBXwYI2024NAisIFp59Fw3k1DRQ5fa5SuKAE="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "kBfGYBTkBaooeZ+NTVR0EiVGSybxQdb/ifXCRX5O7e0=",
"message": "Sea breeze 🌊"
},
"out_msgs": []
},
{
"transaction_id": {
"lt": "1943166000003",
"hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=",
"message": "Spring forest 🌲"
},
"out_msgs": []
}
]
}
Мы получили две последние транзакции с этого адреса. Добавив в запрос lt
и hash
, мы снова получим две транзакции. Однако вторая станет следующей в ряду. То есть, мы получ им вторую и третью транзакции для этого адреса.
{
"ok": true,
"result": [
{
"transaction_id": {
"lt": "1943166000003",
"hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=",
"message": "Spring forest 🌲"
},
"out_msgs": []
},
{
"transaction_id": {
"lt": "1845458000003",
"hash": "k5U9AwIRNGhC10hHJ3MBOPT//bxAgW5d9flFiwr1Sao="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "XpTXquHXP64qN6ihHe7Tokkpy88tiL+5DeqIrvrNCyo=",
"message": "Second"
},
"out_msgs": []
}
]
}
Запрос будет выглядеть вот так
Нам также понадобится метод detectAddress
.
Вот пример адреса кошелька Tonkeeper в тестовой сети: kQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aCTb
. Если мы поищем транзакцию в проводнике, то вместо указанного выше адреса будет: EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R
.
Этот метод возвращает нам "правильный" адрес.
{
"ok": true,
"result": {
"raw_form": "0:b3409241010f85ac415cbf13b9b0dc6157d09a39d2bd0827eadb20819f067868",
"bounceable": {
"b64": "EQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"b64url": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R"
},
"non_bounceable": {
"b64": "UQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU",
"b64url": "UQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU"
}
}
}
Нам нужен b64url
.
Этот метод позволяет нам подтвердить адрес пользователя.
По большей части, это все, что нам нужно.