diff --git a/TeleBot/TeleBot/Handlers/CallbackHandler.cs b/TeleBot/TeleBot/Handlers/CallbackHandler.cs index 62b216a..03e7af3 100644 --- a/TeleBot/TeleBot/Handlers/CallbackHandler.cs +++ b/TeleBot/TeleBot/Handlers/CallbackHandler.cs @@ -128,6 +128,14 @@ namespace TeleBot.Handlers await HandleSupportCallback(bot, callbackQuery, session); break; + case "refresh_conversation": + await HandleRefreshConversation(bot, callbackQuery, session); + break; + + case "exit_chat": + await HandleExitChat(bot, callbackQuery, session); + break; + case "noop": // No operation - used for display-only buttons break; @@ -605,18 +613,87 @@ namespace TeleBot.Handlers { session.State = SessionState.CustomerSupport; + await ShowCustomerConversationInCallback(bot, callbackQuery, session); + await bot.AnswerCallbackQueryAsync(callbackQuery.Id); + } + + private async Task HandleRefreshConversation(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) + { + await ShowCustomerConversationInCallback(bot, callbackQuery, session); + await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Messages refreshed"); + } + + private async Task HandleExitChat(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) + { + session.State = SessionState.MainMenu; + await bot.EditMessageTextAsync( callbackQuery.Message!.Chat.Id, callbackQuery.Message.MessageId, - "🎧 *Customer Support*\n\n" + - "You can now send a message to our support team. Simply type your message and we'll respond as soon as possible.\n\n" + - "_Type your message below, or use the Cancel button to return to the main menu._", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, - replyMarkup: MenuBuilder.SupportMenu() + "Chat ended. How can I help you today?", + replyMarkup: MenuBuilder.MainMenu() ); await bot.AnswerCallbackQueryAsync(callbackQuery.Id); } + + private async Task ShowCustomerConversationInCallback(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) + { + try + { + var user = callbackQuery.From; + + // Get conversation history for this customer + var messages = await _shopService.GetCustomerConversationAsync( + user.Id, + user.Username ?? "", + $"{user.FirstName} {user.LastName}".Trim(), + user.FirstName ?? "", + user.LastName ?? "" + ); + + var conversationText = "💬 *Your Messages*\n\n"; + + if (messages?.Any() == true) + { + conversationText += "Recent conversation:\n\n"; + + foreach (var msg in messages.OrderBy(m => m.CreatedAt).TakeLast(8)) // Show last 8 messages + { + var isFromBusiness = msg.Direction == 0; // AdminToCustomer + var sender = isFromBusiness ? "🏪 Shop" : "👤 You"; + var time = msg.CreatedAt.ToString("MMM dd, HH:mm"); + + conversationText += $"*{sender}* _{time}_\n{msg.Content}\n\n"; + } + } + else + { + conversationText += "No messages yet. Start a conversation by typing below!\n\n"; + } + + conversationText += "_Type your message or use buttons below._"; + + await bot.EditMessageTextAsync( + callbackQuery.Message!.Chat.Id, + callbackQuery.Message.MessageId, + conversationText, + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error showing customer conversation in callback"); + await bot.EditMessageTextAsync( + callbackQuery.Message!.Chat.Id, + callbackQuery.Message.MessageId, + "💬 *Customer Messages*\n\nReady to chat with our support team!\n\n_Type your message below._", + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + } private async Task HandleCancelSupport(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { diff --git a/TeleBot/TeleBot/Handlers/CommandHandler.cs b/TeleBot/TeleBot/Handlers/CommandHandler.cs index 4b7b10d..b2eb9d1 100644 --- a/TeleBot/TeleBot/Handlers/CommandHandler.cs +++ b/TeleBot/TeleBot/Handlers/CommandHandler.cs @@ -158,19 +158,14 @@ namespace TeleBot.Handlers private async Task HandleOrdersCommand(ITelegramBotClient bot, Message message, Models.UserSession session) { - // Get or create identity reference for this user - var identityRef = session.OrderFlow?.IdentityReference; - if (string.IsNullOrEmpty(identityRef)) - { - identityRef = _privacyService.GenerateAnonymousReference(); - if (session.OrderFlow == null) - { - session.OrderFlow = new Models.OrderFlowData(); - } - session.OrderFlow.IdentityReference = identityRef; - } - - var orders = await _shopService.GetOrdersAsync(identityRef); + // Use new customer-based order lookup + var orders = await _shopService.GetCustomerOrdersAsync( + message.From!.Id, + message.From.Username ?? "", + $"{message.From.FirstName} {message.From.LastName}".Trim(), + message.From.FirstName ?? "", + message.From.LastName ?? "" + ); if (!orders.Any()) { @@ -310,14 +305,8 @@ namespace TeleBot.Handlers { session.State = Models.SessionState.CustomerSupport; - await bot.SendTextMessageAsync( - message.Chat.Id, - "🎧 *Customer Support*\n\n" + - "You can now send a message to our support team. Simply type your message and we'll respond as soon as possible.\n\n" + - "_Type your message below, or use /cancel to return to the main menu._", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, - replyMarkup: MenuBuilder.SupportMenu() - ); + // Show conversation history first + await ShowCustomerConversation(bot, message, session); } private async Task HandleCancelCommand(ITelegramBotClient bot, Message message, Models.UserSession session) @@ -330,5 +319,59 @@ namespace TeleBot.Handlers replyMarkup: MenuBuilder.MainMenu() ); } + + private async Task ShowCustomerConversation(ITelegramBotClient bot, Message message, Models.UserSession session) + { + try + { + // Get conversation history for this customer + var messages = await _shopService.GetCustomerConversationAsync( + message.From!.Id, + message.From.Username ?? "", + $"{message.From.FirstName} {message.From.LastName}".Trim(), + message.From.FirstName ?? "", + message.From.LastName ?? "" + ); + + var conversationText = "💬 *Your Messages*\n\n"; + + if (messages?.Any() == true) + { + conversationText += "Here's your conversation history:\n\n"; + + foreach (var msg in messages.OrderBy(m => m.CreatedAt).Take(10)) // Show last 10 messages + { + var isFromBusiness = msg.Direction == 0; // AdminToCustomer + var sender = isFromBusiness ? "🏪 Shop" : "👤 You"; + var time = msg.CreatedAt.ToString("MMM dd, HH:mm"); + + conversationText += $"*{sender}* _{time}_\n{msg.Content}\n\n"; + } + } + else + { + conversationText += "No messages yet. Start a conversation by typing below!\n\n"; + } + + conversationText += "_Type your message or use the Exit Chat button to return to shopping._"; + + await bot.SendTextMessageAsync( + message.Chat.Id, + conversationText, + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error showing customer conversation"); + await bot.SendTextMessageAsync( + message.Chat.Id, + "🎧 *Customer Support*\n\nReady to chat with our support team!\n\n_Type your message below._", + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + } } } \ No newline at end of file diff --git a/TeleBot/TeleBot/Handlers/MessageHandler.cs b/TeleBot/TeleBot/Handlers/MessageHandler.cs index aafba20..8f0ffc7 100644 --- a/TeleBot/TeleBot/Handlers/MessageHandler.cs +++ b/TeleBot/TeleBot/Handlers/MessageHandler.cs @@ -247,16 +247,8 @@ namespace TeleBot.Handlers if (success) { - await bot.SendTextMessageAsync( - message.Chat.Id, - "✅ *Message sent to support team*\n\n" + - "We've received your message and will respond as soon as possible.\n\n" + - "You can continue shopping or send additional messages.", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, - replyMarkup: MenuBuilder.MainMenu() - ); - - session.State = SessionState.MainMenu; + // Show updated conversation instead of going back to main menu + await RefreshCustomerConversation(bot, message, session); } else { @@ -277,5 +269,54 @@ namespace TeleBot.Handlers ); } } + + private async Task RefreshCustomerConversation(ITelegramBotClient bot, Message message, UserSession session) + { + try + { + // Get updated conversation history + var messages = await _shopService.GetCustomerConversationAsync( + message.From!.Id, + message.From.Username ?? "", + $"{message.From.FirstName} {message.From.LastName}".Trim(), + message.From.FirstName ?? "", + message.From.LastName ?? "" + ); + + var conversationText = "💬 *Your Messages*\n\n"; + + if (messages?.Any() == true) + { + conversationText += "Recent conversation:\n\n"; + + foreach (var msg in messages.OrderBy(m => m.CreatedAt).TakeLast(8)) // Show last 8 messages + { + var isFromBusiness = msg.Direction == 0; // AdminToCustomer + var sender = isFromBusiness ? "🏪 Shop" : "👤 You"; + var time = msg.CreatedAt.ToString("MMM dd, HH:mm"); + + conversationText += $"*{sender}* _{time}_\n{msg.Content}\n\n"; + } + } + + conversationText += "_Type your next message or use buttons below._"; + + await bot.SendTextMessageAsync( + message.Chat.Id, + conversationText, + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing customer conversation"); + await bot.SendTextMessageAsync( + message.Chat.Id, + "✅ Message sent! Continue chatting or use the buttons below.", + replyMarkup: MenuBuilder.ConversationMenu() + ); + } + } } } \ No newline at end of file diff --git a/TeleBot/TeleBot/Services/LittleShopService.cs b/TeleBot/TeleBot/Services/LittleShopService.cs index 5838a4f..ac3c1c6 100644 --- a/TeleBot/TeleBot/Services/LittleShopService.cs +++ b/TeleBot/TeleBot/Services/LittleShopService.cs @@ -18,12 +18,14 @@ namespace TeleBot.Services Task GetProductAsync(Guid productId); Task CreateOrderAsync(UserSession session, long telegramUserId = 0, string telegramUsername = "", string telegramDisplayName = "", string telegramFirstName = "", string telegramLastName = ""); Task> GetOrdersAsync(string identityReference); + Task> GetCustomerOrdersAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName); Task GetOrderAsync(Guid orderId); Task CreatePaymentAsync(Guid orderId, string currency); Task?> GetPendingMessagesAsync(); Task MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null); Task MarkMessageAsFailedAsync(Guid messageId, string reason); Task SendCustomerMessageAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName, string subject, string content); + Task?> GetCustomerConversationAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName); } public class LittleShopService : ILittleShopService @@ -450,5 +452,95 @@ namespace TeleBot.Services return false; } } + + public async Task> GetCustomerOrdersAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName) + { + try + { + if (!await AuthenticateAsync()) + return new List(); + + // Get or create the customer + var customer = await _client.Customers.GetOrCreateCustomerAsync(new CreateCustomerRequest + { + TelegramUserId = telegramUserId, + TelegramUsername = telegramUsername, + TelegramDisplayName = displayName, + TelegramFirstName = firstName, + TelegramLastName = lastName, + AllowOrderUpdates = true, + AllowMarketing = false + }); + + if (!customer.IsSuccess || customer.Data == null) + { + return new List(); + } + + // Get customer orders using the new endpoint + var ordersResult = await _client.Orders.GetOrdersByCustomerIdAsync(customer.Data.Id); + + if (ordersResult.IsSuccess && ordersResult.Data != null) + { + return ordersResult.Data; + } + + return new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching customer orders"); + return new List(); + } + } + + public async Task?> GetCustomerConversationAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName) + { + try + { + if (!await AuthenticateAsync()) + return null; + + // First, get or create the customer + var customer = await _client.Customers.GetOrCreateCustomerAsync(new CreateCustomerRequest + { + TelegramUserId = telegramUserId, + TelegramUsername = telegramUsername, + TelegramDisplayName = displayName, + TelegramFirstName = firstName, + TelegramLastName = lastName, + AllowOrderUpdates = true, + AllowMarketing = false + }); + + if (!customer.IsSuccess || customer.Data == null) + { + return new List(); + } + + // Get all messages for this customer (conversation history) + var clientMessages = await _client.Messages.GetCustomerMessagesAsync(customer.Data.Id); + + // Convert to TeleBot CustomerMessage format + return clientMessages?.Select(m => new CustomerMessage + { + Id = m.Id, + CustomerId = m.CustomerId, + TelegramUserId = m.TelegramUserId, + Subject = m.Subject, + Content = m.Content, + Type = (MessageType)m.Type, + IsUrgent = m.IsUrgent, + OrderReference = m.OrderReference, + CreatedAt = m.CreatedAt, + Direction = m.Direction + }).ToList() ?? new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting customer conversation"); + return null; + } + } } } \ No newline at end of file diff --git a/TeleBot/TeleBot/Services/MessageDeliveryService.cs b/TeleBot/TeleBot/Services/MessageDeliveryService.cs index b3196cd..6949e7e 100644 --- a/TeleBot/TeleBot/Services/MessageDeliveryService.cs +++ b/TeleBot/TeleBot/Services/MessageDeliveryService.cs @@ -187,6 +187,7 @@ namespace TeleBot.Services public bool IsUrgent { get; set; } public string? OrderReference { get; set; } public DateTime CreatedAt { get; set; } + public int Direction { get; set; } // 0 = AdminToCustomer, 1 = CustomerToAdmin } public class PendingMessageDto diff --git a/TeleBot/TeleBot/Services/SessionManager.cs b/TeleBot/TeleBot/Services/SessionManager.cs index 013c91e..04e6e48 100644 --- a/TeleBot/TeleBot/Services/SessionManager.cs +++ b/TeleBot/TeleBot/Services/SessionManager.cs @@ -154,8 +154,8 @@ namespace TeleBot.Services session.LastActivityAt = DateTime.UtcNow; _inMemorySessions.AddOrUpdate(session.Id, session, (key, old) => session); - // Don't persist ephemeral sessions - if (session.IsEphemeral) + // Don't persist ephemeral sessions, except when in checkout flow + if (session.IsEphemeral && session.State != SessionState.CheckoutFlow) { return; } diff --git a/TeleBot/TeleBot/UI/MenuBuilder.cs b/TeleBot/TeleBot/UI/MenuBuilder.cs index 9a3de73..dc39961 100644 --- a/TeleBot/TeleBot/UI/MenuBuilder.cs +++ b/TeleBot/TeleBot/UI/MenuBuilder.cs @@ -16,7 +16,7 @@ namespace TeleBot.UI new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") }, new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") }, new[] { InlineKeyboardButton.WithCallbackData("📦 My Orders", "orders") }, - new[] { InlineKeyboardButton.WithCallbackData("🎧 Customer Support", "support") }, + new[] { InlineKeyboardButton.WithCallbackData("💬 Messages", "support") }, new[] { InlineKeyboardButton.WithCallbackData("🔒 Privacy Settings", "privacy") }, new[] { InlineKeyboardButton.WithCallbackData("❓ Help", "help") } }); @@ -291,6 +291,20 @@ namespace TeleBot.UI return markup; } + public static InlineKeyboardMarkup ConversationMenu() + { + var markup = new InlineKeyboardMarkup(new[] + { + new[] + { + InlineKeyboardButton.WithCallbackData("🔄 Refresh Messages", "refresh_conversation"), + InlineKeyboardButton.WithCallbackData("⬅️ Back to Menu", "exit_chat") + } + }); + + return markup; + } + private static string GetOrderStatusEmoji(int status) { return status switch diff --git a/TeleBot/TeleBot/UI/MessageFormatter.cs b/TeleBot/TeleBot/UI/MessageFormatter.cs index 9aa8f75..65e9fb2 100644 --- a/TeleBot/TeleBot/UI/MessageFormatter.cs +++ b/TeleBot/TeleBot/UI/MessageFormatter.cs @@ -256,7 +256,7 @@ namespace TeleBot.UI "/browse - Browse products\n" + "/cart - View shopping cart\n" + "/orders - View your orders\n" + - "/support - Contact customer support\n" + + "/support - View messages and chat\n" + "/privacy - Privacy settings\n" + "/pgpkey - Set PGP public key\n" + "/ephemeral - Toggle ephemeral mode\n" +