From 9d9e0e80adaa8c32940193edfe52b2d95d1e4f41 Mon Sep 17 00:00:00 2001 From: NW Date: Sat, 14 Dec 2024 23:12:36 +0000 Subject: [PATCH] Bug update function --- src/config/database.js | 31 +- .../userHandlers/userProductHandler.js | 207 ++++++++---- .../userHandlers/userPurchaseHandler.js | 309 ++++++++++++------ src/index.js | 3 + src/services/categoryService.js | 11 +- src/services/locationService.js | 14 +- src/services/productService.js | 11 + src/services/purchaseService.js | 68 ++-- src/services/userService.js | 21 ++ 9 files changed, 474 insertions(+), 201 deletions(-) diff --git a/src/config/database.js b/src/config/database.js index 8bb8c60..3979b3b 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -183,19 +183,30 @@ const initDb = async () => { // Create purchases table await db.runAsync(` CREATE TABLE IF NOT EXISTS purchases ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - wallet_type TEXT NOT NULL, - tx_hash TEXT NOT NULL, - quantity INTEGER NOT NULL CHECK (quantity > 0), - total_price REAL NOT NULL CHECK (total_price > 0), - purchase_date DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + wallet_type TEXT NOT NULL, + tx_hash TEXT NOT NULL, + quantity INTEGER NOT NULL CHECK (quantity > 0), + total_price REAL NOT NULL CHECK (total_price > 0), + purchase_date DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT DEFAULT 'pending', + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE ) `); + // Проверка наличия поля status в таблице purchases + const statusExists = await checkColumnExists('purchases', 'status'); + if (!statusExists) { + await db.runAsync(` + ALTER TABLE purchases + ADD COLUMN status TEXT DEFAULT 'pending' + `); + console.log('Column status added to purchases table'); + } + // Create locations table await db.runAsync(` CREATE TABLE IF NOT EXISTS locations ( diff --git a/src/handlers/userHandlers/userProductHandler.js b/src/handlers/userHandlers/userProductHandler.js index 603f809..20bc30c 100644 --- a/src/handlers/userHandlers/userProductHandler.js +++ b/src/handlers/userHandlers/userProductHandler.js @@ -176,15 +176,29 @@ export default class UserProductHandler { const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_'); try { + // Удаляем текущее сообщение + await bot.deleteMessage(chatId, messageId); + + // Получаем состояние пользователя + const state = userStates.get(chatId); + + // Удаляем сообщение с фотографией, если оно существует + if (state && state.photoMessageId) { + try { + await bot.deleteMessage(chatId, state.photoMessageId); + } catch (error) { + console.error('Error deleting photo message:', error); + } + } + // Получаем товары для выбранной категории const products = await ProductService.getProductsByCategoryId(categoryId); if (products.length === 0) { - await bot.editMessageText( + await bot.sendMessage( + chatId, 'No products available in this category.', { - chat_id: chatId, - message_id: messageId, reply_markup: { inline_keyboard: [[ { text: '« Back', callback_data: `shop_district_${locationId}` } @@ -211,14 +225,16 @@ export default class UserProductHandler { ]); // Отправляем сообщение с товарами - await bot.editMessageText( + await bot.sendMessage( + chatId, 'Select a product:', { - chat_id: chatId, - message_id: messageId, reply_markup: keyboard } ); + + // Удаляем состояние пользователя + userStates.delete(chatId); } catch (error) { console.error('Error in handleCategorySelection:', error); await bot.sendMessage(chatId, 'Error loading products. Please try again.'); @@ -290,71 +306,82 @@ export default class UserProductHandler { const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const productId = callbackQuery.data.replace('shop_product_', ''); - + try { const product = await ProductService.getDetailedProductById(productId); - + if (!product) { throw new Error('Product not found'); } - - // Delete the previous message + + // Удаляем предыдущее сообщение await bot.deleteMessage(chatId, messageId); - + + // Получаем состояние пользователя + const state = userStates.get(chatId); + + // Удаляем сообщение с фотографией, если оно существует + if (state?.photoMessageId) { + try { + await bot.deleteMessage(chatId, state.photoMessageId); + } catch (error) { + console.error('Error deleting photo message:', error); + } + } + const message = ` -📦 ${product.name} - -💰 Price: $${product.price} -📝 Description: ${product.description} -📦 Available: ${product.quantity_in_stock} pcs - -Category: ${product.category_name} -`; - - let photoMessageId = null; - - // First send the photo if it exists + 📦 ${product.name} + + 💰 Price: $${product.price} + 📝 Description: ${product.description} + 📦 Available: ${product.quantity_in_stock} pcs + + Category: ${product.category_name} + `; + + // Отправляем фото, если оно существует let photoMessage; if (product.photo_url) { try { - photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'}); + photoMessage = await bot.sendPhoto(chatId, product.photo_url, { caption: 'Public photo' }); } catch (e) { - photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'}) + photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Public photo' }); } } - + const keyboard = { inline_keyboard: [ - [{text: '🛒 Buy Now', callback_data: `buy_product_${productId}`}], + [{ text: '🛒 Buy Now', callback_data: `buy_product_${productId}` }], [ { text: '➖', callback_data: `decrease_quantity_${productId}`, - callback_game: {} // Initially disabled as quantity starts at 1 + callback_game: {} // Изначально отключено, так как количество начинается с 1 }, - {text: '1', callback_data: 'current_quantity'}, + { text: '1', callback_data: 'current_quantity' }, { text: '➕', callback_data: `increase_quantity_${productId}`, - callback_game: product.quantity_in_stock <= 1 ? {} : null // Disabled if stock is 1 or less + callback_game: product.quantity_in_stock <= 1 ? {} : null // Отключено, если остаток 1 или меньше } ], [{ text: `« Back ${product.category_name}`, callback_data: `shop_category_${product.location_id}_${product.category_id}` }] // Возврат к категории ] }; - - // Then send the message with controls - await bot.sendMessage(chatId, message, { + + // Отправляем сообщение с кнопками + const productMessage = await bot.sendMessage(chatId, message, { reply_markup: keyboard, parse_mode: 'HTML' }); - - // Store the current quantity and photo message ID in user state + + // Сохраняем ID сообщения с фотографией и ID сообщения с товаром в состояние пользователя userStates.set(chatId, { action: 'buying_product', productId, quantity: 1, - photoMessageId + photoMessageId: photoMessage ? photoMessage.message_id : null, + productMessageId: productMessage.message_id }); } catch (error) { console.error('Error in handleProductSelection:', error); @@ -502,8 +529,9 @@ Category: ${product.category_name} const quantity = state?.quantity || 1; const totalPrice = product.price * quantity; - // Проверка баланса пользователя + // Получение баланса пользователя const userBalance = await UserService.getUserBalance(user.id); + if (userBalance < totalPrice) { await bot.sendMessage( chatId, @@ -545,11 +573,12 @@ Category: ${product.category_name} const keyboard = { inline_keyboard: [ [{ text: `Pay`, callback_data: `pay_with_main_${productId}_${quantity}` }], - [{ text: '« Cancel', callback_data: `shop_product_${productId}` }] + [{ text: '« Cancel', callback_data: `shop_product_${productId}` }] // Кнопка "Back" ] }; - await bot.editMessageText( + // Отправка сообщения с кнопками + const purchaseMessage = await bot.editMessageText( `🛒 Purchase Summary:\n\n` + `Product: ${product.name}\n` + `Quantity: ${quantity}\n` + @@ -560,6 +589,13 @@ Category: ${product.category_name} reply_markup: keyboard } ); + + // Сохранение ID сообщения с фотографией в состояние пользователя + userStates.set(chatId, { + ...state, + photoMessageId: state?.photoMessageId || null, + purchaseMessageId: purchaseMessage.message_id + }); } catch (error) { console.error('Error in handleBuyProduct:', error); await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); @@ -571,23 +607,23 @@ Category: ${product.category_name} const telegramId = callbackQuery.from.id; const [walletType, productId, quantity] = callbackQuery.data.replace('pay_with_', '').split('_'); const state = userStates.get(chatId); - + try { await UserService.recalculateUserBalanceByTelegramId(telegramId); - const user = await UserService.getUserByTelegramId(telegramId) - + const user = await UserService.getUserByTelegramId(telegramId); + if (!user) { throw new Error('User not found'); } - + const product = await ProductService.getProductById(productId); if (!product) { throw new Error('Product not found'); } - + const totalPrice = product.price * quantity; const balance = user.total_balance + user.bonus_balance; - + if (totalPrice > balance) { userStates.delete(chatId); await bot.editMessageText(`Not enough money`, { @@ -596,45 +632,74 @@ Category: ${product.category_name} }); return; } - - await PurchaseService.createPurchase(user.id, product.id, walletType, quantity, totalPrice) - + + // Проверка наличия товара + if (product.quantity_in_stock < quantity) { + await bot.sendMessage(chatId, `❌ Not enough items in stock. Only ${product.quantity_in_stock} available.`); + return; + } + + // Создаем покупку и получаем её ID + const purchaseId = await PurchaseService.createPurchase(user.id, productId, walletType, quantity, totalPrice); + + // Уменьшаем количество товара в базе данных + await ProductService.decreaseProductQuantity(productId, quantity); + + // Извлекаем данные о локации + const location = await LocationService.getLocationById(product.location_id); + const category = await CategoryService.getCategoryById(product.category_id); + + // Удаляем сообщение с Public Photo, если оно существует + if (state?.photoMessageId) { + try { + await bot.deleteMessage(chatId, state.photoMessageId); + } catch (error) { + console.error('Error deleting Public Photo message:', error); + } + } + + // Отправляем Hidden Photo let hiddenPhotoMessage; if (product.hidden_photo_url) { try { - hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'}); + hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, { caption: 'Hidden photo' }); } catch (e) { - hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'}) + hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' }); } } - + const message = ` -📦 Product Details: - -Name: ${product.name} -Price: $${product.price} -Description: ${product.description} -Stock: ${product.quantity_in_stock} -Location: ${product.country}, ${product.city}, ${product.district} -Category: ${product.category_name} - -🔒 Private Information: -${product.private_data} -Hidden Location: ${product.hidden_description} -Coordinates: ${product.hidden_coordinates} -`; - + 📦 Purchase Details: + Name: ${product.name} + Quantity: ${quantity} + Total: $${totalPrice} + Location: ${location?.country || 'N/A'}, ${location?.city || 'N/A'}, ${location?.district || 'N/A'} + Category: ${category?.name || 'N/A'} + + 🔒 Private Information: + ${product.private_data || 'N/A'} + Hidden Location: ${product.hidden_description || 'N/A'} + Coordinates: ${product.hidden_coordinates || 'N/A'} + `; + const keyboard = { inline_keyboard: [ - [{text: "I've got it!", callback_data: "Asdasdasd"}], - [{text: "Contact support", url: config.SUPPORT_LINK}] + [{ text: 'View new purchase', callback_data: `view_purchase_${purchaseId}` }], // Переход к покупке + [{ text: "Contact support", url: config.SUPPORT_LINK }] // Сохранение кнопки "Contact support" ] }; - - await bot.sendMessage(chatId, message, {reply_markup: keyboard}); + + await bot.sendMessage(chatId, message, { reply_markup: keyboard }); await bot.deleteMessage(chatId, callbackQuery.message.message_id); + + // Сохраняем ID сообщения с Hidden Photo в состояние пользователя + userStates.set(chatId, { + action: 'viewing_purchase', + purchaseId, + hiddenPhotoMessageId: hiddenPhotoMessage ? hiddenPhotoMessage.message_id : null + }); } catch (error) { - console.error('Error in handleBuyProduct:', error); + console.error('Error in handlePay:', error); await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); } } diff --git a/src/handlers/userHandlers/userPurchaseHandler.js b/src/handlers/userHandlers/userPurchaseHandler.js index ae7e5f0..1640887 100644 --- a/src/handlers/userHandlers/userPurchaseHandler.js +++ b/src/handlers/userHandlers/userPurchaseHandler.js @@ -1,160 +1,279 @@ import config from "../../config/config.js"; import PurchaseService from "../../services/purchaseService.js"; import UserService from "../../services/userService.js"; -import bot from "../../context/bot.js"; +import LocationService from "../../services/locationService.js"; import ProductService from "../../services/productService.js"; +import CategoryService from "../../services/categoryService.js"; +import bot from "../../context/bot.js"; +import userStates from "../../context/userStates.js"; export default class UserPurchaseHandler { static async viewPurchasePage(userId, page) { try { - const limit = 10; - const offset = (page || 0) * limit; - - const previousPage = page > 0 ? page - 1 : 0; - const nextPage = page + 1; - + const limit = 10; // Количество покупок на странице + const offset = page * limit; + + // Получаем покупки пользователя с учетом пагинации const purchases = await PurchaseService.getPurchasesByUserId(userId, limit, offset); - - if ((purchases.length === 0) && (page == 0)) { + + // Получаем общее количество покупок пользователя + const totalPurchases = await PurchaseService.getTotalPurchasesByUserId(userId); + + // Вычисляем общее количество страниц + const totalPages = Math.ceil(totalPurchases / limit); + + // Если покупок нет, возвращаем сообщение о пустом архиве + if (totalPurchases === 0) { return { - text: 'You haven\'t made any purchases yet.', - markup: [[ - {text: '🛍 Browse Products', callback_data: 'shop_start'} - ]] - } + text: 'Your purchase history is empty.', + markup: { + inline_keyboard: [ + [{ text: '🛍 Browse Products', callback_data: 'shop_start' }] + ] + } + }; } - - if ((purchases.length === 0) && (page > 0)) { - return await this.viewPurchasePage(userId, previousPage); + + // Если покупок нет на текущей странице, но это не первая страница, переходим на предыдущую страницу + if (purchases.length === 0 && page > 0) { + return await this.viewPurchasePage(userId, page - 1); } - + const keyboard = { inline_keyboard: [ ...purchases.map(item => [{ text: `${item.product_name} [${new Date(item.purchase_date).toLocaleString()}]`, callback_data: `view_purchase_${item.id}` }]), + [ + { + text: page > 0 ? `« Back (Page ${page})` : '« Back', + callback_data: page > 0 ? `list_purchases_${page - 1}` : 'no_action', // Если на первой странице, то "no_action" + hide: page === 0 // Скрываем кнопку "Назад", если на первой странице + }, + { + text: `Page ${page + 1} of ${totalPages}`, + callback_data: 'current_page' + }, + { + text: page < totalPages - 1 ? `Next » (Page ${page + 2})` : 'Next »', + callback_data: page < totalPages - 1 ? `list_purchases_${page + 1}` : 'no_action', // Если на последней странице, то "no_action" + hide: page === totalPages - 1 // Скрываем кнопку "Вперед", если на последней странице + } + ] ] }; - - keyboard.inline_keyboard.push([ - {text: `«`, callback_data: `list_purchases_${previousPage}`}, - {text: `»`, callback_data: `list_purchases_${nextPage}`}, - ]); - - keyboard.inline_keyboard.push([ - {text: '🛍 Browse Products', callback_data: 'shop_start'} - ]); - + return { - text: `📦 Select purchase to view detailed information:`, + text: `📦 Select purchase to view detailed information (Page ${page + 1} of ${totalPages}):`, markup: keyboard - } - + }; } catch (error) { - console.error('Error in showPurchases:', error); - return {text: 'Error loading purchase history. Please try again.'}; + console.error('Error in viewPurchasePage:', error); + return { text: 'Error loading purchase history. Please try again.' }; } } static async handlePurchaseListPage(callbackQuery) { const telegramId = callbackQuery.from.id; const chatId = callbackQuery.message.chat.id; - - const page = callbackQuery.data.replace('list_purchases_', ''); - + const page = parseInt(callbackQuery.data.replace('list_purchases_', '')); + try { const user = await UserService.getUserByTelegramId(telegramId); - + if (!user) { await bot.sendMessage(chatId, 'User not found.'); return; } - - const {text, markup} = await this.viewPurchasePage(user.id, parseInt(page)); + + // Удаляем сообщение с Hidden Photo, если оно существует + const state = userStates.get(chatId); + if (state?.hiddenPhotoMessageId) { + try { + await bot.deleteMessage(chatId, state.hiddenPhotoMessageId); + } catch (error) { + console.error('Error deleting Hidden Photo message:', error); + } + } + + const { text, markup } = await this.viewPurchasePage(user.id, page); + await bot.editMessageText(text, { chat_id: chatId, message_id: callbackQuery.message.message_id, reply_markup: markup, - parse_mode: 'Markdown', + parse_mode: 'Markdown' }); + + // Удаляем состояние пользователя + userStates.delete(chatId); } catch (e) { - return; + console.error('Error in handlePurchaseListPage:', e); + await bot.sendMessage(chatId, 'Error loading purchase history. Please try again.'); } } static async showPurchases(msg) { const chatId = msg.chat.id; const telegramId = msg.from.id; - + try { - const user = await UserService.getUserByTelegramId(telegramId); - + if (!user) { await bot.sendMessage(chatId, 'User not found.'); return; } - - const {text, markup} = await this.viewPurchasePage(user.id, 0); - - await bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'}); + + const { text, markup } = await this.viewPurchasePage(user.id, 0); + + await bot.sendMessage(chatId, text, { reply_markup: markup, parse_mode: 'Markdown' }); } catch (error) { - console.error('Error in handleSubcategorySelection:', error); - await bot.sendMessage(chatId, 'Error loading products. Please try again.'); + console.error('Error in showPurchases:', error); + await bot.sendMessage(chatId, 'Error loading purchase history. Please try again.'); } } static async viewPurchase(callbackQuery) { const chatId = callbackQuery.message.chat.id; const purchaseId = callbackQuery.data.replace('view_purchase_', ''); - - const purchase = await PurchaseService.getPurchaseById(purchaseId); - - if (!purchase) { - await bot.sendMessage(chatId, "No such purchase"); - return; - } - - const product = await ProductService.getProductById(purchase.product_id) - - if (!product) { - await bot.sendMessage(chatId, "No such product"); - return; - } - - let hiddenPhotoMessage; - if (product.hidden_photo_url) { - try { - hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'}); - } catch (e) { - hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'}) + + try { + // Получаем данные покупки + const purchase = await PurchaseService.getPurchaseById(purchaseId); + if (!purchase) { + await bot.sendMessage(chatId, "No such purchase"); + return; } + + // Получаем данные товара по product_id + const product = await ProductService.getProductById(purchase.product_id); + if (!product) { + await bot.sendMessage(chatId, "No such product"); + return; + } + + // Получаем данные локации по location_id + const location = await LocationService.getLocationById(product.location_id); + + // Получаем данные категории по category_id + const category = await CategoryService.getCategoryById(product.category_id); + + // Удаляем старое сообщение с Hidden Photo, если оно существует + const state = userStates.get(chatId); + if (state?.hiddenPhotoMessageId) { + try { + await bot.deleteMessage(chatId, state.hiddenPhotoMessageId); + } catch (error) { + console.error('Error deleting Hidden Photo message:', error); + } + } + + // Отправляем Hidden Photo + let hiddenPhotoMessage; + if (product.hidden_photo_url) { + try { + hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, { caption: 'Hidden photo' }); + } catch (e) { + hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' }); + } + } + + // Формируем сообщение с деталями покупки + const message = ` + 📦 Purchase Details: + Name: ${product.name || 'N/A'} + Quantity: ${purchase.quantity} + Total: $${purchase.total_price} + Location: ${location?.country || 'N/A'}, ${location?.city || 'N/A'}, ${location?.district || 'N/A'} + Category: ${category?.name || 'N/A'} + + 🔒 Private Information: + ${product.private_data || 'N/A'} + Hidden Location: ${product.hidden_description || 'N/A'} + Coordinates: ${product.hidden_coordinates || 'N/A'} + `; + + // Создаем клавиатуру с кнопками + const keyboard = { + inline_keyboard: [ + // Проверяем статус покупки перед добавлением кнопки "I've got it!" + ...(purchase.status !== 'received' ? [[{ text: "I've got it!", callback_data: `confirm_received_${purchaseId}` }]] : []), + [{ text: "« Back to Purchase List", callback_data: `list_purchases_0` }], // Кнопка "Назад к списку покупок" + [{ text: "Contact support", url: config.SUPPORT_LINK }] + ] + }; + + // Отправляем сообщение с деталями покупки + await bot.sendMessage(chatId, message, { reply_markup: keyboard }); + + // Удаляем предыдущее сообщение + await bot.deleteMessage(chatId, callbackQuery.message.message_id); + + // Сохраняем ID сообщения с Hidden Photo в состояние пользователя + userStates.set(chatId, { + action: 'viewing_purchase', + purchaseId, + hiddenPhotoMessageId: hiddenPhotoMessage ? hiddenPhotoMessage.message_id : null + }); + } catch (error) { + console.error('Error in viewPurchase:', error); + await bot.sendMessage(chatId, 'Error loading purchase details. Please try again.'); } + } - const message = ` -📦 Purchase Details: -Name: ${purchase.product_name} -Quantity: ${purchase.quantity} -Total: $${purchase.total_price} -Location: ${purchase.country}, ${purchase.city} -Payment: ${purchase.wallet_type} -Date: ${new Date(purchase.purchase_date).toLocaleString()} - -🔒 Private Information: -${product.private_data} -Hidden Location: ${product.hidden_description} -Coordinates: ${product.hidden_coordinates} -`; - - const keyboard = { - inline_keyboard: [ - [{text: "I've got it!", callback_data: "Asdasdasd"}], - [{text: "Contact support", url: config.SUPPORT_LINK}] - ] - }; - - await bot.sendMessage(chatId, message, {reply_markup: keyboard}); - await bot.deleteMessage(chatId, callbackQuery.message.message_id); + static async handleConfirmReceived(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const purchaseId = callbackQuery.data.replace('confirm_received_', ''); + + try { + // Проверяем, подтверждена ли покупка уже + const purchase = await PurchaseService.getPurchaseById(purchaseId); + if (!purchase) { + await bot.sendMessage(chatId, "Purchase not found."); + return; + } + + if (purchase.status === 'received') { + await bot.sendMessage(chatId, "This purchase has already been confirmed."); + return; + } + + // Обновляем статус покупки в базе данных + await PurchaseService.updatePurchaseStatus(purchaseId, 'received'); + + // Отправляем уведомление администраторам + const adminIds = config.ADMIN_IDS; // Используем массив ADMIN_IDS + for (const adminId of adminIds) { + await bot.sendMessage(adminId, `User ${callbackQuery.from.username} has confirmed receiving purchase #${purchaseId}.`); + } + + // Уведомляем пользователя + await bot.sendMessage(chatId, "Thank you! Your purchase has been marked as received."); + + // Удаляем сообщение с карточкой товара + await bot.deleteMessage(chatId, messageId); + + // Удаляем Hidden Photo, если оно существует + const state = userStates.get(chatId); + if (state?.hiddenPhotoMessageId) { + try { + await bot.deleteMessage(chatId, state.hiddenPhotoMessageId); + } catch (error) { + console.error('Error deleting Hidden Photo message:', error); + } + } + + // Удаляем состояние пользователя + userStates.delete(chatId); + + // Открываем список покупок для пользователя + await this.showPurchases({ chat: { id: chatId }, from: { id: callbackQuery.from.id } }); + } catch (error) { + console.error('Error in handleConfirmReceived:', error); + await bot.sendMessage(chatId, 'Error confirming receipt. Please try again.'); + } } } diff --git a/src/index.js b/src/index.js index 05f3347..0918fd1 100644 --- a/src/index.js +++ b/src/index.js @@ -233,6 +233,9 @@ bot.on('callback_query', async (callbackQuery) => { } else if (action.startsWith('view_purchase_')) { logDebug(action, 'viewPurchase'); await userPurchaseHandler.viewPurchase(callbackQuery); + } else if (action.startsWith('confirm_received_')) { + logDebug(action, 'handleConfirmReceived'); + await userPurchaseHandler.handleConfirmReceived(callbackQuery); } // Admin location management else if (action === 'add_location') { diff --git a/src/services/categoryService.js b/src/services/categoryService.js index bc6c803..c055d03 100644 --- a/src/services/categoryService.js +++ b/src/services/categoryService.js @@ -16,7 +16,16 @@ class CategoryService { } static async getCategoryById(categoryId) { - return await db.getAsync('SELECT id, name FROM categories WHERE id = ?', [categoryId]); + try { + const category = await db.getAsync( + 'SELECT * FROM categories WHERE id = ?', + [categoryId] + ); + return category; + } catch (error) { + console.error('Error fetching category by ID:', error); + throw new Error('Failed to fetch category'); + } } static async getSubcategoryById(subcategoryId) { diff --git a/src/services/locationService.js b/src/services/locationService.js index 4c47adb..39bd595 100644 --- a/src/services/locationService.js +++ b/src/services/locationService.js @@ -27,10 +27,16 @@ class LocationService { } static async getLocationById(locationId) { - return await db.getAsync( - 'SELECT country, city, district FROM locations WHERE id = ?', - [locationId] - ); + try { + const location = await db.getAsync( + 'SELECT * FROM locations WHERE id = ?', + [locationId] + ); + return location; + } catch (error) { + console.error('Error fetching location by ID:', error); + throw new Error('Failed to fetch location'); + } } } diff --git a/src/services/productService.js b/src/services/productService.js index 06632ad..e3913e9 100644 --- a/src/services/productService.js +++ b/src/services/productService.js @@ -42,6 +42,17 @@ class ProductService { ); } + static async decreaseProductQuantity(productId, quantity) { + try { + await db.runAsync( + 'UPDATE products SET quantity_in_stock = quantity_in_stock - ? WHERE id = ?', + [quantity, productId] + ); + } catch (error) { + console.error('Error decreasing product quantity:', error); + throw new Error('Failed to update product quantity'); + } + } } export default ProductService \ No newline at end of file diff --git a/src/services/purchaseService.js b/src/services/purchaseService.js index 1da4d3b..8f02333 100644 --- a/src/services/purchaseService.js +++ b/src/services/purchaseService.js @@ -1,5 +1,5 @@ import db from "../config/database.js"; - +import CryptoJS from "crypto-js"; // Импортируем библиотеку crypto-js class PurchaseService { static async getPurchasesByUserId(userId, limit, offset) { try { @@ -28,31 +28,59 @@ class PurchaseService { static async getPurchaseById(purchaseId) { try { - return await db.getAsync(` - SELECT - p.*, - pr.name as product_name, - pr.description, - l.country, - l.city, - l.district - FROM purchases p - JOIN products pr ON p.product_id = pr.id - JOIN locations l ON pr.location_id = l.id - WHERE p.id = ? - `, [purchaseId]); + return await db.getAsync( + `SELECT * FROM purchases WHERE id = ?`, + [purchaseId] + ); } catch (error) { - console.error('Error get purchase:', error); + console.error('Error getting purchase by ID:', error); throw error; } - } static async createPurchase(userId, productId, walletType, quantity, totalPrice) { - await db.runAsync( - 'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)', - [userId, productId, walletType, "null", quantity, totalPrice] - ); + try { + // Генерируем MD5-хеш для tx_hash + const txHash = CryptoJS.MD5(Date.now().toString()).toString(); + + // Вставка новой покупки в базу данных + const result = await db.runAsync( + `INSERT INTO purchases (user_id, product_id, wallet_type, quantity, total_price, purchase_date, tx_hash) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [userId, productId, walletType, quantity, totalPrice, new Date().toISOString(), txHash] + ); + + // Возвращаем ID новой покупки + return result.lastID; + } catch (error) { + console.error('Error creating purchase:', error); + throw error; + } + } + + static async updatePurchaseStatus(purchaseId, status) { + try { + await db.runAsync( + 'UPDATE purchases SET status = ? WHERE id = ?', + [status, purchaseId] + ); + } catch (error) { + console.error('Error updating purchase status:', error); + throw new Error('Failed to update purchase status'); + } + } + + static async getTotalPurchasesByUserId(userId) { + try { + const total = await db.getAsync( + `SELECT COUNT(*) AS total FROM purchases WHERE user_id = ?`, + [userId] + ); + return total.total; + } catch (error) { + console.error('Error fetching total purchases by user ID:', error); + throw new Error('Failed to fetch total purchases'); + } } } diff --git a/src/services/userService.js b/src/services/userService.js index 322b59c..2541724 100644 --- a/src/services/userService.js +++ b/src/services/userService.js @@ -135,6 +135,27 @@ class UserService { throw e; } } + + static async getUserBalance(userId) { + try { + const user = await db.getAsync( + `SELECT total_balance, bonus_balance + FROM users + WHERE id = ?`, + [userId] + ); + + if (!user) { + throw new Error('User not found'); + } + + // Возвращаем сумму основного и бонусного баланса + return user.total_balance + user.bonus_balance; + } catch (error) { + console.error('Error getting user balance:', error); + throw error; + } + } } export default UserService; \ No newline at end of file