Mini Apps
React і Vue
Когда фронтенд Mini App готов (React или Vue), главная задача — безопасно передать действия пользователя на PHP-бэкенд. В главе Проверка initData мы разобрали функцию verifyInitData() — она валидирует подпись и возвращает массив с данными пользователя. В главе Первый запрос: getMe у нас есть telegramApi() для вызовов Bot API. Здесь соединим всё вместе.
Архитектура запроса от SPA к PHP
Mini App (SPA) никогда не дергает Bot API напрямую — токен бота не должен попасть в браузер. Схема:
- React/Vue читает
window.Telegram.WebApp.initData(строка query string). - Отправляет POST на ваш PHP-эндпоинт, кладя
initDataв заголовокX-TG-Init-Data. - PHP проверяет подпись через
verifyInitData(), получаетuser.id,query_idи ваши параметры запуска. - Выполняет бизнес-логику и, если Mini App открыт через inline-кнопку, отвечает методом
answerWebAppQuery— это мгновенно закрывает WebView и шлёт сообщение в чат.
Пример PHP-обработчика (endpoint для SPA)
Файл api/miniapp/order.php. Используем хелперы из прошлых глав — не дублируем HMAC-код:
<?php
require_once __DIR__ . '/../../bootstrap.php'; // подключает telegramApi(), verifyInitData()
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method Not Allowed']);
exit;
}
$headers = getallheaders();
$initData = $headers['X-TG-Init-Data'] ?? '';
if (empty($initData)) {
http_response_code(401);
echo json_encode(['error' => 'Missing X-TG-Init-Data header']);
exit;
}
// 1. Валидация — используем функцию из главы «Проверка initData"
$validated = verifyInitData($initData);
if (!$validated) {
http_response_code(403);
echo json_encode(['error' => 'Invalid initData signature']);
exit;
}
// $validated содержит: ['user' => [...], 'query_id' => '...', 'start_param' => 'ref_123', ...]
$queryId = $validated['query_id'] ?? null;
$userId = $validated['user']['id'] ?? null;
if (!$queryId) {
http_response_code(400);
echo json_encode(['error' => 'query_id absent — Mini App не открыт через inline-кнопку']);
exit;
}
// 2. Читаем тело запроса (product_id, quantity и т.д.)
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$productId = (int)($input['product_id'] ?? 0);
if ($productId <= 0) {
http_response_code(422);
echo json_encode(['error' => 'Invalid product_id']);
exit;
}
// 3. Бизнес-логика: сохраняем заказ, списываем склад и т.д.
// $orderId = createOrder($userId, $productId);
// 4. Отвечаем Telegram через answerWebAppQuery — закрывает Mini App и шлёт сообщение
$answer = telegramApi('answerWebAppQuery', [
'web_app_query_id' => $queryId,
'result' => [
'type' => 'article',
'id' => 'order_' . time(),
'title' => 'Заказ оформлен',
'input_message_content' => [
'message_text' => "✅ Заказ #{$productId} принят в работу. Ожидайте звонка менеджера.",
'parse_mode' => 'HTML'
],
'description' => 'Статус: новый',
'thumb_url' => 'https://example.com/thumb-ok.png',
'thumb_width' => 64,
'thumb_height' => 64
]
]);
// 5. Проверяем ответ Telegram API
if (!($answer['ok'] ?? false)) {
error_log('answerWebAppQuery failed: ' . json_encode($answer));
http_response_code(502);
echo json_encode(['error' => 'Telegram API error', 'details' => $answer]);
exit;
}
echo json_encode(['success' => true, 'order_id' => $orderId ?? null]);
Пример успешного ответа Telegram API
Метод answerWebAppQuery возвращает объект SentWebAppMessage (или true в старых версиях). В поле result приходит сообщение, которое пользователь увидит в чате:
{
"ok": true,
"result": {
"message_id": 142,
"date": 1700000123,
"chat": {
"id": 123456789,
"type": "private",
"first_name": "Ivan",
"username": "ivan_p"
},
"via_bot": {
"id": 987654321,
"is_bot": true,
"first_name": "MyShopBot",
"username": "myshopbot"
},
"text": "✅ Заказ #42 принят в работу. Ожидайте звонка менеджера.",
"entities": []
}
}
Что смотреть: ok=true — запрос прошёл. В result.message_id — ID отправленного сообщения (можно потом отредактировать через editMessageText). via_bot подтверждает, что сообщение отправлено от имени вашего бота.
Типичные ошибки
{"ok":false,"error_code":400,"description":"Bad Request: query_id is invalid"}—query_idустарел (живёт ~24 часа) или Mini App открыт не через inline-кнопку (нет query_id).{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}— пользователь заблокировал бота, сообщение не уйдёт.{"ok":false,"error_code":429,"description":"Too Many Requests"}— превышен лимит, нужен retry с задержкой (см. главу Production: очередь и retries).
Как вызвать этот эндпоинт из React / Vue
Минимальный пример на fetch (общий для обоих фреймворков):
async function sendOrder(productId) {
const initData = window.Telegram?.WebApp?.initData || '';
const res = await fetch('/api/miniapp/order.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-TG-Init-Data': initData
},
body: JSON.stringify({ product_id: productId })
});
const data = await res.json();
if (!res.ok) {
console.error('Backend error:', data);
// покажите пользователю data.error
}
// если success — Mini App закроется сам через answerWebAppQuery
}
В React вызывайте внутри обработчика кнопки, во Vue — в методе компонента или composable. Главное — передать заголовок X-TG-Init-Data.
Отладка: что и где смотреть
- PHP-логи:
error_log()пишет в php-fpm/error.log — там видно ответ Telegram API при ошибках. - Браузер DevTools → Network: смотрите запрос к
/api/miniapp/order.php, проверяйте заголовокX-TG-Init-Dataи JSON-ответ. - Telegram Desktop / Web: откройте консоль WebView (Ctrl+Shift+I в Telegram Desktop) — там видны
console.logиз Mini App. - В терминале:
php api/miniapp/order.phpне сработает (нет POST), но можно протестировать через curl:curl -X POST http://localhost/api/miniapp/order.php \
-H 'Content-Type: application/json' \
-H 'X-TG-Init-Data: query_id=...&user=...&hash=...' \
-d '{"product_id":42}' | jq .
Дальше: Invoices и Payments