Гибкая система выдачи товаров через HTTP запросы к вашему API серверу
API доставка товаров позволяет выдавать купленные товары не через RCON команды Minecraft, а через HTTP запросы к вашему собственному API серверу. Это дает вам полную гибкость в обработке доставки товаров.
💡 Для чего это нужно?
Когда пользователь покупает товар с включенной API доставкой, бот выполняет следующие действия:
delivery_endpoint
⚠️ Важно: При ошибке
Если ваш сервер возвращает ошибку (не 2xx код) или не отвечает, бот автоматически повторяет запрос несколько раз с увеличивающимися задержками (см. раздел Повторные запросы).
При возврате средств (refund) или отмене заказа, бот отправляет POST запрос на ваш rollback_endpoint с теми же данными. Ваш сервер должен отменить выдачу товара.
Настройка API доставки выполняется в два простых шага:
Зайдите в админ-панель бота и найдите кнопку "🌐 Настройки API Delivery" в главном меню. Заполните все необходимые параметры:
https://your-server.com)/api/delivery)/api/rollback)💡 Удобно!
Все настройки сохраняются в файл .env автоматически. Вам не нужно редактировать конфиги вручную!
В настройках товара выберите метод доставки: API. Это всё! Товар автоматически будет использовать глобальную конфигурацию API.
products:
- id: premium_rank_api
type: donation
title: "Premium Rank (API)"
price_usd: 15.0
enabled: true
delivery_method: api # ← Использовать API доставку
delivery_identifier_type: email # Опционально: идентификатор для доставки
📝 Важно:
Все товары с delivery_method: api используют одну общую конфигурацию из админ-меню. Локальные настройки per-product больше не поддерживаются для упрощения управления.
Вы можете использовать разные методы доставки для разных товаров. Настройка API выполняется один раз через админ-меню.
products:
# Товар через API
- id: vip_privilege
title: "⭐ VIP привилегия"
type: donation
price_usd: 10.0
enabled: true
delivery_method: api # ✅ Использует глобальные настройки API
delivery_identifier_type: email
# Товар через RCON
- id: starter_kit
title: "📦 Стартовый набор"
type: donation
price_usd: 5.0
enabled: true
delivery_method: rcon # ✅ Использует RCON команды
target_servers:
- survival
rcon_commands:
- command: "give {minecraft_nick} diamond 64"
timeout: 10
retry_on_failure: true
# Товар без автоматической доставки
- id: info_product
title: "📘 Информационный товар"
type: donation
price_usd: 1.0
enabled: true
delivery_method: none # ℹ️ Без доставки
💡 Просто и понятно:
Выбираете delivery_method: api — товар использует API. Выбираете delivery_method: rcon — товар использует RCON. Никаких дополнительных флагов или локальных конфигураций!
При работе с подписками (recurring items) бот автоматически управляет жизненным циклом:
delivery запрос → Ваш сервер выдает права
delivery запрос
rollback запрос → Ваш сервер забирает права
🎮 Ваш сервер управляет правами, бот только уведомляет о событиях:
⚠️ Важно:
В запросе будет дополнительное поле subscription_renewal:
{
"delivery_id": 789,
"purchase_id": 456,
"item_id": "vip_subscription",
"item_title": "VIP подписка",
"minecraft_nick": "Player123",
"quantity": 1,
"timestamp": "2025-11-30T15:30:00Z",
"subscription_renewal": true // ← Это продление существующей подписки
}
Если subscription_renewal: true - это автоматическое продление, иначе - первая покупка.
@app.route('/api/delivery', methods=['POST'])
def delivery():
data = request.json
is_renewal = data.get('subscription_renewal', False)
minecraft_nick = data.get('minecraft_nick')
item_id = data.get('item_id')
if is_renewal:
print(f"Продление подписки {item_id} для {minecraft_nick}")
# Продлить срок действия прав
extend_player_permission(minecraft_nick, item_id, days=30)
else:
print(f"Новая подписка {item_id} для {minecraft_nick}")
# Выдать новые права
grant_player_permission(minecraft_nick, item_id, days=30)
return jsonify({"success": True}), 200
@app.route('/api/rollback', methods=['POST'])
def rollback():
data = request.json
minecraft_nick = data.get('minecraft_nick')
item_id = data.get('item_id')
print(f"Отмена подписки {item_id} для {minecraft_nick}")
# Забрать права у игрока
revoke_player_permission(minecraft_nick, item_id)
return jsonify({"success": True}), 200
⚠️ Важное уточнение про "webhook":
В конфигурации упоминаются delivery_endpoint и rollback_endpoint. Это НЕ webhook для Telegram!
Это endpoints вашего собственного API сервера, на которые бот будет отправлять POST запросы для доставки и отмены товаров.
Endpoints (эндпоинты) — это пути (URL адреса) на вашем сервере, которые обрабатывают входящие HTTP запросы:
| Endpoint | Назначение | Когда вызывается |
|---|---|---|
delivery_endpoint |
Выдача товара игроку | После успешной оплаты товара |
rollback_endpoint |
Отмена выдачи товара | При возврате средств (refund) |
Бот объединяет base_url + endpoint для получения полного адреса:
base_url = "https://your-server.com"
delivery_endpoint = "/api/delivery"
Полный URL = "https://your-server.com/api/delivery"
💡 Можно ли сделать endpoint для проверки статуса?
Да! Вы можете добавить в свой API сервер дополнительный endpoint для проверки статуса доставки, например /api/delivery/status/{delivery_id}. Бот не использует его автоматически, но вы можете вызывать его вручную для отладки или мониторинга.
POST {base_url}{delivery_endpoint}
Authorization: Bearer your_secret_token
Content-Type: application/json
{
"delivery_id": 123, // Уникальный ID доставки
"purchase_id": 456, // ID покупки в базе данных бота
"item_id": "premium_privilege", // ID товара из конфига
"item_title": "Premium привилегия", // Название товара
"minecraft_nick": "Player123", // Никнейм игрока (указан при покупке)
"quantity": 1, // Количество товара
"timestamp": "2025-10-27T15:30:00Z" // Время покупки (ISO 8601)
}
{
"success": true,
"delivery_id": 123,
"message": "Item delivered successfully"
}
✅ Успешная доставка:
Любой HTTP статус код в диапазоне 200-299 считается успехом. Тело ответа может быть любым JSON объектом — бот его не проверяет.
{
"error": "Player not found",
"delivery_id": 123
}
❌ Ошибка доставки:
Любой HTTP статус код вне диапазона 200-299 считается ошибкой. Бот автоматически повторит запрос несколько раз (см. Повторные запросы).
POST {base_url}{rollback_endpoint}
Authorization: Bearer your_secret_token
Content-Type: application/json
{
"delivery_id": 123,
"purchase_id": 456,
"item_id": "premium_privilege",
"item_title": "Premium привилегия",
"minecraft_nick": "Player123",
"quantity": 1,
"timestamp": "2025-10-27T15:30:00Z"
}
Формат запроса идентичен запросу доставки, только отправляется на другой endpoint.
{
"success": true,
"delivery_id": 123,
"message": "Rollback completed successfully"
}
Да, бот делает повторные попытки при ошибках!
Если ваш API сервер недоступен или возвращает ошибку, бот автоматически повторяет запрос доставки несколько раз с увеличивающимися задержками:
| Попытка | Задержка | Уведомление пользователю |
|---|---|---|
| 1 | Сразу | — |
| 2 | ~10 секунд | "Доставка задерживается..." |
| 3 | ~30 секунд | "Повторная попытка доставки..." |
| 4 | ~1 минута | "Доставка все еще выполняется..." |
| 5 | ~2 минуты | "Последняя попытка доставки..." |
После 5-й неудачной попытки бот прекращает попытки и уведомляет администратора о проблеме. Пользователю показывается сообщение о том, что товар будет доставлен вручную.
⚠️ Идемпотентность:
Ваш сервер должен быть готов к повторным запросам с одинаковым delivery_id. Используйте это поле для проверки, не была ли доставка уже выполнена ранее.
@app.route('/api/delivery', methods=['POST'])
def delivery():
data = request.json
delivery_id = data['delivery_id']
# Проверяем, не была ли доставка уже выполнена
if is_already_delivered(delivery_id):
return jsonify({"success": True, "message": "Already delivered"}), 200
# Выполняем доставку
perform_delivery(data)
# Сохраняем факт доставки
mark_as_delivered(delivery_id)
return jsonify({"success": True}), 200
Хотя бот не использует endpoint для проверки статуса автоматически, вы можете добавить его в свой API для мониторинга и отладки:
GET /api/delivery/status/{delivery_id}
{
"delivery_id": 123,
"status": "completed", // completed, pending, failed
"item_id": "premium_privilege",
"minecraft_nick": "Player123",
"delivered_at": "2025-10-27T15:30:15Z",
"attempts": 1,
"last_error": null
}
💡 Зачем это нужно?
from flask import Flask, request, jsonify
import time
app = Flask(__name__)
BEARER_TOKEN = "your_secret_token"
# База данных доставок (в реальности используйте PostgreSQL/MySQL)
delivered_items = {}
def check_auth():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return False
token = auth.split('Bearer ')[1]
return token == BEARER_TOKEN
@app.route('/api/delivery', methods=['POST'])
def delivery():
# Проверка авторизации
if not check_auth():
return jsonify({"error": "Unauthorized"}), 401
data = request.json
delivery_id = data.get('delivery_id')
minecraft_nick = data.get('minecraft_nick')
item_id = data.get('item_id')
quantity = data.get('quantity', 1)
# Проверка на повторный запрос (идемпотентность)
if delivery_id in delivered_items:
return jsonify({
"success": True,
"message": "Already delivered",
"delivery_id": delivery_id
}), 200
# Ваша логика выдачи товара
print(f"Delivering {item_id} x{quantity} to {minecraft_nick}")
# Например, выполните команду на сервере Minecraft через RCON
# rcon_client.execute(f"give {minecraft_nick} {item_id} {quantity}")
# Или через плагин API
# minecraft_api.grant_item(minecraft_nick, item_id, quantity)
# Сохраняем факт доставки
delivered_items[delivery_id] = {
"minecraft_nick": minecraft_nick,
"item_id": item_id,
"quantity": quantity,
"timestamp": time.time()
}
return jsonify({
"success": True,
"delivery_id": delivery_id,
"message": "Item delivered successfully"
}), 200
@app.route('/api/rollback', methods=['POST'])
def rollback():
if not check_auth():
return jsonify({"error": "Unauthorized"}), 401
data = request.json
delivery_id = data.get('delivery_id')
minecraft_nick = data.get('minecraft_nick')
item_id = data.get('item_id')
quantity = data.get('quantity', 1)
# Ваша логика отмены выдачи
print(f"Rolling back {item_id} x{quantity} from {minecraft_nick}")
# Например, заберите товар обратно
# rcon_client.execute(f"clear {minecraft_nick} {item_id} {quantity}")
# Удаляем из базы доставок
if delivery_id in delivered_items:
del delivered_items[delivery_id]
return jsonify({
"success": True,
"delivery_id": delivery_id,
"message": "Rollback completed successfully"
}), 200
@app.route('/api/delivery/status/', methods=['GET'])
def check_status(delivery_id):
if not check_auth():
return jsonify({"error": "Unauthorized"}), 401
if delivery_id in delivered_items:
item = delivered_items[delivery_id]
return jsonify({
"delivery_id": delivery_id,
"status": "completed",
"minecraft_nick": item["minecraft_nick"],
"item_id": item["item_id"],
"quantity": item["quantity"],
"delivered_at": item["timestamp"]
}), 200
else:
return jsonify({
"delivery_id": delivery_id,
"status": "not_found"
}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
const express = require('express');
const app = express();
const BEARER_TOKEN = 'your_secret_token';
const deliveredItems = {};
app.use(express.json());
// Middleware для проверки авторизации
function checkAuth(req, res, next) {
const auth = req.headers.authorization || '';
if (!auth.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = auth.substring(7);
if (token !== BEARER_TOKEN) {
return res.status(401).json({ error: 'Invalid token' });
}
next();
}
app.post('/api/delivery', checkAuth, (req, res) => {
const { delivery_id, minecraft_nick, item_id, quantity = 1 } = req.body;
// Проверка на повторный запрос (идемпотентность)
if (deliveredItems[delivery_id]) {
return res.json({
success: true,
message: 'Already delivered',
delivery_id
});
}
// Ваша логика выдачи товара
console.log(`Delivering ${item_id} x${quantity} to ${minecraft_nick}`);
// Например, выполните команду через RCON или API
// minecraftAPI.grantItem(minecraft_nick, item_id, quantity);
// Сохраняем факт доставки
deliveredItems[delivery_id] = {
minecraft_nick,
item_id,
quantity,
timestamp: Date.now()
};
res.json({
success: true,
delivery_id,
message: 'Item delivered successfully'
});
});
app.post('/api/rollback', checkAuth, (req, res) => {
const { delivery_id, minecraft_nick, item_id, quantity = 1 } = req.body;
// Ваша логика отмены выдачи
console.log(`Rolling back ${item_id} x${quantity} from ${minecraft_nick}`);
// Удаляем из базы доставок
if (deliveredItems[delivery_id]) {
delete deliveredItems[delivery_id];
}
res.json({
success: true,
delivery_id,
message: 'Rollback completed successfully'
});
});
app.get('/api/delivery/status/:delivery_id', checkAuth, (req, res) => {
const delivery_id = parseInt(req.params.delivery_id);
if (deliveredItems[delivery_id]) {
const item = deliveredItems[delivery_id];
return res.json({
delivery_id,
status: 'completed',
minecraft_nick: item.minecraft_nick,
item_id: item.item_id,
quantity: item.quantity,
delivered_at: item.timestamp
});
} else {
return res.status(404).json({
delivery_id,
status: 'not_found'
});
}
});
app.listen(5000, () => {
console.log('API server running on port 5000');
});
⚠️ Критически важные рекомендации:
Никогда не используйте HTTP в production окружении! Bearer token передается в открытом виде и может быть перехвачен.
# ❌ Небезопасно
base_url: "http://your-server.com"
# ✅ Безопасно
base_url: "https://your-server.com"
Используйте токены длиной минимум 32 символа, содержащие случайные буквы, цифры и специальные символы:
# ❌ Слабый токен
bearer_token: "12345"
# ✅ Сильный токен (пример генерации в Python)
import secrets
token = secrets.token_urlsafe(32)
# Результат: "xK8_2mP9jQ7vN4hR6wL3yS5zT1aB0cD8eF9gH2iJ4k"
config.yml с реальными токенами в Gitconfig.yml в .gitignoreВсегда проверяйте данные на стороне сервера:
@app.route('/api/delivery', methods=['POST'])
def delivery():
data = request.json
# Проверка обязательных полей
required_fields = ['delivery_id', 'item_id', 'minecraft_nick']
for field in required_fields:
if field not in data:
return jsonify({"error": f"Missing field: {field}"}), 400
# Валидация типов
if not isinstance(data['delivery_id'], int):
return jsonify({"error": "delivery_id must be integer"}), 400
# Валидация значений
if not data['minecraft_nick'].isalnum():
return jsonify({"error": "Invalid minecraft_nick"}), 400
# Продолжаем обработку...
Защитите API от DDoS атак и перебора токенов:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per hour", "50 per minute"]
)
@app.route('/api/delivery', methods=['POST'])
@limiter.limit("10 per minute") # Максимум 10 доставок в минуту
def delivery():
# ...
Сохраняйте логи для аудита и отладки:
⚠️ Не логируйте:
docs/API_DELIVERY.md и docs/example_api_item.yaml✨ Готово!
Теперь вы знаете, как работает API доставка товаров. Если у вас остались вопросы, обратитесь к исходным файлам документации или свяжитесь с разработчиком.