Bug update function
This commit is contained in:
parent
682246675e
commit
9d9e0e80ad
@ -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 (
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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') {
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
Loading…
Reference in New Issue
Block a user