D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Предисловие
В данной статье будет реализирована собственная платежная система на TON с использованием собственного API, а также тестирование этого API. Для реализации платежной системы за основу будет взята библиотека tonutils в связке с tonconsole. Эта библиотека была выбрана, поскольку она показалась более простой в освоении по сравнению с другими библиотеками, список которых можно посмотреть по этой ссылке: https://docs.ton.org/develop/dapps/apis/sdk#adnl-based-sdksСоздание кошелька
Для начала будет рассмотрен код для создания TON-кошелька. Первое, что нужно сделать, — это зарегистрироваться на tonconsole и получить API-ключ: https://tonconsole.com/. Регистрация там простая, нужно лишь авторизоваться через аккаунт Telegram. Ограничения у данного сервиса достаточно незаметные: возможен только один запрос в секунду, что, по моему мнению, абсолютно не критично. После получения ключа можно начинать писать код. В новом проекте будет создан файл create_wallets.py. В этом файле сразу же нужно указать необходимые импорты.Python: Скопировать в буфер обмена
Код:
from tonutils.client import TonapiClient
from tonutils.wallet import WalletV3R1
Далее нужно создать переменную с API-ключом, полученным ранее, и переменную, которая будет использоваться как флаг для указания, в какой сети будет работать софт — в тестовой или основной (в статье будет использоваться только тестовая сеть, так как в ней можно получить TON бесплатно).
Python: Скопировать в буфер обмена
Код:
api_key = ""
is_testnet = True
Python: Скопировать в буфер обмена
Код:
client = TonapiClient(api_key=api_key, is_testnet=is_testnet)
# Создание кошелька
wallet, public_key, private_key, mnemonic = WalletV3R1.create(client)
# Конвертация ключей в hex формат
public_key_hex = public_key.hex()
private_key_hex = private_key.hex()
Для отображения результата были добавлены принты.
Python: Скопировать в буфер обмена
Код:
print("Кошелек успешно создан!")
print(f"Адрес: {wallet.address.to_str()}")
print(f"Публичный ключ: {public_key_hex}")
print(f"Приватный ключ: {private_key_hex}")
print(f"Мнемоническая фраза: {mnemonic}")
Результат:
Проверка баланса
Теперь будет показано, как реализовать проверку баланса с использованием tonconsole.com. Документацию по их API можно посмотреть по этой ссылке: https://docs.tonconsole.com/. Проверка баланса будет выполняться через обычные запросы, и, кроме ограничения в один запрос в секунду, других ограничений нет.Был создан новый файл с названием check_balance.py. В нем сразу нужно указать API-ключ, кошелек и ссылку для отправки запроса (если убрать из ссылки "testnet.", то работа будет с основной сетью), а также саму отправку запроса.
Python: Скопировать в буфер обмена
Код:
api_key = ''
wallet_address = ''
balance_url = f'https://testnet.tonapi.io/v2/accounts/{wallet_address}'
headers = {
'X-API-KEY': api_key
}
response = requests.get(balance_url, headers=headers)
Далее из ответа нужно извлечь ключ balance и разделить его значение на 1 000 000 000. Если не выполнять деление, то результат будет не в TON, а в nanoTON
Python: Скопировать в буфер обмена
Код:
# Если ответ положительный
if response.status_code == 200:
# Запись ответа в переменную data
data = response.json()
# Извлечение баланса
balance = data.get('balance')
print(f"Баланс: {balance / 1000000000} TON") # Преобразуем в TON
else:
print(f"Ошибка при получении баланса: {response.status_code} - {response.text}")
Результат:
Также можно проверять баланс, не отправляя запросы через requests, а используя tonutils.
Python: Скопировать в буфер обмена
Код:
from tonutils.client import TonapiClient
from tonutils.utils import to_amount
from tonutils.wallet import WalletV3R1
api_key = ""
is_testnet = True
mnemonic: list[str] = []
async def main() -> None:
client = TonapiClient(api_key=api_key, is_testnet=is_testnet )
wallet, public_key, private_key, mnemonic = WalletV3R1.from_mnemonic(client, mnemonic)
balance = await wallet.balance()
print(f"Баланс: {to_amount(balance)}")
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Как получить TON в тестовой сети
Для того чтобы получить монеты, можно отправить команду /get этому боту в Telegram: @testgiver_ton_bot. После этого нужно пройти капчу и ввести кошелек, и всё. В течение 10 секунд на кошелек придет 2 TON в тестовой сети.Важно замечание! Если вы генерируете 2 кошелька и на каждый из них выдали TON в тестовой сети, то по умолчанию кошельки не будут активными. Чтобы сделать их активными, вам потребуется совершить с них любую транзакцию на любой кошелек, даже в тестовой сети. Можете просто отправить любое количество TON с каждого кошелька друг другу.
Чтобы узнать, активен ли кошелек, можете перейти по этой ссылке: https://testnet.tonapi.io/v2/accounts/кошелек. На открывшейся странице найдите ключ "status".
Отправка транзакций
Раз речь пошла об отправке транзакций с одного кошелька на другой, то как раз это сейчас и будет реализовано в коде. Для этого был создан файл transfer_ton.py. В данном файле также нужно указать API, сеть, мнемоническую фразу, а также адрес, куда отправлять, комментарий к транзакции (необязателен) и сумму к отправке.Python: Скопировать в буфер обмена
Код:
from tonutils.client import TonapiClient
from tonutils.wallet import WalletV3R1
api_key = ""
is_testnet = True
MNEMONIC: list[str] = []
DESTINATION_ADDRESS = ""
COMMENT = "hui1234"
Вот код для отправки TON:
Python: Скопировать в буфер обмена
Код:
async def transfer():
client = TonapiClient(api_key=api_key, is_testnet=is_testnet)
# Создание кошелька на основе мнемонической фразы и запись полученных данных в переменные
wallet, public_key, private_key, mnemonic = WalletV3R1.from_mnemonic(client, seed)
# wallet.transfer отправляет транзакцию. В tx_hash записывается хэш транзакции
tx_hash = await wallet.transfer(destination=destination_address, amount=amount, body=comment,)
print(f"Успешно переведено {amount} TON!")
print(f"Хэш транзакции: {tx_hash}")
asyncio.run(transfer())
Результат:
Проверка транзакций
Теперь рассмотрим код для проверки транзакций и поиска нужной. Так же, как и при проверке баланса, код будет работать через запросы.Python: Скопировать в буфер обмена
Код:
api_key = ''
wallet_address = ''
transactions_url = f'https://testnet.tonapi.io/v2/blockchain/accounts/{wallet_address}/transactions'
Я не придумал ничего лучше, чем искать нужную транзакцию по сумме перевода. Учитывая, что суммы могут повторяться, в дальнейшем при переводе с кошелька на кошелек будет добавляться рандомное небольшое значение к сумме, чтобы каждая из транзакций была уникальной. Так как проверка будет происходить по сумме, нужно создать переменную, в которую будет записана сумма для поиска.
Python: Скопировать в буфер обмена
amount = 0.1
Далее следует сама отправка запроса и передача ключа.
Python: Скопировать в буфер обмена
Код:
headers = {
'X-API-KEY': api_key
}
response = requests.get(transactions_url, headers=headers)
Затем в полученном ответе будет искаться ключ value из in_msg. Именно в value хранится сумма перевода в NanoTON.
Python: Скопировать в буфер обмена
Код:
# Если ответ положительный
if response.status_code == 200:
# Запись ответа в переменную data
data = response.json()
# Проходим по всем транзакциям и извлекаем значение
if 'transactions' in data:
transaction_found = False # Флаг для проверки, найдена ли транзакция
# Проходит по всем строкам из ключа 'transactions'
for transaction in data['transactions']:
# Если строка это 'in_msg', то если в 'in_msg' есть ключ 'value'
if 'in_msg' in transaction and 'value' in transaction['in_msg']:
value = transaction['in_msg']['value'] # Получаем value
value_in_ton = value / 1000000000 # Делим на 1,000,000,000 для перевода в TON
# Сравнение каждого значения с заданной суммой
if value_in_ton == amount:
print(f"Транзакция найдена: {value_in_ton} TON")
transaction_found = True # Установка ключа означающего, что транзакция найдена
break # Выход из цикла, если транзакция найдена
# Если ключ transaction_found false то значит транзакция не найдена
if not transaction_found:
print("Транзакция не найдена.")
else:
print("Нет доступных транзакций.")
else:
# Выводим код состояния и текст ответа для отладки
print(f"Ошибка при получении списка транзакций: {response.status_code} - {response.text}")
Результат:
Пользовательский интерфейс
На этом разбор основных функций завершён, и можно приступать к реализации пользовательского интерфейса для управления платежной системой. Интерфейс, как и всегда, я буду реализовывать на Flask.Регистрация
Первое, что будет реализовано, — это регистрация в сервисе. При регистрации пользователю будет сразу выдаваться UUID, и будет создаваться кошелек, на который в дальнейшем будут идти все транзакции.Для начала просто сделаю инициализацию Flask и базы данных.
Python: Скопировать в буфер обмена
Код:
from flask import Flask, request, jsonify, session, render_template, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from uuid import uuid4
from sqlalchemy.ext.mutable import MutableList
from create_wallets import create_wallet
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
# Значение по умолчанию это генерация UUID
uuid = db.Column(db.String(36), unique=True, nullable=False, default=str(uuid4()))
username = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
# Столбцы для данных кошелька
wallet_address = db.Column(db.String(50), nullable=True)
public_key = db.Column(db.String(128), nullable=True)
private_key = db.Column(db.String(128), nullable=True)
mnemonic = db.Column(MutableList.as_mutable(db.JSON), nullable=True) # Используется тип данных JSON
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Python: Скопировать в буфер обмена
uuid = db.Column(db.String(36), unique=True, nullable=False, default=str(uuid4()))
Можно было бы генерировать UUID внутри будущей функции регистрации, но такой вариант мне показался более удобным.
Теперь рассмотрим саму функцию регистрации.
Python: Скопировать в буфер обмена
Код:
@app.route('/register', methods=['GET', 'POST'])
def register():
# POST запрос означает что на адрес был отправлен запрос с html страницы в котором должны передаваться данные из полей ввода
if request.method == 'POST':
# Извлекает данные из полей с сайта
username = request.form.get('username')
password = request.form.get('password')
# Если одно из полей пустое или оба пустые
if not username or not password:
return jsonify({"error": "Требуется имя пользователя и пароль"}), 400
# Сравнивает данные из переменной с данными из бд
existing_user = Users.query.filter_by(username=username).first()
# Если данные сходятся, то регистрации не происходит и выводится ошибка
if existing_user:
return jsonify({"error": "Имя пользователя занято"}), 400
# Берет пароль из переменной и хеширует его с помощью generate_password_hash и записывает хэш в переменную
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
# Назначает столбцам из бд данные из переменных (пароль в виде хэша)
new_user = Users(username=username, password=hashed_password)
# Добавляет новые данные в сессию
db.session.add(new_user)
# Сохраняет изменения в базе данных
db.session.commit()
# Создаем кошелек и получаем его данные
wallet_data = create_wallet() # Вызов функции для создания кошелька
# Сохраняем данные кошелька в базу данных
new_user.wallet_address = wallet_data["address"]
new_user.public_key = wallet_data["public_key"]
new_user.private_key = wallet_data["private_key"]
new_user.mnemonic = wallet_data["mnemonic"]
db.session.commit() # Сохраняем изменения
# Если все действия выше были выполнены удачно и пользователь зарегистрирован, то переадресация на страницу авторизации
return redirect(url_for('login'))
# Это если GET запрос, то есть если просто переход по ссылке в браузере
return render_template('register.html')
Python: Скопировать в буфер обмена
Код:
from tonutils.client import TonapiClient
from tonutils.wallet import WalletV3R1
# API ключ для доступа tonconsole
api_key = ""
# Функция создания кошелька с параметром означающим работу в тестовой сети
def create_wallet(is_testnet=True):
client = TonapiClient(api_key=api_key, is_testnet=is_testnet)
# Создание кошелька
wallet, public_key, private_key, mnemonic = WalletV3R1.create(client)
# Конвертация ключей в hex формат
public_key_hex = public_key.hex()
private_key_hex = private_key.hex()
# Возвращает данные от кошелька в виде словаря json
return {
"address": wallet.address.to_str(),
"public_key": public_key_hex,
"private_key": private_key_hex,
"mnemonic": mnemonic
}
Авторизация
Теперь рассмотрим функцию авторизации.Python: Скопировать в буфер обмена
Код:
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# Получение данных из полей ввода
username = request.form.get('username')
password = request.form.get('password')
# Если одно или оба поля ввода пустые
if not username or not password:
return jsonify({"error": "Требуется имя пользователя и пароль"}), 400
# Сравнивает введённое имя пользователя с данными в базе данных
user = Users.query.filter_by(username=username).first()
# Если пользователь найден, то сравнивает его пароль в виде хэша с паролем из поля ввода
if user and bcrypt.check_password_hash(user.password, password):
# Записывает в сессию, внутрь ключа user_id ид из столбца в базе данных
session['user_id'] = user.id
return redirect(url_for('profile'))
return jsonify({"error": "Неправильное имя пользователя или пароль"}), 401
return render_template('login.html')
Профиль
Теперь рассмотрим маршрут страницы профиля.Python: Скопировать в буфер обмена
Код:
@app.route('/profile')
def profile():
# Если в сессии нет ключа с ид пользователя, то редирект на страницу авторизации
if 'user_id' not in session:
return redirect(url_for('login'))
# Если ид пользователя из сессии найден в базе данных, то редирект на страницу профиля
user = Users.query.get(session['user_id'])
# Возвращает страницу профиля и передает параметры username, uuid и wallet_address из базы данных
return render_template('profile.html', username=user.username, uuid=user.uuid, wallet_address=user.wallet_address)
Веб часть
Теперь можно разобрать код веб-части интерфейса.Страница регистрации
Первой на очереди будет страница для регистрации.HTML: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register</title>
</head>
<body>
<h2>Регистрация</h2>
<form method="POST" action="{{ url_for('register') }}">
<label for="username">Имя пользователя:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Зарегистрироваться</button>
</form>
<p>Есть аккаунт? <a href="{{ url_for('login') }}">Авторизация</a>.</p>
</body>
</html>
Страница авторизации
Далее рассмотрим страницу авторизации. Здесь абсолютно то же самое, что и на странице регистрации.HTML: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h2>Авторизация</h2>
<form method="POST" action="{{ url_for('login') }}">
<label for="username">Имя пользователя:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Авторизоваться</button>
</form>
<p>Нет аккаунта? <a href="{{ url_for('register') }}">Регистрация</a>.</p>
</body>
</html>
Страница профиля
Теперь перейдем к странице профиля.HTML: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Profile</title>
</head>
<body>
<p>Имя пользователя: {{username}}</p>
<p>UUID: {{uuid}}</p>
<p>Кошелек: {{wallet_address}}</p>
<form action="{{ url_for('logout') }}" method="POST">
<button type="submit">Выйти</button>
</form>
</body>
</html>
HTML: Скопировать в буфер обмена
return render_template('profile.html', username=user.username, uuid=user.uuid, wallet_address=user.wallet_address)
Форма с ссылкой на logout пока не работает, так как данный маршрут не был прописан на стороне Python. Сейчас это будет исправлено:
Python: Скопировать в буфер обмена
Код:
@app.route('/logout', methods=['POST'])
def logout():
session.pop('user_id', None)
return redirect(url_for('login'))
На этом с веб-частью и регистрацией в сервисе пока что закончено, и вот страницы, которые в данный момент готовы:
Написание API
Теперь можно приступать к реализации собственного API, благодаря которому в дальнейшем можно будет настраивать систему оплаты в сторонних проектах.Принцип работы
Для начала рассмотрим, как будет устроена работа с API. Представим, что есть магазин с кнопкой "Купить". При нажатии на эту кнопку будет отправляться запрос к API на создание заявки об оплате. В запросе будет храниться сумма отправки и UUID человека, использующего API. Далее API создает заявку и отправляет обратно магазину ответ с ID заявки, суммой к оплате (будет добавляться рандомное небольшое значение, чтобы сумма была уникальной) и кошельком, на который оплачивать. Далее магазин получает этот ответ и выводит на своей странице данные к оплате, добавляя кнопку "Оплатил". При нажатии на эту кнопку на API снова отправляется запрос, но уже с номером заявки. Затем API проверяет этот номер заявки, берет кошелек, указанный в ней, и сумму к оплате, после чего проверяет, есть ли транзакция на кошельке на сумму, указанную в заявке. Если есть, API возвращает положительный ответ магазину, а дальше магазин делает то, что хочет.Создание заявок на оплату
Первое, что будет сделано, — это возможность создавать заявки. Первым делом нужно переделать базу данных, добавить в нее таблицу, в которой будет в дальнейшем записываться ID транзакции и сумма.Python: Скопировать в буфер обмена
Код:
class Users(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# Значение по умолчанию это генерация UUID
uuid = db.Column(db.String(36), unique=True, nullable=False, default=str(uuid4()))
username = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
# Столбцы для данных кошелька
wallet_address = db.Column(db.String(50), nullable=True)
public_key = db.Column(db.String(128), nullable=True)
private_key = db.Column(db.String(128), nullable=True)
mnemonic = db.Column(MutableList.as_mutable(db.JSON), nullable=True) # Используется тип данных JSON
# Связь с таблицей Transactions
transactions = relationship("Transactions", backref="user", lazy=True)
class Transactions(db.Model):
__tablename__ = 'transactions'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, ForeignKey('users.id'), nullable=False) # Внешний ключ для связи с Users
amount = db.Column(db.Float, nullable=False)
timestamp = db.Column(db.DateTime, default=db.func.current_timestamp())
Далее можно приступать к написанию API-логики. Для этого понадобится создать функцию, которая будет принимать POST-запросы с данными для создания заявки на оплату, а именно: суммой и UUID. После этого полученная сумма будет записываться в базу данных, а также ID пользователя, чей UUID был принят.
Python: Скопировать в буфер обмена
Код:
@app.route('/create_invoice', methods=['POST'])
def create_invoice():
# Извлекаем UUID из заголовков запроса
uuid = request.headers.get('Authorization')
if not uuid:
return jsonify({"error": "UUID не предоставлен"}), 400
# Ищем пользователя с этим UUID в базе данных
user = Users.query.filter_by(uuid=uuid).first()
if not user:
return jsonify({"error": "Пользователь не найден"}), 404
# Извлекаем данные из запроса и записываем в переменную
data = request.json
# Запись суммы из ключа amount
amount = data.get('amount')
# Добавляем небольшое случайное значение от 0.00001 до 0.00011
random_increment = random.uniform(0.00001, 0.00999)
amount = round(amount + random_increment, 5)
if amount is None:
return jsonify({"error": "Сумма не предоставлена"}), 400
# Создаем новую заявку и добавляем в базу данных
transaction = Transactions(user_id=user.id, amount=amount)
db.session.add(transaction)
db.session.commit()
# Возвращаем ответ с суммой и ID транзакции
return jsonify({"transaction_id": transaction.id, "amount": transaction.amount, "wallets": user.wallet_address}), 201
Теперь напишем тестовый код для отправки запроса на данный маршрут.
Python: Скопировать в буфер обмена
Код:
import requests
UUID = 'd1fced81-9cb5-4843-8b7b-0887dfc35827'
# URL API
URL = 'http://127.0.0.1:5000/create_invoice'
# Данные для создания инвойса
data = {
'amount': 2.0,
}
# Заголовки для запроса
headers = {
'Authorization': UUID,
'Content-Type': 'application/json'
}
# Отправляем запрос
response = requests.post(URL, headers=headers, json=data)
print(response.json())
В общем, после отправки запроса тестовый код получит сообщение, содержащее новую сумму с сгенерированным числом, ID транзакции, кошелек, и база данных также пополнится информацией о заявке.
Проверка заявок на оплату
Заявка есть, теперь нужно как-то проверить ее статус. Для этого будет написана новая функция, также принимающая POST-запросы. Новая функция будет извлекать из базы данных сумму и кошелек.Python: Скопировать в буфер обмена
Код:
@app.route('/check_transaction', methods=['POST'])
def check_transaction():
# Извлекаем UUID из заголовков запроса
uuid = request.headers.get('Authorization')
if not uuid:
return jsonify({"error": "UUID не предоставлен"}), 400
# Ищем пользователя с этим UUID в базе данных
user = Users.query.filter_by(uuid=uuid).first()
if not user:
return jsonify({"error": "Пользователь не найден"}), 404
# Извлекаем данные из запроса и записываем в переменную
data = request.json
transaction_id = data.get('transaction_id')
if transaction_id is None:
return jsonify({"error": "ID транзакции не предоставлен"}), 400
# Ищем транзакцию по ID и user_id
transaction = Transactions.query.filter_by(id=transaction_id, user_id=user.id).first()
if not transaction:
return jsonify({"error": "Транзакция не найдена"}), 404
# Получаем сумму и адрес кошелька из данных пользователя и транзакции
amount = transaction.amount
wallet_address = user.wallet_address
# Вызываем функцию для проверки транзакции
result = check_transactions(amount, wallet_address)
# Возвращаем ответ на основе результата проверки
if result['status'] == "success":
return jsonify({"status": "success"}), 200
elif result['status'] == "not_found":
return jsonify({"status": "not_found"}), 404
elif result['status'] == "no_transactions":
return jsonify({"status": "message"}), 404
else:
return jsonify({"status": "error"}), 500
Python: Скопировать в буфер обмена
Код:
import requests
def check_transactions(amount, wallet_address):
api_key = "AH35FW35LU7OF3AAAAAH5PZIYY6APMW6PQ7EBQJC5WA7H6OG4UT7PAZWYXJV7V6WQF42YJA"
# URL для получения списка транзакций
transactions_url = f'https://testnet.tonapi.io/v2/blockchain/accounts/{wallet_address}/transactions'
# Заголовки для запроса, включая API ключ
headers = {
'X-API-KEY': api_key
}
# Отправляем GET запрос
response = requests.get(transactions_url, headers=headers)
# Если ответ положительный
if response.status_code == 200:
# Запись ответа в переменную data
data = response.json()
# Проходим по всем транзакциям и извлекаем значение
if 'transactions' in data:
for transaction in data['transactions']:
if 'in_msg' in transaction and 'value' in transaction['in_msg']:
value = transaction['in_msg']['value']
value_in_ton = value / 1000000000 # Перевод в TON
# Сравнение каждого значения с заданной суммой
if value_in_ton == amount:
return {"status": "success"}
return {"status": "not_found"}
else:
return {"status": "no_transactions"}
else:
return {"status": "error"}
Теперь можно написать тестовый код для отправки запроса на проверку транзакции.
Python: Скопировать в буфер обмена
Код:
import requests
# Замените на фактический токен аутентификации пользователя
UUID = 'd1fced81-9cb5-4843-8b7b-0887dfc35827'
# URL вашего сервера
URL = 'http://127.0.0.1:5000/check_transaction'
# Данные для создания инвойса
data = {
'transaction_id': 1,
}
# Заголовки для запроса
headers = {
'Authorization': UUID,
'Content-Type': 'application/json'
}
# Отправляем запрос
response = requests.post(URL, headers=headers, json=data)
print(response.json())
После выполнения данного кода, если вы действительно совершили транзакцию на указанную сумму, вы получите такой ответ:
По сути, с API закончено. Теперь с помощью него можно создавать заявки на оплату и проверять заявки на оплату. Но выводить деньги из сервиса API пока нельзя, и именно это мы сейчас и будем исправлять.
Отображение баланса пользователя в платежной системе
Первое, что будет сделано, — это отображение баланса кошелька. Чтобы это реализовать, я решил вставить логику проверки баланса прямо в маршрут /profile, чтобы при каждом заходе на страницу баланс обновлялся.Python: Скопировать в буфер обмена
Код:
@app.route('/profile')
def profile():
# Проверяем, что пользователь авторизован
if 'user_id' not in session:
return redirect(url_for('login'))
# Получаем данные пользователя
user = Users.query.get(session['user_id'])
# API ключ и адрес кошелька
api_key = 'AH35FW35LU7OF3AAAAAH5PZIYY6APMW6PQ7EBQJC5WA7H6OG4UT7PAZWYXJV7V6WQF42YJA'
wallet_address = user.wallet_address
balance_url = f'https://testnet.tonapi.io/v2/accounts/{wallet_address}'
# Отправляем GET-запрос на API для получения баланса
headers = {'X-API-KEY': api_key}
response = requests.get(balance_url, headers=headers)
if response.status_code == 200:
# Извлекаем баланс и преобразуем его в TON
data = response.json()
balance = data.get('balance') / 1000000000 # Преобразуем в TON
else:
balance = "Ошибка при получении баланса"
# Передаём баланс и данные пользователя на страницу
return render_template('profile.html', username=user.username, uuid=user.uuid, wallet_address=user.wallet_address,
balance=balance)
Теперь нужно дополнить веб-страницу profile. Для этого нужно лишь дописать одну строчку.
HTML: Скопировать в буфер обмена
<p>Баланс: {{ balance }} TON</p>
Вывод денег из платежной системы
С отображением баланса закончено, теперь можно реализовать перевод денег с баланса пользователя API на другой кошелек.Для начала нужно на странице создать форму с полями для ввода суммы и кошелька, куда отправлять TON.
HTML: Скопировать в буфер обмена
Код:
<form action="{{ url_for('transfer') }}" method="POST">
<label for="amount">Сумма перевода (TON):</label>
<input type="number" name="amount" step="0.0001" required>
<label for="destination_address">Адрес получателя:</label>
<input type="text" name="destination_address" required>
<button type="submit">Отправить</button>
</form>
Python: Скопировать в буфер обмена
Код:
@app.route('/transfer', methods=['POST'])
def transfer():
# Получаем данные из формы
amount = float(request.form.get('amount'))
destination_address = request.form.get('destination_address')
user_id = session.get('user_id') # Получаем user_id из сессии
# Проверка наличия user_id в сессии
if not user_id:
return jsonify({"error": "Пользователь не найден"}), 401
# Извлекаем сид-фразу пользователя из базы данных
user = Users.query.get(user_id)
seed = user.mnemonic # Сид-фраза из базы данных
# Запуск функции перевода деняг
tx_hash = asyncio.run(execute_transfer(seed, amount, destination_address))
return jsonify({"message": "Перевод выполнен", "tx_hash": tx_hash})
Python: Скопировать в буфер обмена
Код:
async def execute_transfer(seed, amount, destination_address):
client = TonapiClient(api_key=api_key, is_testnet=True)
wallet, public_key, private_key, mnemonic = WalletV3R1.from_mnemonic(client, seed)
tx_hash = await wallet.transfer(destination=destination_address, amount=amount, body="Перевод с профиля")
return tx_hash
Результат:
Вывод
На этом статья закончена. Проект получился несложным, но, как мне кажется, достаточно интересным, если вы не работали с криптовалютой. Код достаточно грязный, так как он был сделан лишь для проверки функционала. Я очень жду ваших замечаний по коду и надеюсь на помощь в исправлении багов и уязвимостей. В дальнейшем я хочу попробовать довести эту платежную систему до ума и опробовать на практике в своём продакшн-проекте. После тестов я обязательно напишу об этом в своём профиле на форуме.P.S. В данной статье я постарался писать меньше банальных объяснений, но всё же решил их указывать как комментарии в коде. Таким образом, я надеюсь, что у людей не будет гореть с каждой оплаченной мне копейки за символ (
Статья в виде документа: https://docs.google.com/document/d/1YSec2B8xz-ubvpUzF_63Sngfxo35YVPl0Sk2DCXgtjU/edit?usp=sharing
Исходники на GitHub: https://github.com/overlordgamedev/Ton-Payment-System
Сделано OverlordGameDev специально для форума XSS.IS