feat: интеграция платежей через Telegram Stars API
- обавлена интеграция с эндпоинтом /create-invoice-link для создания инвойсов
- бновлен paymentService для работы с новым API платежей
- обавлено получение информации о пользователе через /users/{user_id}
- бновлен интерфейс профиля для отображения актуальных данных после оплаты
This commit is contained in:
parent
c3e6a6b05d
commit
92424f6902
@ -38,9 +38,20 @@ const Profile: React.FC = () => {
|
|||||||
const pack = tokenPacks.find(p => p.id === packId);
|
const pack = tokenPacks.find(p => p.id === packId);
|
||||||
if (!pack) return;
|
if (!pack) return;
|
||||||
|
|
||||||
paymentService.showBuyTokensPopup(pack, () => {
|
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();
|
window.location.reload();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -63,26 +63,11 @@ export const trackButtonClick = (buttonName: string): void => {
|
|||||||
* @param prompt - Промпт для генерации
|
* @param prompt - Промпт для генерации
|
||||||
*/
|
*/
|
||||||
export const trackStickerGeneration = (prompt: string): void => {
|
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) {
|
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 => {
|
export const trackTokenPurchase = (amount: number, price: number, currency: string): void => {
|
||||||
trackEvent('Покупка токенов', 'purchase', price, currency);
|
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' // Категория для фильтрации
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { baseWorkflow } from '../constants/baseWorkflow';
|
|||||||
import { prompts } from '../assets/prompts';
|
import { prompts } from '../assets/prompts';
|
||||||
import translateService from './translateService';
|
import translateService from './translateService';
|
||||||
import { getCurrentUserId, isTelegramWebAppAvailable } from '../constants/user';
|
import { getCurrentUserId, isTelegramWebAppAvailable } from '../constants/user';
|
||||||
import { trackStickerGeneration } from './analyticsService';
|
import { trackStickerGeneration, trackRejectedPrompt } from './analyticsService';
|
||||||
|
|
||||||
const API_BASE_URL = 'https://stickerserver.gymnasticstuff.uk';
|
const API_BASE_URL = 'https://stickerserver.gymnasticstuff.uk';
|
||||||
|
|
||||||
@ -50,32 +50,62 @@ class GenerationError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Временное решение для работы с балансом токенов
|
// Временное решение для работы с балансом токенов
|
||||||
// В будущем будет заменено на API-запросы
|
// Используется только если не удалось получить баланс с сервера
|
||||||
let mockBalance = 50; // Начальное значение из MOCK_USER
|
let mockBalance = 50; // Начальное значение из MOCK_USER
|
||||||
|
|
||||||
const apiService = {
|
const apiService = {
|
||||||
// Метод для списания токенов
|
// Метод для получения информации о пользователе
|
||||||
async deductTokens(userId: number, amount: number): Promise<boolean> {
|
async getUserInfo(userId: number = getCurrentUserId()): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// В будущем здесь будет API-запрос для списания токенов
|
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
|
||||||
// Пока реализуем локальное списание
|
method: 'GET',
|
||||||
mockBalance -= amount;
|
headers: {
|
||||||
return true;
|
'accept': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch user info');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deducting tokens:', error);
|
console.error('Error fetching user info:', error);
|
||||||
return false;
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Метод для создания ссылки на инвойс
|
||||||
|
async createInvoiceLink(userId: number, starsAmount: number, tokens: number): Promise<string> {
|
||||||
|
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<number> {
|
async getBalance(userId: number = getCurrentUserId()): Promise<number> {
|
||||||
try {
|
try {
|
||||||
// В будущем здесь будет API-запрос для получения баланса
|
const userInfo = await this.getUserInfo(userId);
|
||||||
// Пока возвращаем локальное значение
|
return userInfo.balance || 0;
|
||||||
return mockBalance;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting balance:', error);
|
console.error('Error getting balance:', error);
|
||||||
throw error;
|
// В случае ошибки возвращаем mockBalance для обратной совместимости
|
||||||
|
return mockBalance;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -218,6 +248,9 @@ const apiService = {
|
|||||||
console.error('Не удалось перевести промпт:', translationResult.text);
|
console.error('Не удалось перевести промпт:', translationResult.text);
|
||||||
translationFailed = true;
|
translationFailed = true;
|
||||||
|
|
||||||
|
// Отслеживаем событие неудавшейся генерации
|
||||||
|
trackRejectedPrompt(userPrompt);
|
||||||
|
|
||||||
// Не продолжаем генерацию, возвращаем ошибку с сообщением
|
// Не продолжаем генерацию, возвращаем ошибку с сообщением
|
||||||
return {
|
return {
|
||||||
translationFailed: true,
|
translationFailed: true,
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { TokenPack } from '../constants/tokenPacks';
|
import { TokenPack } from '../constants/tokenPacks';
|
||||||
|
import apiService from '../services/api';
|
||||||
|
import { getCurrentUserId } from '../constants/user';
|
||||||
|
|
||||||
export const paymentService = {
|
export const paymentService = {
|
||||||
showBuyTokensPopup: (pack: TokenPack, onSuccess?: () => void) => {
|
showBuyTokensPopup: async (pack: TokenPack, onSuccess?: (userData?: any) => void) => {
|
||||||
// Проверяем наличие Telegram WebApp
|
// Проверяем наличие Telegram WebApp
|
||||||
if (!window.Telegram?.WebApp) {
|
if (!window.Telegram?.WebApp) {
|
||||||
console.error('Telegram WebApp не доступен');
|
console.error('Telegram WebApp не доступен');
|
||||||
@ -9,6 +11,7 @@ export const paymentService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const webApp = window.Telegram.WebApp;
|
const webApp = window.Telegram.WebApp;
|
||||||
|
const userId = getCurrentUserId();
|
||||||
|
|
||||||
// Открываем окно оплаты Telegram
|
// Открываем окно оплаты Telegram
|
||||||
webApp.showPopup({
|
webApp.showPopup({
|
||||||
@ -25,14 +28,40 @@ export const paymentService = {
|
|||||||
text: 'Отмена'
|
text: 'Отмена'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, (buttonId: string) => {
|
}, async (buttonId: string) => {
|
||||||
if (buttonId === 'buy') {
|
if (buttonId === 'buy') {
|
||||||
|
try {
|
||||||
|
// Получаем ссылку на инвойс от бэкенда
|
||||||
|
const invoiceLink = await apiService.createInvoiceLink(
|
||||||
|
userId,
|
||||||
|
pack.price,
|
||||||
|
pack.tokens + pack.bonusTokens
|
||||||
|
);
|
||||||
|
|
||||||
// Открываем встроенный платеж Telegram
|
// Открываем встроенный платеж Telegram
|
||||||
webApp.openInvoice(`sticker_tokens_${pack.id}`, (status: 'paid' | 'cancelled' | 'failed' | 'pending') => {
|
webApp.openInvoice(invoiceLink, async (status: 'paid' | 'cancelled' | 'failed' | 'pending') => {
|
||||||
if (status === 'paid' && onSuccess) {
|
if (status === 'paid') {
|
||||||
|
try {
|
||||||
|
// Получаем обновленную информацию о пользователе
|
||||||
|
const userData = await apiService.getUserInfo(userId);
|
||||||
|
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess(userData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении данных пользователя:', error);
|
||||||
|
|
||||||
|
// Даже если не удалось получить данные, вызываем onSuccess
|
||||||
|
if (onSuccess) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при создании инвойса:', error);
|
||||||
|
webApp.showAlert('Произошла ошибка при создании платежа. Пожалуйста, попробуйте позже.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user