Bug update function

This commit is contained in:
NW
2024-12-14 23:12:36 +00:00
parent 682246675e
commit 9d9e0e80ad
9 changed files with 474 additions and 201 deletions

View File

@@ -183,19 +183,30 @@ const initDb = async () => {
// Create purchases table // Create purchases table
await db.runAsync(` await db.runAsync(`
CREATE TABLE IF NOT EXISTS purchases ( CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL, product_id INTEGER NOT NULL,
wallet_type TEXT NOT NULL, wallet_type TEXT NOT NULL,
tx_hash TEXT NOT NULL, tx_hash TEXT NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0), quantity INTEGER NOT NULL CHECK (quantity > 0),
total_price REAL NOT NULL CHECK (total_price > 0), total_price REAL NOT NULL CHECK (total_price > 0),
purchase_date DATETIME DEFAULT CURRENT_TIMESTAMP, purchase_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, status TEXT DEFAULT 'pending',
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE 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 // Create locations table
await db.runAsync(` await db.runAsync(`
CREATE TABLE IF NOT EXISTS locations ( CREATE TABLE IF NOT EXISTS locations (

View File

@@ -176,15 +176,29 @@ export default class UserProductHandler {
const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_'); const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_');
try { 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); const products = await ProductService.getProductsByCategoryId(categoryId);
if (products.length === 0) { if (products.length === 0) {
await bot.editMessageText( await bot.sendMessage(
chatId,
'No products available in this category.', 'No products available in this category.',
{ {
chat_id: chatId,
message_id: messageId,
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '« Back', callback_data: `shop_district_${locationId}` } { 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:', 'Select a product:',
{ {
chat_id: chatId,
message_id: messageId,
reply_markup: keyboard reply_markup: keyboard
} }
); );
// Удаляем состояние пользователя
userStates.delete(chatId);
} catch (error) { } catch (error) {
console.error('Error in handleCategorySelection:', error); console.error('Error in handleCategorySelection:', error);
await bot.sendMessage(chatId, 'Error loading products. Please try again.'); await bot.sendMessage(chatId, 'Error loading products. Please try again.');
@@ -298,63 +314,74 @@ export default class UserProductHandler {
throw new Error('Product not found'); throw new Error('Product not found');
} }
// Delete the previous message // Удаляем предыдущее сообщение
await bot.deleteMessage(chatId, messageId); 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 = ` const message = `
📦 ${product.name} 📦 ${product.name}
💰 Price: $${product.price} 💰 Price: $${product.price}
📝 Description: ${product.description} 📝 Description: ${product.description}
📦 Available: ${product.quantity_in_stock} pcs 📦 Available: ${product.quantity_in_stock} pcs
Category: ${product.category_name} Category: ${product.category_name}
`; `;
let photoMessageId = null; // Отправляем фото, если оно существует
// First send the photo if it exists
let photoMessage; let photoMessage;
if (product.photo_url) { if (product.photo_url) {
try { 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) { } 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 = { const keyboard = {
inline_keyboard: [ inline_keyboard: [
[{text: '🛒 Buy Now', callback_data: `buy_product_${productId}`}], [{ text: '🛒 Buy Now', callback_data: `buy_product_${productId}` }],
[ [
{ {
text: '', text: '',
callback_data: `decrease_quantity_${productId}`, 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: '', text: '',
callback_data: `increase_quantity_${productId}`, 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}` }] // Возврат к категории [{ 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, reply_markup: keyboard,
parse_mode: 'HTML' parse_mode: 'HTML'
}); });
// Store the current quantity and photo message ID in user state // Сохраняем ID сообщения с фотографией и ID сообщения с товаром в состояние пользователя
userStates.set(chatId, { userStates.set(chatId, {
action: 'buying_product', action: 'buying_product',
productId, productId,
quantity: 1, quantity: 1,
photoMessageId photoMessageId: photoMessage ? photoMessage.message_id : null,
productMessageId: productMessage.message_id
}); });
} catch (error) { } catch (error) {
console.error('Error in handleProductSelection:', error); console.error('Error in handleProductSelection:', error);
@@ -502,8 +529,9 @@ Category: ${product.category_name}
const quantity = state?.quantity || 1; const quantity = state?.quantity || 1;
const totalPrice = product.price * quantity; const totalPrice = product.price * quantity;
// Проверка баланса пользователя // Получение баланса пользователя
const userBalance = await UserService.getUserBalance(user.id); const userBalance = await UserService.getUserBalance(user.id);
if (userBalance < totalPrice) { if (userBalance < totalPrice) {
await bot.sendMessage( await bot.sendMessage(
chatId, chatId,
@@ -545,11 +573,12 @@ Category: ${product.category_name}
const keyboard = { const keyboard = {
inline_keyboard: [ inline_keyboard: [
[{ text: `Pay`, callback_data: `pay_with_main_${productId}_${quantity}` }], [{ 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` + `🛒 Purchase Summary:\n\n` +
`Product: ${product.name}\n` + `Product: ${product.name}\n` +
`Quantity: ${quantity}\n` + `Quantity: ${quantity}\n` +
@@ -560,6 +589,13 @@ Category: ${product.category_name}
reply_markup: keyboard reply_markup: keyboard
} }
); );
// Сохранение ID сообщения с фотографией в состояние пользователя
userStates.set(chatId, {
...state,
photoMessageId: state?.photoMessageId || null,
purchaseMessageId: purchaseMessage.message_id
});
} catch (error) { } catch (error) {
console.error('Error in handleBuyProduct:', error); console.error('Error in handleBuyProduct:', error);
await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
@@ -574,7 +610,7 @@ Category: ${product.category_name}
try { try {
await UserService.recalculateUserBalanceByTelegramId(telegramId); await UserService.recalculateUserBalanceByTelegramId(telegramId);
const user = await UserService.getUserByTelegramId(telegramId) const user = await UserService.getUserByTelegramId(telegramId);
if (!user) { if (!user) {
throw new Error('User not found'); throw new Error('User not found');
@@ -597,44 +633,73 @@ Category: ${product.category_name}
return; 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; let hiddenPhotoMessage;
if (product.hidden_photo_url) { if (product.hidden_photo_url) {
try { 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) { } 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 = ` const message = `
📦 Product Details: 📦 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'}
Name: ${product.name} 🔒 Private Information:
Price: $${product.price} ${product.private_data || 'N/A'}
Description: ${product.description} Hidden Location: ${product.hidden_description || 'N/A'}
Stock: ${product.quantity_in_stock} Coordinates: ${product.hidden_coordinates || 'N/A'}
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}
`;
const keyboard = { const keyboard = {
inline_keyboard: [ inline_keyboard: [
[{text: "I've got it!", callback_data: "Asdasdasd"}], [{ text: 'View new purchase', callback_data: `view_purchase_${purchaseId}` }], // Переход к покупке
[{text: "Contact support", url: config.SUPPORT_LINK}] [{ 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); 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) { } catch (error) {
console.error('Error in handleBuyProduct:', error); console.error('Error in handlePay:', error);
await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
} }
} }

View File

@@ -1,31 +1,42 @@
import config from "../../config/config.js"; import config from "../../config/config.js";
import PurchaseService from "../../services/purchaseService.js"; import PurchaseService from "../../services/purchaseService.js";
import UserService from "../../services/userService.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 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 { export default class UserPurchaseHandler {
static async viewPurchasePage(userId, page) { static async viewPurchasePage(userId, page) {
try { try {
const limit = 10; const limit = 10; // Количество покупок на странице
const offset = (page || 0) * limit; const offset = page * limit;
const previousPage = page > 0 ? page - 1 : 0;
const nextPage = page + 1;
// Получаем покупки пользователя с учетом пагинации
const purchases = await PurchaseService.getPurchasesByUserId(userId, limit, offset); 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 { return {
text: 'You haven\'t made any purchases yet.', text: 'Your purchase history is empty.',
markup: [[ markup: {
{text: '🛍 Browse Products', callback_data: 'shop_start'} 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 = { const keyboard = {
@@ -34,34 +45,39 @@ export default class UserPurchaseHandler {
text: `${item.product_name} [${new Date(item.purchase_date).toLocaleString()}]`, text: `${item.product_name} [${new Date(item.purchase_date).toLocaleString()}]`,
callback_data: `view_purchase_${item.id}` 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 { return {
text: `📦 Select purchase to view detailed information:`, text: `📦 Select purchase to view detailed information (Page ${page + 1} of ${totalPages}):`,
markup: keyboard markup: keyboard
} };
} catch (error) { } catch (error) {
console.error('Error in showPurchases:', error); console.error('Error in viewPurchasePage:', error);
return {text: 'Error loading purchase history. Please try again.'}; return { text: 'Error loading purchase history. Please try again.' };
} }
} }
static async handlePurchaseListPage(callbackQuery) { static async handlePurchaseListPage(callbackQuery) {
const telegramId = callbackQuery.from.id; const telegramId = callbackQuery.from.id;
const chatId = callbackQuery.message.chat.id; const chatId = callbackQuery.message.chat.id;
const page = parseInt(callbackQuery.data.replace('list_purchases_', ''));
const page = callbackQuery.data.replace('list_purchases_', '');
try { try {
const user = await UserService.getUserByTelegramId(telegramId); const user = await UserService.getUserByTelegramId(telegramId);
@@ -71,15 +87,30 @@ export default class UserPurchaseHandler {
return; 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, { await bot.editMessageText(text, {
chat_id: chatId, chat_id: chatId,
message_id: callbackQuery.message.message_id, message_id: callbackQuery.message.message_id,
reply_markup: markup, reply_markup: markup,
parse_mode: 'Markdown', parse_mode: 'Markdown'
}); });
// Удаляем состояние пользователя
userStates.delete(chatId);
} catch (e) { } catch (e) {
return; console.error('Error in handlePurchaseListPage:', e);
await bot.sendMessage(chatId, 'Error loading purchase history. Please try again.');
} }
} }
@@ -88,7 +119,6 @@ export default class UserPurchaseHandler {
const telegramId = msg.from.id; const telegramId = msg.from.id;
try { try {
const user = await UserService.getUserByTelegramId(telegramId); const user = await UserService.getUserByTelegramId(telegramId);
if (!user) { if (!user) {
@@ -96,12 +126,12 @@ export default class UserPurchaseHandler {
return; return;
} }
const {text, markup} = await this.viewPurchasePage(user.id, 0); const { text, markup } = await this.viewPurchasePage(user.id, 0);
await bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'}); await bot.sendMessage(chatId, text, { reply_markup: markup, parse_mode: 'Markdown' });
} catch (error) { } catch (error) {
console.error('Error in handleSubcategorySelection:', error); console.error('Error in showPurchases:', error);
await bot.sendMessage(chatId, 'Error loading products. Please try again.'); await bot.sendMessage(chatId, 'Error loading purchase history. Please try again.');
} }
} }
@@ -109,52 +139,141 @@ export default class UserPurchaseHandler {
const chatId = callbackQuery.message.chat.id; const chatId = callbackQuery.message.chat.id;
const purchaseId = callbackQuery.data.replace('view_purchase_', ''); const purchaseId = callbackQuery.data.replace('view_purchase_', '');
const purchase = await PurchaseService.getPurchaseById(purchaseId); try {
// Получаем данные покупки
if (!purchase) { const purchase = await PurchaseService.getPurchaseById(purchaseId);
await bot.sendMessage(chatId, "No such purchase"); if (!purchase) {
return; 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'})
} }
// Получаем данные товара по 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 = ` static async handleConfirmReceived(callbackQuery) {
📦 Purchase Details: const chatId = callbackQuery.message.chat.id;
Name: ${purchase.product_name} const messageId = callbackQuery.message.message_id;
Quantity: ${purchase.quantity} const purchaseId = callbackQuery.data.replace('confirm_received_', '');
Total: $${purchase.total_price}
Location: ${purchase.country}, ${purchase.city}
Payment: ${purchase.wallet_type}
Date: ${new Date(purchase.purchase_date).toLocaleString()}
🔒 Private Information: try {
${product.private_data} // Проверяем, подтверждена ли покупка уже
Hidden Location: ${product.hidden_description} const purchase = await PurchaseService.getPurchaseById(purchaseId);
Coordinates: ${product.hidden_coordinates} if (!purchase) {
`; await bot.sendMessage(chatId, "Purchase not found.");
return;
}
const keyboard = { if (purchase.status === 'received') {
inline_keyboard: [ await bot.sendMessage(chatId, "This purchase has already been confirmed.");
[{text: "I've got it!", callback_data: "Asdasdasd"}], return;
[{text: "Contact support", url: config.SUPPORT_LINK}] }
]
};
await bot.sendMessage(chatId, message, {reply_markup: keyboard}); // Обновляем статус покупки в базе данных
await bot.deleteMessage(chatId, callbackQuery.message.message_id); 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.');
}
} }
} }

View File

@@ -233,6 +233,9 @@ bot.on('callback_query', async (callbackQuery) => {
} else if (action.startsWith('view_purchase_')) { } else if (action.startsWith('view_purchase_')) {
logDebug(action, 'viewPurchase'); logDebug(action, 'viewPurchase');
await userPurchaseHandler.viewPurchase(callbackQuery); await userPurchaseHandler.viewPurchase(callbackQuery);
} else if (action.startsWith('confirm_received_')) {
logDebug(action, 'handleConfirmReceived');
await userPurchaseHandler.handleConfirmReceived(callbackQuery);
} }
// Admin location management // Admin location management
else if (action === 'add_location') { else if (action === 'add_location') {

View File

@@ -16,7 +16,16 @@ class CategoryService {
} }
static async getCategoryById(categoryId) { 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) { static async getSubcategoryById(subcategoryId) {

View File

@@ -27,10 +27,16 @@ class LocationService {
} }
static async getLocationById(locationId) { static async getLocationById(locationId) {
return await db.getAsync( try {
'SELECT country, city, district FROM locations WHERE id = ?', const location = await db.getAsync(
[locationId] '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');
}
} }
} }

View File

@@ -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 export default ProductService

View File

@@ -1,5 +1,5 @@
import db from "../config/database.js"; import db from "../config/database.js";
import CryptoJS from "crypto-js"; // Импортируем библиотеку crypto-js
class PurchaseService { class PurchaseService {
static async getPurchasesByUserId(userId, limit, offset) { static async getPurchasesByUserId(userId, limit, offset) {
try { try {
@@ -28,31 +28,59 @@ class PurchaseService {
static async getPurchaseById(purchaseId) { static async getPurchaseById(purchaseId) {
try { try {
return await db.getAsync(` return await db.getAsync(
SELECT `SELECT * FROM purchases WHERE id = ?`,
p.*, [purchaseId]
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]);
} catch (error) { } catch (error) {
console.error('Error get purchase:', error); console.error('Error getting purchase by ID:', error);
throw error; throw error;
} }
} }
static async createPurchase(userId, productId, walletType, quantity, totalPrice) { static async createPurchase(userId, productId, walletType, quantity, totalPrice) {
await db.runAsync( try {
'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)', // Генерируем MD5-хеш для tx_hash
[userId, productId, walletType, "null", quantity, totalPrice] 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');
}
} }
} }

View File

@@ -135,6 +135,27 @@ class UserService {
throw e; 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; export default UserService;