Bug update function
This commit is contained in:
@@ -191,11 +191,22 @@ const initDb = async () => {
|
|||||||
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,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (product_id) REFERENCES products(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 (
|
||||||
|
|||||||
@@ -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,9 +314,21 @@ 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}
|
||||||
|
|
||||||
@@ -311,15 +339,13 @@ export default class UserProductHandler {
|
|||||||
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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,31 +356,32 @@ Category: ${product.category_name}
|
|||||||
{
|
{
|
||||||
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}
|
Name: ${product.name}
|
||||||
Price: $${product.price}
|
Quantity: ${quantity}
|
||||||
Description: ${product.description}
|
Total: $${totalPrice}
|
||||||
Stock: ${product.quantity_in_stock}
|
Location: ${location?.country || 'N/A'}, ${location?.city || 'N/A'}, ${location?.district || 'N/A'}
|
||||||
Location: ${product.country}, ${product.city}, ${product.district}
|
Category: ${category?.name || 'N/A'}
|
||||||
Category: ${product.category_name}
|
|
||||||
|
|
||||||
🔒 Private Information:
|
🔒 Private Information:
|
||||||
${product.private_data}
|
${product.private_data || 'N/A'}
|
||||||
Hidden Location: ${product.hidden_description}
|
Hidden Location: ${product.hidden_description || 'N/A'}
|
||||||
Coordinates: ${product.hidden_coordinates}
|
Coordinates: ${product.hidden_coordinates || 'N/A'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
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.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,25 +45,31 @@ 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.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,8 +77,7 @@ export default class UserPurchaseHandler {
|
|||||||
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) {
|
||||||
@@ -100,8 +130,8 @@ export default class UserPurchaseHandler {
|
|||||||
|
|
||||||
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_', '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем данные покупки
|
||||||
const purchase = await PurchaseService.getPurchaseById(purchaseId);
|
const purchase = await PurchaseService.getPurchaseById(purchaseId);
|
||||||
|
|
||||||
if (!purchase) {
|
if (!purchase) {
|
||||||
await bot.sendMessage(chatId, "No such purchase");
|
await bot.sendMessage(chatId, "No such purchase");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = await ProductService.getProductById(purchase.product_id)
|
// Получаем данные товара по product_id
|
||||||
|
const product = await ProductService.getProductById(purchase.product_id);
|
||||||
if (!product) {
|
if (!product) {
|
||||||
await bot.sendMessage(chatId, "No such product");
|
await bot.sendMessage(chatId, "No such product");
|
||||||
return;
|
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;
|
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 = `
|
||||||
📦 Purchase Details:
|
📦 Purchase Details:
|
||||||
Name: ${purchase.product_name}
|
Name: ${product.name || 'N/A'}
|
||||||
Quantity: ${purchase.quantity}
|
Quantity: ${purchase.quantity}
|
||||||
Total: $${purchase.total_price}
|
Total: $${purchase.total_price}
|
||||||
Location: ${purchase.country}, ${purchase.city}
|
Location: ${location?.country || 'N/A'}, ${location?.city || 'N/A'}, ${location?.district || 'N/A'}
|
||||||
Payment: ${purchase.wallet_type}
|
Category: ${category?.name || 'N/A'}
|
||||||
Date: ${new Date(purchase.purchase_date).toLocaleString()}
|
|
||||||
|
|
||||||
🔒 Private Information:
|
🔒 Private Information:
|
||||||
${product.private_data}
|
${product.private_data || 'N/A'}
|
||||||
Hidden Location: ${product.hidden_description}
|
Hidden Location: ${product.hidden_description || 'N/A'}
|
||||||
Coordinates: ${product.hidden_coordinates}
|
Coordinates: ${product.hidden_coordinates || 'N/A'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Создаем клавиатуру с кнопками
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[{text: "I've got it!", callback_data: "Asdasdasd"}],
|
// Проверяем статус покупки перед добавлением кнопки "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 }]
|
[{ text: "Contact support", url: config.SUPPORT_LINK }]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Отправляем сообщение с деталями покупки
|
||||||
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) {
|
||||||
|
console.error('Error in viewPurchase:', error);
|
||||||
|
await bot.sendMessage(chatId, 'Error loading purchase details. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
'SELECT * FROM locations WHERE id = ?',
|
||||||
[locationId]
|
[locationId]
|
||||||
);
|
);
|
||||||
|
return location;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching location by ID:', error);
|
||||||
|
throw new Error('Failed to fetch location');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user