Создание полноценного интернет-магазина внутри Telegram Mini Apps (TMA) требует не только отзывчивого интерфейса, но и надежной архитектуры на бэкенде. Клиентское приложение на Vue 3 обеспечивает бесшовный UX, однако доверять фронтенду расчет стоимости заказа нельзя. Любые цены, скидки и остатки должны проходить строгую валидацию на стороне сервера.
В этой статье мы разберем создание надежного гибридного checkout-процесса: от ведения корзины в Pinia с сохранением состояния в localStorage до генерации счетов через Telegram Bot API (включая Telegram Stars) и интеграции внешних платежных шлюзов на PHP.
1. Архитектура корзины на Vue 3: Pinia и LocalStorage
Пользователи Mini Apps часто закрывают приложение или переключаются на другие чаты. Чтобы корзина не сбрасывалась при перезапуске WebApp, состояние Pinia необходимо синхронизировать с localStorage. Ниже представлена реактивная корзина, которая сохраняет состояние и автоматически подсчитывает сумму.
// stores/cart.js
import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', {
state: () => ({
items: JSON.parse(localStorage.getItem('tma_cart_items')) || []
}),
getters: {
totalAmount: (state) => {
return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
totalQuantity: (state) => {
return state.items.reduce((sum, item) => sum + item.quantity, 0);
}
},
actions: {
addToCart(product) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
this.saveToStorage();
},
updateQuantity(productId, quantity) {
const item = this.items.find(i => i.id === productId);
if (item) {
item.quantity = Math.max(1, quantity);
}
this.saveToStorage();
},
removeFromCart(productId) {
this.items = this.items.filter(i => i.id !== productId);
this.saveToStorage();
},
clearCart() {
this.items = [];
this.saveToStorage();
},
saveToStorage() {
localStorage.setItem('tma_cart_items', JSON.stringify(this.items));
}
}
});2. Валидация корзины на PHP-бэкенде
Главное правило безопасности: никогда не верьте ценам, пришедшим от клиента. Злоумышленник может изменить цену товара в локальном хранилище перед отправкой запроса. На бэкенд мы передаем только идентификаторы товаров (id) и их количество (quantity), а также строку инициализации initData для проверки подлинности пользователя.
Ниже представлен скрипт оформления заказа, который проверяет цены по базе данных, генерирует уникальный идентификатор сделки lead_id с помощью криптографически безопасной функции и отправляет счет пользователю через Bot API.
<?php
// checkout.php
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['items']) || empty($input['initData'])) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Неверные входные данные']);
exit;
}
// Имитация базы данных товаров
$catalogDb = [
101 => ['title' => 'Набор стикеров Premium', 'price' => 500],
102 => ['title' => 'Подписка на закрытый канал', 'price' => 1500],
];
$prices = [];
$totalAmount = 0;
$leadId = bin2hex(random_bytes(7)); // Уникальный ID заказа
foreach ($input['items'] as $item) {
$id = (int)($item['id'] ?? 0);
$qty = (int)($item['quantity'] ?? 0);
if (isset($catalogDb[$id]) && $qty > 0) {
$product = $catalogDb[$id];
$prices[] = [
'label' => $product['title'] . " x{$qty}\