From 92424f6902257f9aa06c05ca61afbe5ef8dc4a17 Mon Sep 17 00:00:00 2001 From: kazachilo Date: Thu, 27 Mar 2025 14:51:00 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BB=D0=B0=D1=82=D0=B5=D0=B6?= =?UTF-8?q?=D0=B5=D0=B9=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Telegram=20Star?= =?UTF-8?q?s=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - обавлена интеграция с эндпоинтом /create-invoice-link для создания инвойсов - бновлен paymentService для работы с новым API платежей - обавлено получение информации о пользователе через /users/{user_id} - бновлен интерфейс профиля для отображения актуальных данных после оплаты --- src/screens/Profile.tsx | 17 +++++++-- src/services/analyticsService.ts | 36 +++++++++--------- src/services/api.ts | 63 ++++++++++++++++++++++++-------- src/services/paymentService.ts | 45 +++++++++++++++++++---- 4 files changed, 116 insertions(+), 45 deletions(-) diff --git a/src/screens/Profile.tsx b/src/screens/Profile.tsx index c6b76d8..f6e3c32 100644 --- a/src/screens/Profile.tsx +++ b/src/screens/Profile.tsx @@ -38,9 +38,20 @@ const Profile: React.FC = () => { const pack = tokenPacks.find(p => p.id === packId); if (!pack) return; - paymentService.showBuyTokensPopup(pack, () => { - // Обновляем данные после успешной оплаты - window.location.reload(); + paymentService.showBuyTokensPopup(pack, (userData) => { + if (userData) { + // Обновляем данные на основе полученной информации + if (userData.stickers_count !== undefined) { + setStickersCount(userData.stickers_count); + } + if (userData.packs_count !== undefined) { + setPacksCount(userData.packs_count); + } + // Можно также обновить другие данные, если они есть в ответе + } else { + // Если данные не получены, просто перезагружаем страницу + window.location.reload(); + } }); }; diff --git a/src/services/analyticsService.ts b/src/services/analyticsService.ts index e36c889..118e69d 100644 --- a/src/services/analyticsService.ts +++ b/src/services/analyticsService.ts @@ -63,26 +63,11 @@ export const trackButtonClick = (buttonName: string): void => { * @param prompt - Промпт для генерации */ export const trackStickerGeneration = (prompt: string): void => { - // Создаем объект события с дополнительными данными - const eventData: { - event: string; - category: string; - value_num: number; - prompt?: string; - } = { - event: 'Генерация стикера', - category: 'generation', - value_num: 1 - }; - - // Добавляем промпт в данные события, если он предоставлен - if (prompt) { - eventData.prompt = prompt; - } - - // Отправляем событие напрямую, чтобы включить дополнительные данные if (typeof window !== 'undefined' && window.graspil) { - window.graspil.push(eventData); + window.graspil.push({ + event: prompt, // Сам промпт как название события + category: 'generation' // Категория для фильтрации + }); } }; @@ -103,3 +88,16 @@ export const trackStickerPackCreation = (packName: string): void => { export const trackTokenPurchase = (amount: number, price: number, currency: string): void => { trackEvent('Покупка токенов', 'purchase', price, currency); }; + +/** + * Отслеживает неудавшуюся генерацию стикера из-за непрошедшего фильтр промпта + * @param originalPrompt - Оригинальный промпт пользователя + */ +export const trackRejectedPrompt = (originalPrompt: string): void => { + if (typeof window !== 'undefined' && window.graspil) { + window.graspil.push({ + event: `Не принятый промпт: ${originalPrompt}`, // Оригинальный промпт как название события + category: 'rejected_generation' // Категория для фильтрации + }); + } +}; diff --git a/src/services/api.ts b/src/services/api.ts index ab978e9..938661b 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -3,7 +3,7 @@ import { baseWorkflow } from '../constants/baseWorkflow'; import { prompts } from '../assets/prompts'; import translateService from './translateService'; import { getCurrentUserId, isTelegramWebAppAvailable } from '../constants/user'; -import { trackStickerGeneration } from './analyticsService'; +import { trackStickerGeneration, trackRejectedPrompt } from './analyticsService'; const API_BASE_URL = 'https://stickerserver.gymnasticstuff.uk'; @@ -50,32 +50,62 @@ class GenerationError extends Error { } // Временное решение для работы с балансом токенов -// В будущем будет заменено на API-запросы +// Используется только если не удалось получить баланс с сервера let mockBalance = 50; // Начальное значение из MOCK_USER const apiService = { - // Метод для списания токенов - async deductTokens(userId: number, amount: number): Promise { + // Метод для получения информации о пользователе + async getUserInfo(userId: number = getCurrentUserId()): Promise { try { - // В будущем здесь будет API-запрос для списания токенов - // Пока реализуем локальное списание - mockBalance -= amount; - return true; + const response = await fetch(`${API_BASE_URL}/users/${userId}`, { + method: 'GET', + headers: { + 'accept': 'application/json', + } + }); + + if (!response.ok) { + throw new Error('Failed to fetch user info'); + } + + return await response.json(); } catch (error) { - console.error('Error deducting tokens:', error); - return false; + console.error('Error fetching user info:', error); + throw error; + } + }, + + // Метод для создания ссылки на инвойс + async createInvoiceLink(userId: number, starsAmount: number, tokens: number): Promise { + try { + const response = await fetch(`${API_BASE_URL}/create-invoice-link?user_id=${userId}&stars_amount=${starsAmount}&tokens=${tokens}`, { + method: 'POST', + headers: { + 'accept': 'application/json', + } + }); + + if (!response.ok) { + throw new Error('Failed to create invoice link'); + } + + // Предполагаем, что бэкенд возвращает строку с URL инвойса + return await response.text(); + } catch (error) { + console.error('Error creating invoice link:', error); + throw error; } }, // Метод для получения текущего баланса - async getBalance(userId: number): Promise { + async getBalance(userId: number = getCurrentUserId()): Promise { try { - // В будущем здесь будет API-запрос для получения баланса - // Пока возвращаем локальное значение - return mockBalance; + const userInfo = await this.getUserInfo(userId); + return userInfo.balance || 0; } catch (error) { console.error('Error getting balance:', error); - throw error; + // В случае ошибки возвращаем mockBalance для обратной совместимости + return mockBalance; } }, @@ -218,6 +248,9 @@ const apiService = { console.error('Не удалось перевести промпт:', translationResult.text); translationFailed = true; + // Отслеживаем событие неудавшейся генерации + trackRejectedPrompt(userPrompt); + // Не продолжаем генерацию, возвращаем ошибку с сообщением return { translationFailed: true, diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 7a98c99..9ec63eb 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -1,7 +1,9 @@ import { TokenPack } from '../constants/tokenPacks'; +import apiService from '../services/api'; +import { getCurrentUserId } from '../constants/user'; export const paymentService = { - showBuyTokensPopup: (pack: TokenPack, onSuccess?: () => void) => { + showBuyTokensPopup: async (pack: TokenPack, onSuccess?: (userData?: any) => void) => { // Проверяем наличие Telegram WebApp if (!window.Telegram?.WebApp) { console.error('Telegram WebApp не доступен'); @@ -9,6 +11,7 @@ export const paymentService = { } const webApp = window.Telegram.WebApp; + const userId = getCurrentUserId(); // Открываем окно оплаты Telegram webApp.showPopup({ @@ -25,14 +28,40 @@ export const paymentService = { text: 'Отмена' } ] - }, (buttonId: string) => { + }, async (buttonId: string) => { if (buttonId === 'buy') { - // Открываем встроенный платеж Telegram - webApp.openInvoice(`sticker_tokens_${pack.id}`, (status: 'paid' | 'cancelled' | 'failed' | 'pending') => { - if (status === 'paid' && onSuccess) { - onSuccess(); - } - }); + try { + // Получаем ссылку на инвойс от бэкенда + const invoiceLink = await apiService.createInvoiceLink( + userId, + pack.price, + pack.tokens + pack.bonusTokens + ); + + // Открываем встроенный платеж Telegram + webApp.openInvoice(invoiceLink, async (status: 'paid' | 'cancelled' | 'failed' | 'pending') => { + if (status === 'paid') { + try { + // Получаем обновленную информацию о пользователе + const userData = await apiService.getUserInfo(userId); + + if (onSuccess) { + onSuccess(userData); + } + } catch (error) { + console.error('Ошибка при получении данных пользователя:', error); + + // Даже если не удалось получить данные, вызываем onSuccess + if (onSuccess) { + onSuccess(); + } + } + } + }); + } catch (error) { + console.error('Ошибка при создании инвойса:', error); + webApp.showAlert('Произошла ошибка при создании платежа. Пожалуйста, попробуйте позже.'); + } } }); }