Переклад цього гайду зʼявиться незабаром. Поки що текст доступний російською — перемкніться на RU або читайте нижче. Відкрити російську версію

Проєкти

Магазин у боті

Существует два основных подхода к созданию магазина в Telegram: классический интерфейс на Inline-кнопках и современный графический Mini App. В этой главе мы построим классический магазин на кнопках. Это идеальное решение для каталогов с небольшим ассортиментом (до 50 товаров). Оно работает мгновенно, не требует разработки фронтенда на JS и отлично функционирует даже при слабом мобильном интернете.

Мы реализуем полноценный цикл: просмотр каталога, интерактивное управление корзиной (добавление, изменение количества) и переход к оплате. Данные о корзине пользователей будем хранить в базе данных, чтобы они не терялись при перезапуске бота.

1. Структура базы данных

Для работы магазина нам понадобятся три таблицы: товары (products), корзина (cart) и заказы (orders). Ниже представлена SQL-структура для нашей базы данных:

CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
price_stars INT NOT NULL,
photo_url VARCHAR(255)
);

CREATE TABLE cart (
user_id BIGINT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
PRIMARY KEY (user_id, product_id)
);

Поле price_stars хранит цену в Telegram Stars. Если вы планируете принимать фиатную валюту (рубли, доллары), храните цену в минимальных долях валюты (копейках, центах), как того требует Telegram Bot API.

2. Отображение каталога товаров

Поскольку размер поля callback_data в Inline-кнопках строго ограничен 64 байтами, мы должны использовать максимально компактные префиксы для действий:

  • cat:show:ID — показать товар с указанным ID.
  • cat:add:ID — добавить товар в корзину.
  • cart:view — открыть корзину.

Для отправки запросов к API мы будем использовать функцию telegramApi(), подробно описанную в главе HTML и Markdown.

<?php

function showProduct($chatId, $productId, $pdo) {
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$product) {
telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => 'Товар не найден.'
]);
return;
}

// Проверяем, есть ли уже этот товар в корзине пользователя
$cartStmt = $pdo->prepare("SELECT quantity FROM cart WHERE user_id = ? AND product_id = ?");
$cartStmt->execute([$chatId, $productId]);
$cartItem = $cartStmt->fetch(PDO::FETCH_ASSOC);
$inCart = $cartItem ? $cartItem['quantity'] : 0;

$text = "<strong>" . htmlspecialchars($product['title']) . "</strong>\n\n";
$text .= htmlspecialchars($product['description']) . "\n\n";
$text .= "Цена: " . $product['price_stars'] . " ⭐️";

$keyboard = [
'inline_keyboard' => [
[
[
'text' => $inCart > 0 ? "➕ Добавить еще (уже {$inCart} шт)" : "📥 Добавить в корзину",
'callback_data' => "cat:add:" . $product['id']
]
],
[
['text' => "🛒 Перейти в корзину", 'callback_data' => "cart:view"]
]
]
];

telegramApi('sendPhoto', [
'chat_id' => $chatId,
'photo' => $product['photo_url'],
'caption' => $text,
'parse_mode' => 'HTML',
'reply_markup' => json_encode($keyboard)
]);
}

3. Обработка добавления в корзину

Когда пользователь нажимает кнопку «Добавить в корзину», бот получает callback_query. Нам необходимо обновить запись в таблице cart (используя конструкцию ON DUPLICATE KEY UPDATE), изменить текст кнопки на актуальное количество товара и обязательно вызвать answerCallbackQuery, чтобы убрать индикатор загрузки на кнопке.

Для изменения клавиатуры без мерцания экрана мы применим метод editMessageReplyMarkup.

Для отправки запросов к API мы будем использовать функцию telegramApi(), подробно описанную в главе HTML и Markdown.

<?php

function handleAddToCart($callbackQuery, $productId, $pdo) {
$userId = $callbackQuery['from']['id'];
$message = $callbackQuery['message'];
$chatId = $message['chat']['id'];

// Добавляем или увеличиваем количество в БД
$stmt = $pdo->prepare("
INSERT INTO cart (user_id, product_id, quantity)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE quantity = quantity + 1
");
$stmt->execute([$userId, $productId]);

// Получаем новое количество товара в корзине
$cartStmt = $pdo->prepare("SELECT quantity FROM cart WHERE user_id = ? AND product_id = ?");
$cartStmt->execute([$userId, $productId]);
$newQuantity = $cartStmt->fetchColumn();

// Обновляем клавиатуру сообщения
$keyboard = [
'inline_keyboard' => [
[
[
'text' => "➕ Добавить еще ({$newQuantity} шт)",
'callback_data' => "cat:add:" . $productId
]
],
[
['text' => "🛒 Перейти в корзину", 'callback_data' => "cart:view"]
]
]
];

telegramApi('editMessageReplyMarkup', [
'chat_id' => $chatId,
'message_id' => $message['message_id'],
'reply_markup' => json_encode($keyboard)
]);

// Уведомляем пользователя всплывающим окном
telegramApi('answerCallbackQuery', [
'callback_query_id' => $callbackQuery['id'],
'text' => "Товар добавлен в корзину!",
'show_alert' => false
]);
}

4. Просмотр корзины и оформление заказа

При клике на «Перейти в корзину» бот должен вывести список всех выбранных товаров, их суммарную стоимость и кнопку «Оплатить заказ». Для генерации счета мы будем использовать Telegram Stars.

Для отправки счета мы будем использовать функцию sendStarsInvoice(), подробно описанную в главе Telegram Stars.

<?php

function showCart($chatId, $pdo) {
$stmt = $pdo->prepare("
SELECT c.quantity, p.title, p.price_stars, p.id
FROM cart c
JOIN products p ON c.product_id = p.id
WHERE c.user_id = ?
");
$stmt->execute([$chatId]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);

if (empty($items)) {
telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => "Ваша корзина пуста. Вернитесь в каталог!"
]);
return;
}

$text = "🛒 <strong>Ваша корзина:</strong>\n\n";
$totalStars = 0;

foreach ($items as $item) {
$sum = $item['price_stars'] * $item['quantity'];
$totalStars += $sum;
$text .= "• {$item['title']} — {$item['quantity']} шт. x {$item['price_stars']} ⭐️ = {$sum} ⭐️\n";
}

$text .= "\n<strong>Итого к оплате:</strong> {$totalStars} ⭐️";

$keyboard = [
'inline_keyboard' => [
[
['text' => "💳 Оплатить {$totalStars} ⭐️", 'callback_data' => "cart:checkout"]
],
[
['text' => "🗑 Очистить корзину", 'callback_data' => "cart:clear"]
]
]
];

telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
'reply_markup' => json_encode($keyboard)
]);
}

Когда пользователь нажимает кнопку «Оплатить», ваш webhook-обработчик ловит callback_data === 'cart:checkout', рассчитывает общую сумму корзины из БД и вызывает метод sendInvoice (или sendStarsInvoice), передавая уникальный идентификатор заказа в качестве payload. После успешной оплаты (событие successful_payment) корзина пользователя в БД очищается, а статус заказа меняется на «Оплачен».

Дальше: Mini App + PHP API