Projects
In-bot shop
Creating a fully functional store inside Telegram using PHP. We analyze the database structure for the catalog and cart, dynamic updates of Inline buttons, and preparation for payment.
There are two main approaches to creating a store in Telegram: a classic interface using Inline buttons and a modern graphical Mini App. In this chapter, we will build a classic button-based store. This is an ideal solution for catalogs with a small assortment (up to 50 products). It works instantly, does not require JS frontend development, and functions perfectly even with a weak mobile internet connection.
We will implement a complete cycle: catalog browsing, interactive cart management (adding, changing quantity), and proceeding to checkout. We will store user cart data in a database so that it is not lost when the bot restarts.
1. Database Structure
To run the store, we will need three tables: products (products), cart (cart), and orders (orders). Below is the SQL structure for our database:
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)
);The price_stars field stores the price in Telegram Stars. If you plan to accept fiat currency (rubles, dollars), store the price in the smallest units of the currency (cents, kopecks), as required by the Telegram Bot API.
2. Displaying the Product Catalog
Since the size of the callback_data field in Inline buttons is strictly limited to 64 bytes, we must use the most compact prefixes possible for actions:
cat:show:ID— show the product with the specified ID.cat:add:ID— add the product to the cart.cart:view— open the cart.
To send API requests, we will use the telegramApi() function, which is described in detail in chapter 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. Handling Adding to Cart
When a user clicks the "Add to Cart" button, the bot receives a callback_query. We need to update the record in the cart table (using the ON DUPLICATE KEY UPDATE construct), change the button text to the current product quantity, and make sure to call answerCallbackQuery to remove the loading indicator on the button.
To change the keyboard without screen flickering, we will use the editMessageReplyMarkup method.
To send API requests, we will use the telegramApi() function, which is described in detail in chapter 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. Viewing the Cart and Checkout
When clicking "Go to Cart", the bot should display a list of all selected products, their total cost, and a "Pay for Order" button. We will use Telegram Stars to generate the invoice.
To send the invoice, we will use the sendStarsInvoice() function, which is described in detail in chapter 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)
]);
}When the user clicks the "Pay" button, your webhook handler catches callback_data === 'cart:checkout', calculates the total cart amount from the database, and calls the sendInvoice (or sendStarsInvoice) method, passing the unique order ID as payload. After a successful payment (the successful_payment event), the user's cart in the database is cleared, and the order status changes to "Paid".
Next: Mini App + PHP API