🎯 Обзор системы API доставки
API доставка товаров позволяет выдавать купленные товары не через RCON команды Minecraft, а через HTTP запросы к вашему собственному API серверу. Это дает вам полную гибкость в обработке доставки товаров.
💡 Для чего это нужно?
- Интеграция с собственной системой управления игровыми товарами
- Выдача товаров в играх, отличных от Minecraft
- Сложная бизнес-логика обработки покупок
- Централизованное управление доставкой через единый API
- Интеграция с существующим backend вашего проекта
✨ Возможности
- ✅ REST JSON API с Bearer token аутентификацией
- ✅ Глобальная конфигурация для всех товаров
- ✅ Переопределение настроек для отдельных товаров
- ✅ Автоматическая доставка через POST запросы
- ✅ Rollback (отмена) при возврате средств
- ✅ Автоматические повторные попытки при ошибках
- ✅ Полное логирование всех запросов
- ✅ Совместимость с RCON (можно использовать оба метода)
🔄 Как работает система доставки
Когда пользователь покупает товар с включенной API доставкой, бот выполняет следующие действия:
delivery_endpoint
⚠️ Важно: При ошибке
Если ваш сервер возвращает ошибку (не 2xx код) или не отвечает, бот автоматически повторяет запрос несколько раз с увеличивающимися задержками (см. раздел Повторные запросы).
Отмена доставки (Rollback)
При возврате средств (refund) или отмене заказа, бот отправляет POST запрос на ваш rollback_endpoint с теми же данными. Ваш сервер должен отменить выдачу товара.
⚙️ Настройка конфигурации
Глобальная конфигурация (config.yml)
Добавьте секцию api_delivery на верхнем уровне config.yml:
# Глобальная конфигурация API доставки
api_delivery:
enabled: true # Включить API доставку
base_url: "https://your-server.com" # URL вашего API сервера
bearer_token: "your_secret_token" # Секретный токен для авторизации
delivery_endpoint: "/api/delivery" # Endpoint для выдачи товаров
rollback_endpoint: "/api/rollback" # Endpoint для отмены выдачи
timeout: 30 # Таймаут запроса в секундах
Включение для товаров
В каждом товаре добавьте флаг api_delivery_enabled: true:
items:
premium_privilege:
id: "premium_privilege"
title: "Premium привилегия"
type: "privilege"
price_usd: 15.0
payment_methods: ["lava", "telegram_stars"]
enabled: true
api_delivery_enabled: true # ← Включить API доставку для этого товара
Переопределение для отдельного товара
Если нужны разные настройки для конкретного товара (другой сервер, токен, endpoints):
items:
special_item:
id: "special_item"
title: "Особый товар"
type: "privilege"
price_usd: 50.0
enabled: true
# Переопределяем настройки для этого товара
api_delivery:
enabled: true
base_url: "https://special-server.com"
bearer_token: "different_token"
delivery_endpoint: "/api/vip/deliver"
rollback_endpoint: "/api/vip/rollback"
timeout: 60
📋 Приоритет настроек:
- Собственная конфигурация товара (
api_deliveryв товаре) - Глобальная конфигурация (
api_delivery_enabled: true+ глобальная секция) - RCON доставка (если API не настроен)
🌐 Endpoints и "Webhook" в конфиге
⚠️ Важное уточнение про "webhook":
В конфигурации упоминаются delivery_endpoint и rollback_endpoint. Это НЕ webhook для Telegram!
Это endpoints вашего собственного API сервера, на которые бот будет отправлять POST запросы для доставки и отмены товаров.
Что такое endpoints?
Endpoints (эндпоинты) — это пути (URL адреса) на вашем сервере, которые обрабатывают входящие HTTP запросы:
| Endpoint | Назначение | Когда вызывается |
|---|---|---|
delivery_endpoint |
Выдача товара игроку | После успешной оплаты товара |
rollback_endpoint |
Отмена выдачи товара | При возврате средств (refund) |
Как формируется полный URL?
Бот объединяет 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}. Бот не использует его автоматически, но вы можете вызывать его вручную для отладки или мониторинга.
📨 Примеры запросов и ответов
Запрос на доставку (Delivery)
POST {base_url}{delivery_endpoint}
Headers:
Authorization: Bearer your_secret_token
Content-Type: application/json
Request Body (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)
}
Успешный Response (HTTP 200):
{
"success": true,
"delivery_id": 123,
"message": "Item delivered successfully"
}
✅ Успешная доставка:
Любой HTTP статус код в диапазоне 200-299 считается успехом. Тело ответа может быть любым JSON объектом — бот его не проверяет.
Ошибка Response (HTTP 400/500/etc):
{
"error": "Player not found",
"delivery_id": 123
}
❌ Ошибка доставки:
Любой HTTP статус код вне диапазона 200-299 считается ошибкой. Бот автоматически повторит запрос несколько раз (см. Повторные запросы).
Запрос на отмену (Rollback)
POST {base_url}{rollback_endpoint}
Headers:
Authorization: Bearer your_secret_token
Content-Type: application/json
Request Body (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.
Успешный Response (HTTP 200):
{
"success": true,
"delivery_id": 123,
"message": "Rollback completed successfully"
}
🔁 Логика повторных запросов
Да, бот делает повторные попытки при ошибках!
Если ваш API сервер недоступен или возвращает ошибку, бот автоматически повторяет запрос доставки несколько раз с увеличивающимися задержками:
| Попытка | Задержка | Уведомление пользователю |
|---|---|---|
| 1 | Сразу | — |
| 2 | ~10 секунд | "Доставка задерживается..." |
| 3 | ~30 секунд | "Повторная попытка доставки..." |
| 4 | ~1 минута | "Доставка все еще выполняется..." |
| 5 | ~2 минуты | "Последняя попытка доставки..." |
После 5-й неудачной попытки бот прекращает попытки и уведомляет администратора о проблеме. Пользователю показывается сообщение о том, что товар будет доставлен вручную.
Когда происходит retry?
- ❌ Сервер недоступен (timeout, connection refused)
- ❌ HTTP статус код не 2xx (400, 500, 503, и т.д.)
- ❌ Неверный формат ответа (не JSON)
Когда retry НЕ происходит?
- ✅ HTTP 200-299 (успех)
⚠️ Идемпотентность:
Ваш сервер должен быть готов к повторным запросам с одинаковым 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}
Response:
{
"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
}
Возможные статусы:
- pending — Доставка в процессе
- completed — Доставка успешно выполнена
- failed — Доставка не удалась после всех попыток
💡 Зачем это нужно?
- Мониторинг доставок в реальном времени
- Отладка проблем с доставкой
- Ручная проверка статуса конкретной покупки
- Построение отчетов и аналитики
💻 Примеры реализации серверов
Python (Flask)
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)
Node.js (Express)
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');
});
🔐 Безопасность
⚠️ Критически важные рекомендации:
1. Используйте HTTPS в продакшене
Никогда не используйте HTTP в production окружении! Bearer token передается в открытом виде и может быть перехвачен.
# ❌ Небезопасно
base_url: "http://your-server.com"
# ✅ Безопасно
base_url: "https://your-server.com"
2. Генерируйте сильные токены
Используйте токены длиной минимум 32 символа, содержащие случайные буквы, цифры и специальные символы:
# ❌ Слабый токен
bearer_token: "12345"
# ✅ Сильный токен (пример генерации в Python)
import secrets
token = secrets.token_urlsafe(32)
# Результат: "xK8_2mP9jQ7vN4hR6wL3yS5zT1aB0cD8eF9gH2iJ4k"
3. Храните токены в безопасности
- ❌ Не коммитьте
config.ymlс реальными токенами в Git - ✅ Используйте переменные окружения или секретные файлы
- ✅ Добавьте
config.ymlв.gitignore
4. Валидируйте входящие данные
Всегда проверяйте данные на стороне сервера:
@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
# Продолжаем обработку...
5. Ограничьте rate limiting
Защитите 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():
# ...
6. Логируйте все запросы
Сохраняйте логи для аудита и отладки:
- Timestamp запроса
- IP адрес клиента
- Данные о доставке (без чувствительной информации)
- Результат операции (успех/ошибка)
⚠️ Не логируйте:
- Bearer токены
- Пароли или другие секреты
- Полные данные платежных систем
📚 Дополнительные ресурсы
- ← Вернуться к основной документации
- Исходные файлы:
docs/API_DELIVERY.mdиdocs/example_api_item.yaml
✨ Готово!
Теперь вы знаете, как работает API доставка товаров. Если у вас остались вопросы, обратитесь к исходным файлам документации или свяжитесь с разработчиком.