This commit is contained in:
sysadmin 2025-08-27 22:19:10 +01:00
parent 027a3fd0c4
commit 5c6abe5686
8 changed files with 308 additions and 40 deletions

View File

@ -128,6 +128,14 @@ namespace TeleBot.Handlers
await HandleSupportCallback(bot, callbackQuery, session); await HandleSupportCallback(bot, callbackQuery, session);
break; break;
case "refresh_conversation":
await HandleRefreshConversation(bot, callbackQuery, session);
break;
case "exit_chat":
await HandleExitChat(bot, callbackQuery, session);
break;
case "noop": case "noop":
// No operation - used for display-only buttons // No operation - used for display-only buttons
break; break;
@ -605,18 +613,87 @@ namespace TeleBot.Handlers
{ {
session.State = SessionState.CustomerSupport; 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( await bot.EditMessageTextAsync(
callbackQuery.Message!.Chat.Id, callbackQuery.Message!.Chat.Id,
callbackQuery.Message.MessageId, callbackQuery.Message.MessageId,
"🎧 *Customer Support*\n\n" + "Chat ended. How can I help you today?",
"You can now send a message to our support team. Simply type your message and we'll respond as soon as possible.\n\n" + replyMarkup: MenuBuilder.MainMenu()
"_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()
); );
await bot.AnswerCallbackQueryAsync(callbackQuery.Id); 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) private async Task HandleCancelSupport(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session)
{ {

View File

@ -158,19 +158,14 @@ namespace TeleBot.Handlers
private async Task HandleOrdersCommand(ITelegramBotClient bot, Message message, Models.UserSession session) private async Task HandleOrdersCommand(ITelegramBotClient bot, Message message, Models.UserSession session)
{ {
// Get or create identity reference for this user // Use new customer-based order lookup
var identityRef = session.OrderFlow?.IdentityReference; var orders = await _shopService.GetCustomerOrdersAsync(
if (string.IsNullOrEmpty(identityRef)) message.From!.Id,
{ message.From.Username ?? "",
identityRef = _privacyService.GenerateAnonymousReference(); $"{message.From.FirstName} {message.From.LastName}".Trim(),
if (session.OrderFlow == null) message.From.FirstName ?? "",
{ message.From.LastName ?? ""
session.OrderFlow = new Models.OrderFlowData(); );
}
session.OrderFlow.IdentityReference = identityRef;
}
var orders = await _shopService.GetOrdersAsync(identityRef);
if (!orders.Any()) if (!orders.Any())
{ {
@ -310,14 +305,8 @@ namespace TeleBot.Handlers
{ {
session.State = Models.SessionState.CustomerSupport; session.State = Models.SessionState.CustomerSupport;
await bot.SendTextMessageAsync( // Show conversation history first
message.Chat.Id, await ShowCustomerConversation(bot, message, session);
"🎧 *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()
);
} }
private async Task HandleCancelCommand(ITelegramBotClient bot, Message message, Models.UserSession session) private async Task HandleCancelCommand(ITelegramBotClient bot, Message message, Models.UserSession session)
@ -330,5 +319,59 @@ namespace TeleBot.Handlers
replyMarkup: MenuBuilder.MainMenu() 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()
);
}
}
} }
} }

View File

@ -247,16 +247,8 @@ namespace TeleBot.Handlers
if (success) if (success)
{ {
await bot.SendTextMessageAsync( // Show updated conversation instead of going back to main menu
message.Chat.Id, await RefreshCustomerConversation(bot, message, session);
"✅ *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;
} }
else 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()
);
}
}
} }
} }

View File

@ -18,12 +18,14 @@ namespace TeleBot.Services
Task<Product?> GetProductAsync(Guid productId); Task<Product?> GetProductAsync(Guid productId);
Task<Order?> CreateOrderAsync(UserSession session, long telegramUserId = 0, string telegramUsername = "", string telegramDisplayName = "", string telegramFirstName = "", string telegramLastName = ""); Task<Order?> CreateOrderAsync(UserSession session, long telegramUserId = 0, string telegramUsername = "", string telegramDisplayName = "", string telegramFirstName = "", string telegramLastName = "");
Task<List<Order>> GetOrdersAsync(string identityReference); Task<List<Order>> GetOrdersAsync(string identityReference);
Task<List<Order>> GetCustomerOrdersAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName);
Task<Order?> GetOrderAsync(Guid orderId); Task<Order?> GetOrderAsync(Guid orderId);
Task<CryptoPayment?> CreatePaymentAsync(Guid orderId, string currency); Task<CryptoPayment?> CreatePaymentAsync(Guid orderId, string currency);
Task<List<CustomerMessage>?> GetPendingMessagesAsync(); Task<List<CustomerMessage>?> GetPendingMessagesAsync();
Task<bool> MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null); Task<bool> MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null);
Task<bool> MarkMessageAsFailedAsync(Guid messageId, string reason); Task<bool> MarkMessageAsFailedAsync(Guid messageId, string reason);
Task<bool> SendCustomerMessageAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName, string subject, string content); Task<bool> SendCustomerMessageAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName, string subject, string content);
Task<List<CustomerMessage>?> GetCustomerConversationAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName);
} }
public class LittleShopService : ILittleShopService public class LittleShopService : ILittleShopService
@ -450,5 +452,95 @@ namespace TeleBot.Services
return false; return false;
} }
} }
public async Task<List<Order>> GetCustomerOrdersAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName)
{
try
{
if (!await AuthenticateAsync())
return new List<Order>();
// 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<Order>();
}
// 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<Order>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching customer orders");
return new List<Order>();
}
}
public async Task<List<CustomerMessage>?> 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<CustomerMessage>();
}
// 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<CustomerMessage>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting customer conversation");
return null;
}
}
} }
} }

View File

@ -187,6 +187,7 @@ namespace TeleBot.Services
public bool IsUrgent { get; set; } public bool IsUrgent { get; set; }
public string? OrderReference { get; set; } public string? OrderReference { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public int Direction { get; set; } // 0 = AdminToCustomer, 1 = CustomerToAdmin
} }
public class PendingMessageDto public class PendingMessageDto

View File

@ -154,8 +154,8 @@ namespace TeleBot.Services
session.LastActivityAt = DateTime.UtcNow; session.LastActivityAt = DateTime.UtcNow;
_inMemorySessions.AddOrUpdate(session.Id, session, (key, old) => session); _inMemorySessions.AddOrUpdate(session.Id, session, (key, old) => session);
// Don't persist ephemeral sessions // Don't persist ephemeral sessions, except when in checkout flow
if (session.IsEphemeral) if (session.IsEphemeral && session.State != SessionState.CheckoutFlow)
{ {
return; return;
} }

View File

@ -16,7 +16,7 @@ namespace TeleBot.UI
new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") }, new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") },
new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") }, new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") },
new[] { InlineKeyboardButton.WithCallbackData("📦 My Orders", "orders") }, 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("🔒 Privacy Settings", "privacy") },
new[] { InlineKeyboardButton.WithCallbackData("❓ Help", "help") } new[] { InlineKeyboardButton.WithCallbackData("❓ Help", "help") }
}); });
@ -291,6 +291,20 @@ namespace TeleBot.UI
return markup; 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) private static string GetOrderStatusEmoji(int status)
{ {
return status switch return status switch

View File

@ -256,7 +256,7 @@ namespace TeleBot.UI
"/browse - Browse products\n" + "/browse - Browse products\n" +
"/cart - View shopping cart\n" + "/cart - View shopping cart\n" +
"/orders - View your orders\n" + "/orders - View your orders\n" +
"/support - Contact customer support\n" + "/support - View messages and chat\n" +
"/privacy - Privacy settings\n" + "/privacy - Privacy settings\n" +
"/pgpkey - Set PGP public key\n" + "/pgpkey - Set PGP public key\n" +
"/ephemeral - Toggle ephemeral mode\n" + "/ephemeral - Toggle ephemeral mode\n" +