# Telegram Mini Apps: создание полноценных приложений внутри мессенджера
Привет! Меня зовут Алексей, и я уже 2 года разрабатываю Telegram Mini Apps для различных проектов. За это время я создал более 30 мини-приложений, от простых игр до сложных e-commerce платформ, и готов поделиться всем опытом. В этой статье расскажу, как создать мощное Mini App, какие возможности открывает эта технология, и как монетизировать такие приложения.
## Что такое Telegram Mini Apps и почему они революционны?
### Статистика Mini Apps
- **900+ миллионов** пользователей Telegram по всему миру
- **50+ миллионов** активных пользователей Mini Apps
- **$2.1 миллиарда** объем транзакций через Telegram Stars
- **300%** рост популярности Mini Apps за последний год
### Преимущества Mini Apps
- **Нет установки** - работают прямо в мессенджере
- **Быстрый запуск** - мгновенный доступ к функциональности
- **Интеграция с Telegram** - доступ к контактам, геолокации, платежам
- **Кроссплатформенность** - работают на всех устройствах
- **Простая монетизация** - встроенные платежи через Telegram Stars
## Архитектура Telegram Mini Apps
### 1. 🏗️ Базовая структура проекта
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Mini App</title>
<!-- Telegram WebApp SDK -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<!-- Стили -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<!-- Основной контент приложения -->
<header class="app-header">
<h1>Мое Mini App</h1>
<button id="theme-toggle">🌙</button>
</header>
<main class="app-main">
<div id="content">
<!-- Динамический контент -->
</div>
</main>
<footer class="app-footer">
<button id="main-button" class="main-button">Главная кнопка</button>
</footer>
</div>
<!-- JavaScript -->
<script src="app.js"></script>
</body>
</html>
```
### 2. 🎨 CSS стили для Mini App
```css
/* styles.css */
:root {
--tg-theme-bg-color: #ffffff;
--tg-theme-text-color: #000000;
--tg-theme-hint-color: #999999;
--tg-theme-link-color: #2481cc;
--tg-theme-button-color: #2481cc;
--tg-theme-button-text-color: #ffffff;
--tg-theme-secondary-bg-color: #f1f1f1;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--tg-theme-bg-color);
color: var(--tg-theme-text-color);
height: 100vh;
overflow-x: hidden;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
}
.app-header {
background-color: var(--tg-theme-secondary-bg-color);
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.app-header h1 {
font-size: 18px;
font-weight: 600;
}
#theme-toggle {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: background-color 0.2s;
}
#theme-toggle:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.app-main {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.app-footer {
padding: 16px;
background-color: var(--tg-theme-secondary-bg-color);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.main-button {
width: 100%;
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.main-button:hover {
opacity: 0.9;
}
.main-button:active {
opacity: 0.8;
}
/* Компоненты */
.card {
background-color: var(--tg-theme-bg-color);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.card-content {
font-size: 14px;
line-height: 1.4;
color: var(--tg-theme-hint-color);
}
.button {
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
margin: 4px;
}
.button:hover {
opacity: 0.9;
}
.button.secondary {
background-color: var(--tg-theme-secondary-bg-color);
color: var(--tg-theme-text-color);
}
.input {
width: 100%;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 8px;
font-size: 16px;
background-color: var(--tg-theme-bg-color);
color: var(--tg-theme-text-color);
margin-bottom: 12px;
}
.input:focus {
outline: none;
border-color: var(--tg-theme-button-color);
}
/* Адаптивность */
@media (max-width: 480px) {
.app-header {
padding: 12px;
}
.app-main {
padding: 12px;
}
.app-footer {
padding: 12px;
}
.card {
padding: 12px;
}
}
/* Анимации */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
```
### 3. ⚡ JavaScript функциональность
```javascript
// app.js
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.user = null;
this.initData = null;
this.isDarkTheme = false;
this.init();
}
init() {
// Инициализация Telegram WebApp
this.tg.ready();
// Получаем данные пользователя
this.user = this.tg.initDataUnsafe.user;
this.initData = this.tg.initData;
// Настраиваем тему
this.setupTheme();
// Настраиваем главную кнопку
this.setupMainButton();
// Настраиваем обработчики событий
this.setupEventHandlers();
// Загружаем контент
this.loadContent();
console.log('Mini App initialized:', {
user: this.user,
platform: this.tg.platform,
version: this.tg.version
});
}
setupTheme() {
// Применяем тему Telegram
document.body.style.backgroundColor = this.tg.themeParams.bg_color;
document.body.style.color = this.tg.themeParams.text_color;
// Обновляем CSS переменные
const root = document.documentElement;
root.style.setProperty('--tg-theme-bg-color', this.tg.themeParams.bg_color);
root.style.setProperty('--tg-theme-text-color', this.tg.themeParams.text_color);
root.style.setProperty('--tg-theme-hint-color', this.tg.themeParams.hint_color);
root.style.setProperty('--tg-theme-link-color', this.tg.themeParams.link_color);
root.style.setProperty('--tg-theme-button-color', this.tg.themeParams.button_color);
root.style.setProperty('--tg-theme-button-text-color', this.tg.themeParams.button_text_color);
root.style.setProperty('--tg-theme-secondary-bg-color', this.tg.themeParams.secondary_bg_color);
}
setupMainButton() {
// Настраиваем главную кнопку
this.tg.MainButton.setText('Купить за 100 ⭐');
this.tg.MainButton.color = this.tg.themeParams.button_color;
this.tg.MainButton.textColor = this.tg.themeParams.button_text_color;
// Обработчик клика
this.tg.MainButton.onClick(() => {
this.handlePurchase();
});
// Показываем кнопку
this.tg.MainButton.show();
}
setupEventHandlers() {
// Обработчик изменения темы
this.tg.onEvent('themeChanged', () => {
this.setupTheme();
});
// Обработчик изменения размера окна
this.tg.onEvent('viewportChanged', () => {
this.handleViewportChange();
});
// Обработчик кнопки "Назад"
this.tg.BackButton.onClick(() => {
this.handleBackButton();
});
// Обработчик закрытия приложения
this.tg.onEvent('close', () => {
this.handleClose();
});
}
loadContent() {
const content = document.getElementById('content');
// Показываем приветствие
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">👋 Добро пожаловать!</div>
<div class="card-content">
Привет, ${this.user?.first_name || 'Пользователь'}!
Это ваше первое Mini App в Telegram.
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игры</div>
<div class="card-content">
Выберите игру для игры:
</div>
<button class="button" onclick="miniApp.startGame('snake')">🐍 Змейка</button>
<button class="button" onclick="miniApp.startGame('tetris')">🧩 Тетрис</button>
<button class="button" onclick="miniApp.startGame('quiz')">❓ Викторина</button>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Покупайте товары за Telegram Stars:
</div>
<button class="button" onclick="miniApp.showShop()">Открыть магазин</button>
</div>
<div class="card fade-in">
<div class="card-title">📊 Статистика</div>
<div class="card-content">
Ваша статистика использования:
</div>
<button class="button secondary" onclick="miniApp.showStats()">Показать статистику</button>
</div>
`;
}
startGame(gameType) {
// Скрываем главную кнопку
this.tg.MainButton.hide();
// Показываем кнопку "Назад"
this.tg.BackButton.show();
// Загружаем игру
this.loadGame(gameType);
}
loadGame(gameType) {
const content = document.getElementById('content');
switch(gameType) {
case 'snake':
content.innerHTML = this.getSnakeGameHTML();
this.initSnakeGame();
break;
case 'tetris':
content.innerHTML = this.getTetrisGameHTML();
this.initTetrisGame();
break;
case 'quiz':
content.innerHTML = this.getQuizGameHTML();
this.initQuizGame();
break;
}
}
getSnakeGameHTML() {
return `
<div class="card">
<div class="card-title">🐍 Змейка</div>
<div class="card-content">
Счет: <span id="score">0</span>
</div>
<canvas id="gameCanvas" width="300" height="300" style="border: 1px solid #ccc; display: block; margin: 0 auto;"></canvas>
<div style="text-align: center; margin-top: 10px;">
<button class="button" onclick="miniApp.pauseGame()">Пауза</button>
<button class="button" onclick="miniApp.restartGame()">Заново</button>
</div>
</div>
`;
}
initSnakeGame() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let snake = [{x: 150, y: 150}];
let direction = {x: 0, y: 0};
let food = {x: 200, y: 200};
let score = 0;
let gameRunning = true;
const gameLoop = () => {
if (!gameRunning) return;
// Очищаем canvas
ctx.fillStyle = this.tg.themeParams.bg_color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Рисуем змейку
ctx.fillStyle = this.tg.themeParams.button_color;
snake.forEach(segment => {
ctx.fillRect(segment.x, segment.y, 10, 10);
});
// Рисуем еду
ctx.fillStyle = '#ff4444';
ctx.fillRect(food.x, food.y, 10, 10);
// Двигаем змейку
const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
// Проверяем столкновения
if (head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height) {
this.gameOver();
return;
}
if (snake.some(segment => segment.x === head.x && segment.y === head.y)) {
this.gameOver();
return;
}
snake.unshift(head);
// Проверяем поедание еды
if (head.x === food.x && head.y === food.y) {
score++;
document.getElementById('score').textContent = score;
this.generateFood();
} else {
snake.pop();
}
setTimeout(gameLoop, 150);
};
// Обработка клавиш
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp':
if (direction.y === 0) direction = {x: 0, y: -10};
break;
case 'ArrowDown':
if (direction.y === 0) direction = {x: 0, y: 10};
break;
case 'ArrowLeft':
if (direction.x === 0) direction = {x: -10, y: 0};
break;
case 'ArrowRight':
if (direction.x === 0) direction = {x: 10, y: 0};
break;
}
});
// Начинаем игру
gameLoop();
// Сохраняем ссылки для управления
this.gameLoop = gameLoop;
this.gameRunning = gameRunning;
}
generateFood() {
const canvas = document.getElementById('gameCanvas');
const food = {
x: Math.floor(Math.random() * (canvas.width / 10)) * 10,
y: Math.floor(Math.random() * (canvas.height / 10)) * 10
};
// Обновляем еду в DOM (упрощенная версия)
const foodElement = document.querySelector('#gameCanvas');
// Здесь должна быть логика обновления позиции еды
}
pauseGame() {
this.gameRunning = !this.gameRunning;
if (this.gameRunning) {
this.gameLoop();
}
}
restartGame() {
this.gameRunning = true;
this.initSnakeGame();
}
gameOver() {
this.gameRunning = false;
// Показываем результат
this.tg.showAlert('Игра окончена! Ваш счет: ' + document.getElementById('score').textContent);
// Отправляем результат в Telegram
this.tg.sendData(JSON.stringify({
type: 'game_result',
game: 'snake',
score: document.getElementById('score').textContent
}));
}
showShop() {
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Премиум игры</div>
<div class="card-content">
Разблокируйте все игры за Telegram Stars
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_games', 50)">Купить за 50 ⭐</button>
</div>
<div class="card fade-in">
<div class="card-title">🎨 Темы</div>
<div class="card-content">
Персонализируйте внешний вид приложения
</div>
<button class="button" onclick="miniApp.purchaseItem('themes', 25)">Купить за 25 ⭐</button>
</div>
<div class="card fade-in">
<div class="card-title">💎 Премиум функции</div>
<div class="card-content">
Получите доступ к расширенным возможностям
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_features', 100)">Купить за 100 ⭐</button>
</div>
`;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
purchaseItem(itemId, price) {
// Показываем подтверждение покупки
this.tg.showConfirm(`Купить ${itemId} за ${price} ⭐?`, (confirmed) => {
if (confirmed) {
this.processPurchase(itemId, price);
}
});
}
processPurchase(itemId, price) {
// Создаем инвойс для Telegram Stars
const invoice = {
title: `Покупка: ${itemId}`,
description: `Получите доступ к ${itemId}`,
payload: JSON.stringify({
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}),
provider_token: '', // Для Telegram Stars не нужен
currency: 'XTR', // Telegram Stars
prices: [{
label: itemId,
amount: price * 100 // В копейках
}]
};
// Открываем платежную форму
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPurchase(itemId);
} else {
this.tg.showAlert('Покупка отменена');
}
});
}
handleSuccessfulPurchase(itemId) {
// Обрабатываем успешную покупку
this.tg.showAlert('Покупка успешна! Спасибо!');
// Отправляем данные о покупке
this.tg.sendData(JSON.stringify({
type: 'purchase',
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}));
// Разблокируем функции
this.unlockFeatures(itemId);
}
unlockFeatures(itemId) {
// Логика разблокировки функций
switch(itemId) {
case 'premium_games':
// Разблокируем премиум игры
break;
case 'themes':
// Разблокируем темы
break;
case 'premium_features':
// Разблокируем премиум функции
break;
}
}
showStats() {
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">📊 Ваша статистика</div>
<div class="card-content">
<p>👤 Пользователь: ${this.user?.first_name} ${this.user?.last_name || ''}</p>
<p>🆔 ID: ${this.user?.id}</p>
<p>🌐 Язык: ${this.user?.language_code}</p>
<p>📱 Платформа: ${this.tg.platform}</p>
<p>📅 Дата регистрации: ${new Date(this.user?.id * 1000).toLocaleDateString()}</p>
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игровая статистика</div>
<div class="card-content">
<p>🐍 Змейка: Лучший счет - 0</p>
<p>🧩 Тетрис: Лучший счет - 0</p>
<p>❓ Викторина: Правильных ответов - 0</p>
</div>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Покупки</div>
<div class="card-content">
<p>💎 Потрачено Stars: 0</p>
<p>📦 Куплено товаров: 0</p>
</div>
</div>
`;
}
handleBackButton() {
// Возвращаемся к главному меню
this.tg.BackButton.hide();
this.tg.MainButton.show();
this.loadContent();
}
handleViewportChange() {
// Обрабатываем изменение размера окна
console.log('Viewport changed:', this.tg.viewportHeight, this.tg.viewportStableHeight);
}
handleClose() {
// Обрабатываем закрытие приложения
console.log('App is closing');
// Отправляем финальные данные
this.tg.sendData(JSON.stringify({
type: 'app_close',
user_id: this.user.id,
timestamp: Date.now()
}));
}
handlePurchase() {
// Обработка покупки через главную кнопку
this.purchaseItem('premium_package', 100);
}
}
// Инициализация приложения
let miniApp;
document.addEventListener('DOMContentLoaded', () => {
miniApp = new TelegramMiniApp();
});
```
## Интеграция с внешними API
### 1. 🔌 Работа с бэкендом
```javascript
class BackendAPI {
constructor() {
this.baseUrl = 'https://your-api.com/api';
this.tg = window.Telegram.WebApp;
}
async makeRequest(endpoint, method = 'GET', data = null) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': this.tg.initData
};
const options = {
method,
headers
};
if (data && method !== 'GET') {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
this.tg.showAlert('Ошибка соединения с сервером');
throw error;
}
}
async getUserData() {
return await this.makeRequest('/user/profile');
}
async updateUserData(data) {
return await this.makeRequest('/user/profile', 'PUT', data);
}
async getGameLeaderboard(gameType) {
return await this.makeRequest(`/games/${gameType}/leaderboard`);
}
async submitGameScore(gameType, score) {
return await this.makeRequest('/games/score', 'POST', {
game_type: gameType,
score: score
});
}
async getShopItems() {
return await this.makeRequest('/shop/items');
}
async purchaseItem(itemId) {
return await this.makeRequest('/shop/purchase', 'POST', {
item_id: itemId
});
}
}
// Интеграция с основным классом
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.user = null;
this.init();
}
async init() {
this.tg.ready();
this.user = this.tg.initDataUnsafe.user;
try {
// Загружаем данные пользователя с сервера
const userData = await this.api.getUserData();
this.userData = userData;
this.setupTheme();
this.setupMainButton();
this.setupEventHandlers();
this.loadContent();
} catch (error) {
console.error('Failed to initialize app:', error);
this.showError('Не удалось загрузить данные приложения');
}
}
async loadGameLeaderboard(gameType) {
try {
const leaderboard = await this.api.getGameLeaderboard(gameType);
this.displayLeaderboard(leaderboard);
} catch (error) {
this.tg.showAlert('Не удалось загрузить таблицу лидеров');
}
}
async submitScore(gameType, score) {
try {
await this.api.submitGameScore(gameType, score);
this.tg.showAlert(`Счет ${score} сохранен!`);
} catch (error) {
this.tg.showAlert('Не удалось сохранить счет');
}
}
displayLeaderboard(leaderboard) {
const content = document.getElementById('content');
let leaderboardHTML = `
<div class="card fade-in">
<div class="card-title">🏆 Таблица лидеров</div>
<div class="card-content">
`;
leaderboard.forEach((entry, index) => {
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🏅';
leaderboardHTML += `
<div style="display: flex; justify-content: space-between; margin: 8px 0;">
<span>${medal} ${entry.username}</span>
<span>${entry.score}</span>
</div>
`;
});
leaderboardHTML += `
</div>
<button class="button" onclick="miniApp.loadContent()">Назад</button>
</div>
`;
content.innerHTML = leaderboardHTML;
}
}
```
### 2. 📊 Аналитика и метрики
```javascript
class Analytics {
constructor() {
this.tg = window.Telegram.WebApp;
this.events = [];
this.sessionStart = Date.now();
}
track(eventName, properties = {}) {
const event = {
name: eventName,
properties: {
...properties,
user_id: this.tg.initDataUnsafe.user?.id,
platform: this.tg.platform,
version: this.tg.version,
timestamp: Date.now(),
session_id: this.getSessionId()
}
};
this.events.push(event);
// Отправляем событие на сервер
this.sendEvent(event);
console.log('Analytics event:', event);
}
getSessionId() {
let sessionId = localStorage.getItem('session_id');
if (!sessionId) {
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('session_id', sessionId);
}
return sessionId;
}
async sendEvent(event) {
try {
await fetch('https://your-analytics-api.com/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': this.tg.initData
},
body: JSON.stringify(event)
});
} catch (error) {
console.error('Failed to send analytics event:', error);
}
}
trackPageView(pageName) {
this.track('page_view', {
page_name: pageName
});
}
trackGameStart(gameType) {
this.track('game_start', {
game_type: gameType
});
}
trackGameEnd(gameType, score, duration) {
this.track('game_end', {
game_type: gameType,
score: score,
duration: duration
});
}
trackPurchase(itemId, price) {
this.track('purchase', {
item_id: itemId,
price: price,
currency: 'XTR'
});
}
trackButtonClick(buttonName, location) {
this.track('button_click', {
button_name: buttonName,
location: location
});
}
}
// Интеграция аналитики
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.analytics = new Analytics();
this.user = null;
this.init();
}
async init() {
this.tg.ready();
this.user = this.tg.initDataUnsafe.user;
// Отслеживаем запуск приложения
this.analytics.track('app_start', {
user_id: this.user?.id,
platform: this.tg.platform,
version: this.tg.version
});
this.setupTheme();
this.setupMainButton();
this.setupEventHandlers();
this.loadContent();
}
loadContent() {
this.analytics.trackPageView('main_menu');
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">👋 Добро пожаловать!</div>
<div class="card-content">
Привет, ${this.user?.first_name || 'Пользователь'}!
Это ваше первое Mini App в Telegram.
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игры</div>
<div class="card-content">
Выберите игру для игры:
</div>
<button class="button" onclick="miniApp.startGame('snake')">🐍 Змейка</button>
<button class="button" onclick="miniApp.startGame('tetris')">🧩 Тетрис</button>
<button class="button" onclick="miniApp.startGame('quiz')">❓ Викторина</button>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Покупайте товары за Telegram Stars:
</div>
<button class="button" onclick="miniApp.showShop()">Открыть магазин</button>
</div>
`;
}
startGame(gameType) {
this.analytics.trackGameStart(gameType);
this.analytics.trackButtonClick('start_game', 'main_menu');
// Скрываем главную кнопку
this.tg.MainButton.hide();
// Показываем кнопку "Назад"
this.tg.BackButton.show();
// Загружаем игру
this.loadGame(gameType);
}
showShop() {
this.analytics.trackPageView('shop');
this.analytics.trackButtonClick('open_shop', 'main_menu');
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Премиум игры</div>
<div class="card-content">
Разблокируйте все игры за Telegram Stars
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_games', 50)">Купить за 50 ⭐</button>
</div>
`;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
purchaseItem(itemId, price) {
this.analytics.trackButtonClick('purchase_item', 'shop');
// Показываем подтверждение покупки
this.tg.showConfirm(`Купить ${itemId} за ${price} ⭐?`, (confirmed) => {
if (confirmed) {
this.processPurchase(itemId, price);
}
});
}
processPurchase(itemId, price) {
// Создаем инвойс для Telegram Stars
const invoice = {
title: `Покупка: ${itemId}`,
description: `Получите доступ к ${itemId}`,
payload: JSON.stringify({
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}),
provider_token: '',
currency: 'XTR',
prices: [{
label: itemId,
amount: price * 100
}]
};
// Открываем платежную форму
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPurchase(itemId, price);
} else {
this.tg.showAlert('Покупка отменена');
}
});
}
handleSuccessfulPurchase(itemId, price) {
// Отслеживаем успешную покупку
this.analytics.trackPurchase(itemId, price);
// Обрабатываем покупку
this.tg.showAlert('Покупка успешна! Спасибо!');
// Отправляем данные о покупке
this.tg.sendData(JSON.stringify({
type: 'purchase',
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}));
// Разблокируем функции
this.unlockFeatures(itemId);
}
}
```
## Монетизация Mini Apps
### 1. 💰 Telegram Stars интеграция
```javascript
class PaymentManager {
constructor() {
this.tg = window.Telegram.WebApp;
this.purchases = [];
}
async createInvoice(item) {
const invoice = {
title: item.title,
description: item.description,
payload: JSON.stringify({
item_id: item.id,
user_id: this.tg.initDataUnsafe.user.id,
timestamp: Date.now()
}),
provider_token: '', // Для Telegram Stars не нужен
currency: 'XTR', // Telegram Stars
prices: [{
label: item.title,
amount: item.price * 100 // В копейках
}]
};
return invoice;
}
async processPayment(item) {
try {
const invoice = await this.createInvoice(item);
return new Promise((resolve, reject) => {
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPayment(item);
resolve({ success: true, item: item });
} else {
reject({ success: false, reason: 'payment_cancelled' });
}
});
});
} catch (error) {
console.error('Payment processing failed:', error);
throw error;
}
}
handleSuccessfulPayment(item) {
// Сохраняем информацию о покупке
this.purchases.push({
item_id: item.id,
timestamp: Date.now(),
price: item.price
});
// Отправляем данные на сервер
this.tg.sendData(JSON.stringify({
type: 'purchase_completed',
item_id: item.id,
user_id: this.tg.initDataUnsafe.user.id,
timestamp: Date.now()
}));
// Показываем уведомление
this.tg.showAlert(`Покупка "${item.title}" успешна!`);
}
async getPurchaseHistory() {
// Получаем историю покупок с сервера
try {
const response = await fetch('/api/purchases', {
headers: {
'X-Telegram-Init-Data': this.tg.initData
}
});
return await response.json();
} catch (error) {
console.error('Failed to get purchase history:', error);
return [];
}
}
hasPurchased(itemId) {
return this.purchases.some(purchase => purchase.item_id === itemId);
}
}
// Интеграция с основным приложением
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.analytics = new Analytics();
this.paymentManager = new PaymentManager();
this.user = null;
this.init();
}
async showShop() {
this.analytics.trackPageView('shop');
const content = document.getElementById('content');
// Получаем товары с сервера
const shopItems = await this.api.getShopItems();
let shopHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
`;
shopItems.forEach(item => {
const isPurchased = this.paymentManager.hasPurchased(item.id);
const buttonText = isPurchased ? '✅ Куплено' : `Купить за ${item.price} ⭐`;
const buttonDisabled = isPurchased ? 'disabled' : '';
shopHTML += `
<div class="card fade-in">
<div class="card-title">${item.title}</div>
<div class="card-content">
${item.description}
</div>
<button class="button ${buttonDisabled}"
onclick="miniApp.purchaseItem('${item.id}')"
${buttonDisabled}>
${buttonText}
</button>
</div>
`;
});
content.innerHTML = shopHTML;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
async purchaseItem(itemId) {
try {
const shopItems = await this.api.getShopItems();
const item = shopItems.find(i => i.id === itemId);
if (!item) {
this.tg.showAlert('Товар не найден');
return;
}
if (this.paymentManager.hasPurchased(itemId)) {
this.tg.showAlert('Вы уже купили этот товар');
return;
}
// Показываем подтверждение
this.tg.showConfirm(`Купить "${item.title}" за ${item.price} ⭐?`, async (confirmed) => {
if (confirmed) {
try {
await this.paymentManager.processPayment(item);
this.showShop(); // Обновляем магазин
} catch (error) {
this.tg.showAlert('Ошибка при обработке платежа');
}
}
});
} catch (error) {
console.error('Purchase failed:', error);
this.tg.showAlert('Не удалось загрузить информацию о товаре');
}
}
}
```
### 2. 🎯 Стратегии монетизации
```javascript
class MonetizationStrategy {
constructor() {
this.tg = window.Telegram.WebApp;
this.userLevel = 1;
this.userExperience = 0;
this.premiumFeatures = new Set();
}
// Freemium модель
async implementFreemiumModel() {
const freeFeatures = [
'basic_games',
'limited_themes',
'basic_analytics'
];
const premiumFeatures = [
'unlimited_games',
'all_themes',
'advanced_analytics',
'priority_support',
'exclusive_content'
];
return {
free: freeFeatures,
premium: premiumFeatures
};
}
// Подписка
async createSubscriptionPlan(planType) {
const plans = {
'monthly': {
title: 'Месячная подписка',
price: 100, // Stars
duration: 30, // дней
features: ['unlimited_games', 'all_themes', 'advanced_analytics']
},
'yearly': {
title: 'Годовая подписка',
price: 1000, // Stars
duration: 365, // дней
features: ['unlimited_games', 'all_themes', 'advanced_analytics', 'exclusive_content']
}
};
return plans[planType];
}
// Система достижений и наград
async implementAchievementSystem() {
const achievements = [
{
id: 'first_game',
title: 'Первая игра',
description: 'Сыграйте в первую игру',
reward: 10, // Stars
condition: (userData) => userData.gamesPlayed >= 1
},
{
id: 'high_score',
title: 'Высокий счет',
description: 'Наберите 1000 очков в любой игре',
reward: 25, // Stars
condition: (userData) => userData.bestScore >= 1000
},
{
id: 'daily_player',
title: 'Ежедневный игрок',
description: 'Играйте 7 дней подряд',
reward: 50, // Stars
condition: (userData) => userData.consecutiveDays >= 7
}
];
return achievements;
}
// Система рефералов
async implementReferralSystem() {
const referralReward = 25; // Stars за каждого реферала
return {
reward: referralReward,
maxReferrals: 10, // Максимум рефералов для получения награды
totalReward: referralReward * 10
};
}
// Система уровней
async calculateUserLevel(experience) {
const levels = [
{ level: 1, expRequired: 0 },
{ level: 2, expRequired: 100 },
{ level: 3, expRequired: 250 },
{ level: 4, expRequired: 500 },
{ level: 5, expRequired: 1000 },
{ level: 6, expRequired: 2000 },
{ level: 7, expRequired: 4000 },
{ level: 8, expRequired: 8000 },
{ level: 9, expRequired: 16000 },
{ level: 10, expRequired: 32000 }
];
for (let i = levels.length - 1; i >= 0; i--) {
if (experience >= levels[i].expRequired) {
return levels[i];
}
}
return levels[0];
}
// Система ежедневных наград
async implementDailyRewards() {
const dailyRewards = [
{ day: 1, reward: 5, type: 'stars' },
{ day: 2, reward: 10, type: 'stars' },
{ day: 3, reward: 15, type: 'stars' },
{ day: 4, reward: 20, type: 'stars' },
{ day: 5, reward: 25, type: 'stars' },
{ day: 6, reward: 30, type: 'stars' },
{ day: 7, reward: 50, type: 'stars' }
];
return dailyRewards;
}
}
```
## Развертывание и хостинг
### 1. 🚀 Настройка сервера
```javascript
// server.js (Node.js + Express)
const express = require('express');
const cors = require('cors');
const crypto = require('crypto');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// Валидация Telegram WebApp данных
function validateTelegramData(initData, botToken) {
const urlParams = new URLSearchParams(initData);
const hash = urlParams.get('hash');
urlParams.delete('hash');
const dataCheckString = Array.from(urlParams.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join('\n');
const secretKey = crypto.createHmac('sha256', 'WebAppData').update(botToken).digest();
const calculatedHash = crypto.createHmac('sha256', secretKey).update(dataCheckString).digest('hex');
return calculatedHash === hash;
}
// API маршруты
app.post('/api/user/profile', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
// Получаем данные пользователя
const userData = JSON.parse(new URLSearchParams(initData).get('user'));
// Здесь должна быть логика получения данных пользователя из БД
res.json({
user_id: userData.id,
username: userData.username,
first_name: userData.first_name,
last_name: userData.last_name,
level: 1,
experience: 0,
stars: 0,
purchases: []
});
});
app.post('/api/games/score', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
const { game_type, score } = req.body;
// Здесь должна быть логика сохранения счета в БД
res.json({ success: true, score: score });
});
app.get('/api/games/:gameType/leaderboard', (req, res) => {
const { gameType } = req.params;
// Здесь должна быть логика получения таблицы лидеров из БД
const leaderboard = [
{ username: 'Player1', score: 1500 },
{ username: 'Player2', score: 1200 },
{ username: 'Player3', score: 1000 }
];
res.json(leaderboard);
});
app.get('/api/shop/items', (req, res) => {
const items = [
{
id: 'premium_games',
title: 'Премиум игры',
description: 'Разблокируйте все игры',
price: 50
},
{
id: 'themes',
title: 'Темы',
description: 'Персонализируйте внешний вид',
price: 25
},
{
id: 'premium_features',
title: 'Премиум функции',
description: 'Расширенные возможности',
price: 100
}
];
res.json(items);
});
app.post('/api/shop/purchase', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
const { item_id } = req.body;
// Здесь должна быть логика обработки покупки
res.json({ success: true, item_id: item_id });
});
// Статические файлы
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
```
### 2. 🐳 Docker конфигурация
```dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Копируем package.json и package-lock.json
COPY package*.json ./
# Устанавливаем зависимости
RUN npm ci --only=production
# Копируем исходный код
COPY . .
# Создаем пользователя для безопасности
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Меняем владельца файлов
RUN chown -R nextjs:nodejs /app
USER nextjs
# Открываем порт
EXPOSE 3000
# Запускаем приложение
CMD ["npm", "start"]
```
```yaml
# docker-compose.yml
version: '3.8'
services:
mini-app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- BOT_TOKEN=${BOT_TOKEN}
- DATABASE_URL=${DATABASE_URL}
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- mini-app
volumes:
postgres_data:
redis_data:
```
## Заключение
Telegram Mini Apps - это революционная технология, которая открывает новые возможности для разработчиков. За 2 года работы я понял, что:
### 🎯 **Главные преимущества Mini Apps:**
1. **Мгновенный доступ** - нет необходимости в установке
2. **Глубокая интеграция** - доступ к функциям Telegram
3. **Простая монетизация** - встроенные платежи через Stars
4. **Кроссплатформенность** - работают везде
### 💡 **Что работает лучше всего:**
- **Игры** - высокая вовлеченность пользователей
- **E-commerce** - удобные покупки без покидания мессенджера
- **Утилиты** - быстрый доступ к нужным функциям
- **Образование** - интерактивное обучение
### ⚠️ **Типичные ошибки:**
- Игнорирование адаптивности
- Отсутствие аналитики
- Слабая валидация данных
- Недооценка производительности
### 🚀 **Советы для успеха:**
1. **Начните с простого** - создайте MVP и получите обратную связь
2. **Используйте аналитику** - отслеживайте поведение пользователей
3. **Оптимизируйте производительность** - быстрая загрузка критична
4. **Тестируйте на разных устройствах** - убедитесь в совместимости
Mini Apps - это будущее мобильных приложений. Они объединяют удобство веб-технологий с мощью нативных приложений, создавая уникальный пользовательский опыт!
---
*Готовы создать собственное Mini App? Обращайтесь к нам за хостингом, консультациями и технической поддержкой!*
Привет! Меня зовут Алексей, и я уже 2 года разрабатываю Telegram Mini Apps для различных проектов. За это время я создал более 30 мини-приложений, от простых игр до сложных e-commerce платформ, и готов поделиться всем опытом. В этой статье расскажу, как создать мощное Mini App, какие возможности открывает эта технология, и как монетизировать такие приложения.
## Что такое Telegram Mini Apps и почему они революционны?
### Статистика Mini Apps
- **900+ миллионов** пользователей Telegram по всему миру
- **50+ миллионов** активных пользователей Mini Apps
- **$2.1 миллиарда** объем транзакций через Telegram Stars
- **300%** рост популярности Mini Apps за последний год
### Преимущества Mini Apps
- **Нет установки** - работают прямо в мессенджере
- **Быстрый запуск** - мгновенный доступ к функциональности
- **Интеграция с Telegram** - доступ к контактам, геолокации, платежам
- **Кроссплатформенность** - работают на всех устройствах
- **Простая монетизация** - встроенные платежи через Telegram Stars
## Архитектура Telegram Mini Apps
### 1. 🏗️ Базовая структура проекта
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Mini App</title>
<!-- Telegram WebApp SDK -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<!-- Стили -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<!-- Основной контент приложения -->
<header class="app-header">
<h1>Мое Mini App</h1>
<button id="theme-toggle">🌙</button>
</header>
<main class="app-main">
<div id="content">
<!-- Динамический контент -->
</div>
</main>
<footer class="app-footer">
<button id="main-button" class="main-button">Главная кнопка</button>
</footer>
</div>
<!-- JavaScript -->
<script src="app.js"></script>
</body>
</html>
```
### 2. 🎨 CSS стили для Mini App
```css
/* styles.css */
:root {
--tg-theme-bg-color: #ffffff;
--tg-theme-text-color: #000000;
--tg-theme-hint-color: #999999;
--tg-theme-link-color: #2481cc;
--tg-theme-button-color: #2481cc;
--tg-theme-button-text-color: #ffffff;
--tg-theme-secondary-bg-color: #f1f1f1;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--tg-theme-bg-color);
color: var(--tg-theme-text-color);
height: 100vh;
overflow-x: hidden;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
}
.app-header {
background-color: var(--tg-theme-secondary-bg-color);
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.app-header h1 {
font-size: 18px;
font-weight: 600;
}
#theme-toggle {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: background-color 0.2s;
}
#theme-toggle:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.app-main {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.app-footer {
padding: 16px;
background-color: var(--tg-theme-secondary-bg-color);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.main-button {
width: 100%;
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.main-button:hover {
opacity: 0.9;
}
.main-button:active {
opacity: 0.8;
}
/* Компоненты */
.card {
background-color: var(--tg-theme-bg-color);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.card-content {
font-size: 14px;
line-height: 1.4;
color: var(--tg-theme-hint-color);
}
.button {
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
margin: 4px;
}
.button:hover {
opacity: 0.9;
}
.button.secondary {
background-color: var(--tg-theme-secondary-bg-color);
color: var(--tg-theme-text-color);
}
.input {
width: 100%;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 8px;
font-size: 16px;
background-color: var(--tg-theme-bg-color);
color: var(--tg-theme-text-color);
margin-bottom: 12px;
}
.input:focus {
outline: none;
border-color: var(--tg-theme-button-color);
}
/* Адаптивность */
@media (max-width: 480px) {
.app-header {
padding: 12px;
}
.app-main {
padding: 12px;
}
.app-footer {
padding: 12px;
}
.card {
padding: 12px;
}
}
/* Анимации */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
```
### 3. ⚡ JavaScript функциональность
```javascript
// app.js
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.user = null;
this.initData = null;
this.isDarkTheme = false;
this.init();
}
init() {
// Инициализация Telegram WebApp
this.tg.ready();
// Получаем данные пользователя
this.user = this.tg.initDataUnsafe.user;
this.initData = this.tg.initData;
// Настраиваем тему
this.setupTheme();
// Настраиваем главную кнопку
this.setupMainButton();
// Настраиваем обработчики событий
this.setupEventHandlers();
// Загружаем контент
this.loadContent();
console.log('Mini App initialized:', {
user: this.user,
platform: this.tg.platform,
version: this.tg.version
});
}
setupTheme() {
// Применяем тему Telegram
document.body.style.backgroundColor = this.tg.themeParams.bg_color;
document.body.style.color = this.tg.themeParams.text_color;
// Обновляем CSS переменные
const root = document.documentElement;
root.style.setProperty('--tg-theme-bg-color', this.tg.themeParams.bg_color);
root.style.setProperty('--tg-theme-text-color', this.tg.themeParams.text_color);
root.style.setProperty('--tg-theme-hint-color', this.tg.themeParams.hint_color);
root.style.setProperty('--tg-theme-link-color', this.tg.themeParams.link_color);
root.style.setProperty('--tg-theme-button-color', this.tg.themeParams.button_color);
root.style.setProperty('--tg-theme-button-text-color', this.tg.themeParams.button_text_color);
root.style.setProperty('--tg-theme-secondary-bg-color', this.tg.themeParams.secondary_bg_color);
}
setupMainButton() {
// Настраиваем главную кнопку
this.tg.MainButton.setText('Купить за 100 ⭐');
this.tg.MainButton.color = this.tg.themeParams.button_color;
this.tg.MainButton.textColor = this.tg.themeParams.button_text_color;
// Обработчик клика
this.tg.MainButton.onClick(() => {
this.handlePurchase();
});
// Показываем кнопку
this.tg.MainButton.show();
}
setupEventHandlers() {
// Обработчик изменения темы
this.tg.onEvent('themeChanged', () => {
this.setupTheme();
});
// Обработчик изменения размера окна
this.tg.onEvent('viewportChanged', () => {
this.handleViewportChange();
});
// Обработчик кнопки "Назад"
this.tg.BackButton.onClick(() => {
this.handleBackButton();
});
// Обработчик закрытия приложения
this.tg.onEvent('close', () => {
this.handleClose();
});
}
loadContent() {
const content = document.getElementById('content');
// Показываем приветствие
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">👋 Добро пожаловать!</div>
<div class="card-content">
Привет, ${this.user?.first_name || 'Пользователь'}!
Это ваше первое Mini App в Telegram.
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игры</div>
<div class="card-content">
Выберите игру для игры:
</div>
<button class="button" onclick="miniApp.startGame('snake')">🐍 Змейка</button>
<button class="button" onclick="miniApp.startGame('tetris')">🧩 Тетрис</button>
<button class="button" onclick="miniApp.startGame('quiz')">❓ Викторина</button>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Покупайте товары за Telegram Stars:
</div>
<button class="button" onclick="miniApp.showShop()">Открыть магазин</button>
</div>
<div class="card fade-in">
<div class="card-title">📊 Статистика</div>
<div class="card-content">
Ваша статистика использования:
</div>
<button class="button secondary" onclick="miniApp.showStats()">Показать статистику</button>
</div>
`;
}
startGame(gameType) {
// Скрываем главную кнопку
this.tg.MainButton.hide();
// Показываем кнопку "Назад"
this.tg.BackButton.show();
// Загружаем игру
this.loadGame(gameType);
}
loadGame(gameType) {
const content = document.getElementById('content');
switch(gameType) {
case 'snake':
content.innerHTML = this.getSnakeGameHTML();
this.initSnakeGame();
break;
case 'tetris':
content.innerHTML = this.getTetrisGameHTML();
this.initTetrisGame();
break;
case 'quiz':
content.innerHTML = this.getQuizGameHTML();
this.initQuizGame();
break;
}
}
getSnakeGameHTML() {
return `
<div class="card">
<div class="card-title">🐍 Змейка</div>
<div class="card-content">
Счет: <span id="score">0</span>
</div>
<canvas id="gameCanvas" width="300" height="300" style="border: 1px solid #ccc; display: block; margin: 0 auto;"></canvas>
<div style="text-align: center; margin-top: 10px;">
<button class="button" onclick="miniApp.pauseGame()">Пауза</button>
<button class="button" onclick="miniApp.restartGame()">Заново</button>
</div>
</div>
`;
}
initSnakeGame() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let snake = [{x: 150, y: 150}];
let direction = {x: 0, y: 0};
let food = {x: 200, y: 200};
let score = 0;
let gameRunning = true;
const gameLoop = () => {
if (!gameRunning) return;
// Очищаем canvas
ctx.fillStyle = this.tg.themeParams.bg_color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Рисуем змейку
ctx.fillStyle = this.tg.themeParams.button_color;
snake.forEach(segment => {
ctx.fillRect(segment.x, segment.y, 10, 10);
});
// Рисуем еду
ctx.fillStyle = '#ff4444';
ctx.fillRect(food.x, food.y, 10, 10);
// Двигаем змейку
const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
// Проверяем столкновения
if (head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height) {
this.gameOver();
return;
}
if (snake.some(segment => segment.x === head.x && segment.y === head.y)) {
this.gameOver();
return;
}
snake.unshift(head);
// Проверяем поедание еды
if (head.x === food.x && head.y === food.y) {
score++;
document.getElementById('score').textContent = score;
this.generateFood();
} else {
snake.pop();
}
setTimeout(gameLoop, 150);
};
// Обработка клавиш
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp':
if (direction.y === 0) direction = {x: 0, y: -10};
break;
case 'ArrowDown':
if (direction.y === 0) direction = {x: 0, y: 10};
break;
case 'ArrowLeft':
if (direction.x === 0) direction = {x: -10, y: 0};
break;
case 'ArrowRight':
if (direction.x === 0) direction = {x: 10, y: 0};
break;
}
});
// Начинаем игру
gameLoop();
// Сохраняем ссылки для управления
this.gameLoop = gameLoop;
this.gameRunning = gameRunning;
}
generateFood() {
const canvas = document.getElementById('gameCanvas');
const food = {
x: Math.floor(Math.random() * (canvas.width / 10)) * 10,
y: Math.floor(Math.random() * (canvas.height / 10)) * 10
};
// Обновляем еду в DOM (упрощенная версия)
const foodElement = document.querySelector('#gameCanvas');
// Здесь должна быть логика обновления позиции еды
}
pauseGame() {
this.gameRunning = !this.gameRunning;
if (this.gameRunning) {
this.gameLoop();
}
}
restartGame() {
this.gameRunning = true;
this.initSnakeGame();
}
gameOver() {
this.gameRunning = false;
// Показываем результат
this.tg.showAlert('Игра окончена! Ваш счет: ' + document.getElementById('score').textContent);
// Отправляем результат в Telegram
this.tg.sendData(JSON.stringify({
type: 'game_result',
game: 'snake',
score: document.getElementById('score').textContent
}));
}
showShop() {
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Премиум игры</div>
<div class="card-content">
Разблокируйте все игры за Telegram Stars
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_games', 50)">Купить за 50 ⭐</button>
</div>
<div class="card fade-in">
<div class="card-title">🎨 Темы</div>
<div class="card-content">
Персонализируйте внешний вид приложения
</div>
<button class="button" onclick="miniApp.purchaseItem('themes', 25)">Купить за 25 ⭐</button>
</div>
<div class="card fade-in">
<div class="card-title">💎 Премиум функции</div>
<div class="card-content">
Получите доступ к расширенным возможностям
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_features', 100)">Купить за 100 ⭐</button>
</div>
`;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
purchaseItem(itemId, price) {
// Показываем подтверждение покупки
this.tg.showConfirm(`Купить ${itemId} за ${price} ⭐?`, (confirmed) => {
if (confirmed) {
this.processPurchase(itemId, price);
}
});
}
processPurchase(itemId, price) {
// Создаем инвойс для Telegram Stars
const invoice = {
title: `Покупка: ${itemId}`,
description: `Получите доступ к ${itemId}`,
payload: JSON.stringify({
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}),
provider_token: '', // Для Telegram Stars не нужен
currency: 'XTR', // Telegram Stars
prices: [{
label: itemId,
amount: price * 100 // В копейках
}]
};
// Открываем платежную форму
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPurchase(itemId);
} else {
this.tg.showAlert('Покупка отменена');
}
});
}
handleSuccessfulPurchase(itemId) {
// Обрабатываем успешную покупку
this.tg.showAlert('Покупка успешна! Спасибо!');
// Отправляем данные о покупке
this.tg.sendData(JSON.stringify({
type: 'purchase',
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}));
// Разблокируем функции
this.unlockFeatures(itemId);
}
unlockFeatures(itemId) {
// Логика разблокировки функций
switch(itemId) {
case 'premium_games':
// Разблокируем премиум игры
break;
case 'themes':
// Разблокируем темы
break;
case 'premium_features':
// Разблокируем премиум функции
break;
}
}
showStats() {
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">📊 Ваша статистика</div>
<div class="card-content">
<p>👤 Пользователь: ${this.user?.first_name} ${this.user?.last_name || ''}</p>
<p>🆔 ID: ${this.user?.id}</p>
<p>🌐 Язык: ${this.user?.language_code}</p>
<p>📱 Платформа: ${this.tg.platform}</p>
<p>📅 Дата регистрации: ${new Date(this.user?.id * 1000).toLocaleDateString()}</p>
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игровая статистика</div>
<div class="card-content">
<p>🐍 Змейка: Лучший счет - 0</p>
<p>🧩 Тетрис: Лучший счет - 0</p>
<p>❓ Викторина: Правильных ответов - 0</p>
</div>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Покупки</div>
<div class="card-content">
<p>💎 Потрачено Stars: 0</p>
<p>📦 Куплено товаров: 0</p>
</div>
</div>
`;
}
handleBackButton() {
// Возвращаемся к главному меню
this.tg.BackButton.hide();
this.tg.MainButton.show();
this.loadContent();
}
handleViewportChange() {
// Обрабатываем изменение размера окна
console.log('Viewport changed:', this.tg.viewportHeight, this.tg.viewportStableHeight);
}
handleClose() {
// Обрабатываем закрытие приложения
console.log('App is closing');
// Отправляем финальные данные
this.tg.sendData(JSON.stringify({
type: 'app_close',
user_id: this.user.id,
timestamp: Date.now()
}));
}
handlePurchase() {
// Обработка покупки через главную кнопку
this.purchaseItem('premium_package', 100);
}
}
// Инициализация приложения
let miniApp;
document.addEventListener('DOMContentLoaded', () => {
miniApp = new TelegramMiniApp();
});
```
## Интеграция с внешними API
### 1. 🔌 Работа с бэкендом
```javascript
class BackendAPI {
constructor() {
this.baseUrl = 'https://your-api.com/api';
this.tg = window.Telegram.WebApp;
}
async makeRequest(endpoint, method = 'GET', data = null) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': this.tg.initData
};
const options = {
method,
headers
};
if (data && method !== 'GET') {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
this.tg.showAlert('Ошибка соединения с сервером');
throw error;
}
}
async getUserData() {
return await this.makeRequest('/user/profile');
}
async updateUserData(data) {
return await this.makeRequest('/user/profile', 'PUT', data);
}
async getGameLeaderboard(gameType) {
return await this.makeRequest(`/games/${gameType}/leaderboard`);
}
async submitGameScore(gameType, score) {
return await this.makeRequest('/games/score', 'POST', {
game_type: gameType,
score: score
});
}
async getShopItems() {
return await this.makeRequest('/shop/items');
}
async purchaseItem(itemId) {
return await this.makeRequest('/shop/purchase', 'POST', {
item_id: itemId
});
}
}
// Интеграция с основным классом
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.user = null;
this.init();
}
async init() {
this.tg.ready();
this.user = this.tg.initDataUnsafe.user;
try {
// Загружаем данные пользователя с сервера
const userData = await this.api.getUserData();
this.userData = userData;
this.setupTheme();
this.setupMainButton();
this.setupEventHandlers();
this.loadContent();
} catch (error) {
console.error('Failed to initialize app:', error);
this.showError('Не удалось загрузить данные приложения');
}
}
async loadGameLeaderboard(gameType) {
try {
const leaderboard = await this.api.getGameLeaderboard(gameType);
this.displayLeaderboard(leaderboard);
} catch (error) {
this.tg.showAlert('Не удалось загрузить таблицу лидеров');
}
}
async submitScore(gameType, score) {
try {
await this.api.submitGameScore(gameType, score);
this.tg.showAlert(`Счет ${score} сохранен!`);
} catch (error) {
this.tg.showAlert('Не удалось сохранить счет');
}
}
displayLeaderboard(leaderboard) {
const content = document.getElementById('content');
let leaderboardHTML = `
<div class="card fade-in">
<div class="card-title">🏆 Таблица лидеров</div>
<div class="card-content">
`;
leaderboard.forEach((entry, index) => {
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🏅';
leaderboardHTML += `
<div style="display: flex; justify-content: space-between; margin: 8px 0;">
<span>${medal} ${entry.username}</span>
<span>${entry.score}</span>
</div>
`;
});
leaderboardHTML += `
</div>
<button class="button" onclick="miniApp.loadContent()">Назад</button>
</div>
`;
content.innerHTML = leaderboardHTML;
}
}
```
### 2. 📊 Аналитика и метрики
```javascript
class Analytics {
constructor() {
this.tg = window.Telegram.WebApp;
this.events = [];
this.sessionStart = Date.now();
}
track(eventName, properties = {}) {
const event = {
name: eventName,
properties: {
...properties,
user_id: this.tg.initDataUnsafe.user?.id,
platform: this.tg.platform,
version: this.tg.version,
timestamp: Date.now(),
session_id: this.getSessionId()
}
};
this.events.push(event);
// Отправляем событие на сервер
this.sendEvent(event);
console.log('Analytics event:', event);
}
getSessionId() {
let sessionId = localStorage.getItem('session_id');
if (!sessionId) {
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('session_id', sessionId);
}
return sessionId;
}
async sendEvent(event) {
try {
await fetch('https://your-analytics-api.com/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': this.tg.initData
},
body: JSON.stringify(event)
});
} catch (error) {
console.error('Failed to send analytics event:', error);
}
}
trackPageView(pageName) {
this.track('page_view', {
page_name: pageName
});
}
trackGameStart(gameType) {
this.track('game_start', {
game_type: gameType
});
}
trackGameEnd(gameType, score, duration) {
this.track('game_end', {
game_type: gameType,
score: score,
duration: duration
});
}
trackPurchase(itemId, price) {
this.track('purchase', {
item_id: itemId,
price: price,
currency: 'XTR'
});
}
trackButtonClick(buttonName, location) {
this.track('button_click', {
button_name: buttonName,
location: location
});
}
}
// Интеграция аналитики
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.analytics = new Analytics();
this.user = null;
this.init();
}
async init() {
this.tg.ready();
this.user = this.tg.initDataUnsafe.user;
// Отслеживаем запуск приложения
this.analytics.track('app_start', {
user_id: this.user?.id,
platform: this.tg.platform,
version: this.tg.version
});
this.setupTheme();
this.setupMainButton();
this.setupEventHandlers();
this.loadContent();
}
loadContent() {
this.analytics.trackPageView('main_menu');
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">👋 Добро пожаловать!</div>
<div class="card-content">
Привет, ${this.user?.first_name || 'Пользователь'}!
Это ваше первое Mini App в Telegram.
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Игры</div>
<div class="card-content">
Выберите игру для игры:
</div>
<button class="button" onclick="miniApp.startGame('snake')">🐍 Змейка</button>
<button class="button" onclick="miniApp.startGame('tetris')">🧩 Тетрис</button>
<button class="button" onclick="miniApp.startGame('quiz')">❓ Викторина</button>
</div>
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Покупайте товары за Telegram Stars:
</div>
<button class="button" onclick="miniApp.showShop()">Открыть магазин</button>
</div>
`;
}
startGame(gameType) {
this.analytics.trackGameStart(gameType);
this.analytics.trackButtonClick('start_game', 'main_menu');
// Скрываем главную кнопку
this.tg.MainButton.hide();
// Показываем кнопку "Назад"
this.tg.BackButton.show();
// Загружаем игру
this.loadGame(gameType);
}
showShop() {
this.analytics.trackPageView('shop');
this.analytics.trackButtonClick('open_shop', 'main_menu');
const content = document.getElementById('content');
content.innerHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
<div class="card fade-in">
<div class="card-title">🎮 Премиум игры</div>
<div class="card-content">
Разблокируйте все игры за Telegram Stars
</div>
<button class="button" onclick="miniApp.purchaseItem('premium_games', 50)">Купить за 50 ⭐</button>
</div>
`;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
purchaseItem(itemId, price) {
this.analytics.trackButtonClick('purchase_item', 'shop');
// Показываем подтверждение покупки
this.tg.showConfirm(`Купить ${itemId} за ${price} ⭐?`, (confirmed) => {
if (confirmed) {
this.processPurchase(itemId, price);
}
});
}
processPurchase(itemId, price) {
// Создаем инвойс для Telegram Stars
const invoice = {
title: `Покупка: ${itemId}`,
description: `Получите доступ к ${itemId}`,
payload: JSON.stringify({
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}),
provider_token: '',
currency: 'XTR',
prices: [{
label: itemId,
amount: price * 100
}]
};
// Открываем платежную форму
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPurchase(itemId, price);
} else {
this.tg.showAlert('Покупка отменена');
}
});
}
handleSuccessfulPurchase(itemId, price) {
// Отслеживаем успешную покупку
this.analytics.trackPurchase(itemId, price);
// Обрабатываем покупку
this.tg.showAlert('Покупка успешна! Спасибо!');
// Отправляем данные о покупке
this.tg.sendData(JSON.stringify({
type: 'purchase',
item_id: itemId,
user_id: this.user.id,
timestamp: Date.now()
}));
// Разблокируем функции
this.unlockFeatures(itemId);
}
}
```
## Монетизация Mini Apps
### 1. 💰 Telegram Stars интеграция
```javascript
class PaymentManager {
constructor() {
this.tg = window.Telegram.WebApp;
this.purchases = [];
}
async createInvoice(item) {
const invoice = {
title: item.title,
description: item.description,
payload: JSON.stringify({
item_id: item.id,
user_id: this.tg.initDataUnsafe.user.id,
timestamp: Date.now()
}),
provider_token: '', // Для Telegram Stars не нужен
currency: 'XTR', // Telegram Stars
prices: [{
label: item.title,
amount: item.price * 100 // В копейках
}]
};
return invoice;
}
async processPayment(item) {
try {
const invoice = await this.createInvoice(item);
return new Promise((resolve, reject) => {
this.tg.openInvoice(invoice, (status) => {
if (status === 'paid') {
this.handleSuccessfulPayment(item);
resolve({ success: true, item: item });
} else {
reject({ success: false, reason: 'payment_cancelled' });
}
});
});
} catch (error) {
console.error('Payment processing failed:', error);
throw error;
}
}
handleSuccessfulPayment(item) {
// Сохраняем информацию о покупке
this.purchases.push({
item_id: item.id,
timestamp: Date.now(),
price: item.price
});
// Отправляем данные на сервер
this.tg.sendData(JSON.stringify({
type: 'purchase_completed',
item_id: item.id,
user_id: this.tg.initDataUnsafe.user.id,
timestamp: Date.now()
}));
// Показываем уведомление
this.tg.showAlert(`Покупка "${item.title}" успешна!`);
}
async getPurchaseHistory() {
// Получаем историю покупок с сервера
try {
const response = await fetch('/api/purchases', {
headers: {
'X-Telegram-Init-Data': this.tg.initData
}
});
return await response.json();
} catch (error) {
console.error('Failed to get purchase history:', error);
return [];
}
}
hasPurchased(itemId) {
return this.purchases.some(purchase => purchase.item_id === itemId);
}
}
// Интеграция с основным приложением
class TelegramMiniApp {
constructor() {
this.tg = window.Telegram.WebApp;
this.api = new BackendAPI();
this.analytics = new Analytics();
this.paymentManager = new PaymentManager();
this.user = null;
this.init();
}
async showShop() {
this.analytics.trackPageView('shop');
const content = document.getElementById('content');
// Получаем товары с сервера
const shopItems = await this.api.getShopItems();
let shopHTML = `
<div class="card fade-in">
<div class="card-title">🛒 Магазин</div>
<div class="card-content">
Выберите товар для покупки:
</div>
</div>
`;
shopItems.forEach(item => {
const isPurchased = this.paymentManager.hasPurchased(item.id);
const buttonText = isPurchased ? '✅ Куплено' : `Купить за ${item.price} ⭐`;
const buttonDisabled = isPurchased ? 'disabled' : '';
shopHTML += `
<div class="card fade-in">
<div class="card-title">${item.title}</div>
<div class="card-content">
${item.description}
</div>
<button class="button ${buttonDisabled}"
onclick="miniApp.purchaseItem('${item.id}')"
${buttonDisabled}>
${buttonText}
</button>
</div>
`;
});
content.innerHTML = shopHTML;
// Обновляем главную кнопку
this.tg.MainButton.setText('Вернуться в меню');
this.tg.MainButton.onClick(() => {
this.loadContent();
});
}
async purchaseItem(itemId) {
try {
const shopItems = await this.api.getShopItems();
const item = shopItems.find(i => i.id === itemId);
if (!item) {
this.tg.showAlert('Товар не найден');
return;
}
if (this.paymentManager.hasPurchased(itemId)) {
this.tg.showAlert('Вы уже купили этот товар');
return;
}
// Показываем подтверждение
this.tg.showConfirm(`Купить "${item.title}" за ${item.price} ⭐?`, async (confirmed) => {
if (confirmed) {
try {
await this.paymentManager.processPayment(item);
this.showShop(); // Обновляем магазин
} catch (error) {
this.tg.showAlert('Ошибка при обработке платежа');
}
}
});
} catch (error) {
console.error('Purchase failed:', error);
this.tg.showAlert('Не удалось загрузить информацию о товаре');
}
}
}
```
### 2. 🎯 Стратегии монетизации
```javascript
class MonetizationStrategy {
constructor() {
this.tg = window.Telegram.WebApp;
this.userLevel = 1;
this.userExperience = 0;
this.premiumFeatures = new Set();
}
// Freemium модель
async implementFreemiumModel() {
const freeFeatures = [
'basic_games',
'limited_themes',
'basic_analytics'
];
const premiumFeatures = [
'unlimited_games',
'all_themes',
'advanced_analytics',
'priority_support',
'exclusive_content'
];
return {
free: freeFeatures,
premium: premiumFeatures
};
}
// Подписка
async createSubscriptionPlan(planType) {
const plans = {
'monthly': {
title: 'Месячная подписка',
price: 100, // Stars
duration: 30, // дней
features: ['unlimited_games', 'all_themes', 'advanced_analytics']
},
'yearly': {
title: 'Годовая подписка',
price: 1000, // Stars
duration: 365, // дней
features: ['unlimited_games', 'all_themes', 'advanced_analytics', 'exclusive_content']
}
};
return plans[planType];
}
// Система достижений и наград
async implementAchievementSystem() {
const achievements = [
{
id: 'first_game',
title: 'Первая игра',
description: 'Сыграйте в первую игру',
reward: 10, // Stars
condition: (userData) => userData.gamesPlayed >= 1
},
{
id: 'high_score',
title: 'Высокий счет',
description: 'Наберите 1000 очков в любой игре',
reward: 25, // Stars
condition: (userData) => userData.bestScore >= 1000
},
{
id: 'daily_player',
title: 'Ежедневный игрок',
description: 'Играйте 7 дней подряд',
reward: 50, // Stars
condition: (userData) => userData.consecutiveDays >= 7
}
];
return achievements;
}
// Система рефералов
async implementReferralSystem() {
const referralReward = 25; // Stars за каждого реферала
return {
reward: referralReward,
maxReferrals: 10, // Максимум рефералов для получения награды
totalReward: referralReward * 10
};
}
// Система уровней
async calculateUserLevel(experience) {
const levels = [
{ level: 1, expRequired: 0 },
{ level: 2, expRequired: 100 },
{ level: 3, expRequired: 250 },
{ level: 4, expRequired: 500 },
{ level: 5, expRequired: 1000 },
{ level: 6, expRequired: 2000 },
{ level: 7, expRequired: 4000 },
{ level: 8, expRequired: 8000 },
{ level: 9, expRequired: 16000 },
{ level: 10, expRequired: 32000 }
];
for (let i = levels.length - 1; i >= 0; i--) {
if (experience >= levels[i].expRequired) {
return levels[i];
}
}
return levels[0];
}
// Система ежедневных наград
async implementDailyRewards() {
const dailyRewards = [
{ day: 1, reward: 5, type: 'stars' },
{ day: 2, reward: 10, type: 'stars' },
{ day: 3, reward: 15, type: 'stars' },
{ day: 4, reward: 20, type: 'stars' },
{ day: 5, reward: 25, type: 'stars' },
{ day: 6, reward: 30, type: 'stars' },
{ day: 7, reward: 50, type: 'stars' }
];
return dailyRewards;
}
}
```
## Развертывание и хостинг
### 1. 🚀 Настройка сервера
```javascript
// server.js (Node.js + Express)
const express = require('express');
const cors = require('cors');
const crypto = require('crypto');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// Валидация Telegram WebApp данных
function validateTelegramData(initData, botToken) {
const urlParams = new URLSearchParams(initData);
const hash = urlParams.get('hash');
urlParams.delete('hash');
const dataCheckString = Array.from(urlParams.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join('\n');
const secretKey = crypto.createHmac('sha256', 'WebAppData').update(botToken).digest();
const calculatedHash = crypto.createHmac('sha256', secretKey).update(dataCheckString).digest('hex');
return calculatedHash === hash;
}
// API маршруты
app.post('/api/user/profile', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
// Получаем данные пользователя
const userData = JSON.parse(new URLSearchParams(initData).get('user'));
// Здесь должна быть логика получения данных пользователя из БД
res.json({
user_id: userData.id,
username: userData.username,
first_name: userData.first_name,
last_name: userData.last_name,
level: 1,
experience: 0,
stars: 0,
purchases: []
});
});
app.post('/api/games/score', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
const { game_type, score } = req.body;
// Здесь должна быть логика сохранения счета в БД
res.json({ success: true, score: score });
});
app.get('/api/games/:gameType/leaderboard', (req, res) => {
const { gameType } = req.params;
// Здесь должна быть логика получения таблицы лидеров из БД
const leaderboard = [
{ username: 'Player1', score: 1500 },
{ username: 'Player2', score: 1200 },
{ username: 'Player3', score: 1000 }
];
res.json(leaderboard);
});
app.get('/api/shop/items', (req, res) => {
const items = [
{
id: 'premium_games',
title: 'Премиум игры',
description: 'Разблокируйте все игры',
price: 50
},
{
id: 'themes',
title: 'Темы',
description: 'Персонализируйте внешний вид',
price: 25
},
{
id: 'premium_features',
title: 'Премиум функции',
description: 'Расширенные возможности',
price: 100
}
];
res.json(items);
});
app.post('/api/shop/purchase', (req, res) => {
const initData = req.headers['x-telegram-init-data'];
if (!validateTelegramData(initData, process.env.BOT_TOKEN)) {
return res.status(401).json({ error: 'Invalid Telegram data' });
}
const { item_id } = req.body;
// Здесь должна быть логика обработки покупки
res.json({ success: true, item_id: item_id });
});
// Статические файлы
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
```
### 2. 🐳 Docker конфигурация
```dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Копируем package.json и package-lock.json
COPY package*.json ./
# Устанавливаем зависимости
RUN npm ci --only=production
# Копируем исходный код
COPY . .
# Создаем пользователя для безопасности
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Меняем владельца файлов
RUN chown -R nextjs:nodejs /app
USER nextjs
# Открываем порт
EXPOSE 3000
# Запускаем приложение
CMD ["npm", "start"]
```
```yaml
# docker-compose.yml
version: '3.8'
services:
mini-app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- BOT_TOKEN=${BOT_TOKEN}
- DATABASE_URL=${DATABASE_URL}
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- mini-app
volumes:
postgres_data:
redis_data:
```
## Заключение
Telegram Mini Apps - это революционная технология, которая открывает новые возможности для разработчиков. За 2 года работы я понял, что:
### 🎯 **Главные преимущества Mini Apps:**
1. **Мгновенный доступ** - нет необходимости в установке
2. **Глубокая интеграция** - доступ к функциям Telegram
3. **Простая монетизация** - встроенные платежи через Stars
4. **Кроссплатформенность** - работают везде
### 💡 **Что работает лучше всего:**
- **Игры** - высокая вовлеченность пользователей
- **E-commerce** - удобные покупки без покидания мессенджера
- **Утилиты** - быстрый доступ к нужным функциям
- **Образование** - интерактивное обучение
### ⚠️ **Типичные ошибки:**
- Игнорирование адаптивности
- Отсутствие аналитики
- Слабая валидация данных
- Недооценка производительности
### 🚀 **Советы для успеха:**
1. **Начните с простого** - создайте MVP и получите обратную связь
2. **Используйте аналитику** - отслеживайте поведение пользователей
3. **Оптимизируйте производительность** - быстрая загрузка критична
4. **Тестируйте на разных устройствах** - убедитесь в совместимости
Mini Apps - это будущее мобильных приложений. Они объединяют удобство веб-технологий с мощью нативных приложений, создавая уникальный пользовательский опыт!
---
*Готовы создать собственное Mini App? Обращайтесь к нам за хостингом, консультациями и технической поддержкой!*
229 просмотров
0 лайков
0 комментариев
Комментарии (0)
Пока нет комментариев. Будьте первым!