Feature: Enhanced order display and basket summary

1. Basket Summary Below Navigation
   - Welcome message now shows basket items and total
   - Format: "🛒 Basket: X item(s) - £X.XX"
   - Only shown when basket has items

2. Order Display Improvements
   - Order GUID replaced with friendly reference (last 8 chars: #XXXXXXXX)
   - Added "View Payment Details" button for pending payment orders
   - Button shows QR code, amount, address, and time until expiry
   - Variants now properly displayed in order items (already working via commit 330116e)

3. Payment Details View (New Feature)
   - HandleViewPayment: Shows payment info with QR code
   - Displays: Currency, amount, address, expiry time
   - Shows time remaining until payment expires
   - QR code generation for easy mobile payment
   - OrderDetailsMenu: Context-aware navigation buttons

4. Postal Address Auto-Load (Verified Working)
   - Checkout automatically detects saved addresses
   - Offers to use saved address with preview
   - One-click selection or option to enter new address
   - SavedAddress copied to OrderFlow when selected

Technical Changes:
- MessageFormatter.FormatWelcome: Added optional cart parameter
- MessageFormatter.FormatOrder: Uses friendly order reference
- MenuBuilder.OrderDetailsMenu: New menu with payment button
- CallbackHandler.HandleViewPayment: Payment details with QR
- CallbackHandler.HandleMainMenu: Pass cart to welcome formatter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SysAdmin 2025-10-06 03:29:11 +01:00
parent 9432ae8ccb
commit 99bb083bd6
3 changed files with 136 additions and 16 deletions

View File

@ -172,6 +172,10 @@ namespace TeleBot.Handlers
await HandleViewOrder(bot, callbackQuery.Message, session, Guid.Parse(data[1]), callbackQuery.From); await HandleViewOrder(bot, callbackQuery.Message, session, Guid.Parse(data[1]), callbackQuery.From);
break; break;
case "payment":
await HandleViewPayment(bot, callbackQuery.Message, session, Guid.Parse(data[1]), callbackQuery.From);
break;
case "reviews": case "reviews":
await HandleViewReviews(bot, callbackQuery.Message, session); await HandleViewReviews(bot, callbackQuery.Message, session);
break; break;
@ -256,7 +260,7 @@ namespace TeleBot.Handlers
// Send new message at bottom instead of editing old one // Send new message at bottom instead of editing old one
await bot.SendTextMessageAsync( await bot.SendTextMessageAsync(
message.Chat.Id, message.Chat.Id,
MessageFormatter.FormatWelcome(true), MessageFormatter.FormatWelcome(true, session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.MainMenu() replyMarkup: MenuBuilder.MainMenu()
); );
@ -1166,12 +1170,91 @@ namespace TeleBot.Handlers
message.Chat.Id, message.Chat.Id,
MessageFormatter.FormatOrder(order), MessageFormatter.FormatOrder(order),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.MainMenu() replyMarkup: _menuBuilder.OrderDetailsMenu(order)
); );
session.State = SessionState.ViewingOrder; session.State = SessionState.ViewingOrder;
} }
private async Task HandleViewPayment(ITelegramBotClient bot, Message message, UserSession session, Guid orderId, User telegramUser)
{
var order = await _shopService.GetCustomerOrderAsync(
orderId,
telegramUser.Id,
telegramUser.Username ?? "",
$"{telegramUser.FirstName} {telegramUser.LastName}".Trim(),
telegramUser.FirstName ?? "",
telegramUser.LastName ?? ""
);
if (order == null)
{
await bot.AnswerCallbackQueryAsync("", "Order not found", showAlert: true);
return;
}
var payment = order.Payments.FirstOrDefault(p => p.Status == 0); // Find pending payment
if (payment == null)
{
await bot.SendTextMessageAsync(
message.Chat.Id,
"No pending payment found for this order.",
replyMarkup: _menuBuilder.OrderDetailsMenu(order)
);
return;
}
var paymentText = MessageFormatter.FormatPayment(payment);
// Calculate time until expiry
var timeUntilExpiry = payment.ExpiresAt - DateTime.UtcNow;
if (timeUntilExpiry.TotalMinutes > 0)
{
paymentText += $"\n⏰ *Time remaining:* {timeUntilExpiry.Hours}h {timeUntilExpiry.Minutes}m";
}
// Generate QR code if enabled
if (_configuration.GetValue<bool>("Features:EnableQRCodes"))
{
try
{
using var qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode(payment.WalletAddress, QRCodeGenerator.ECCLevel.Q);
using var qrCode = new PngByteQRCode(qrCodeData);
var qrCodeBytes = qrCode.GetGraphic(10);
using var stream = new System.IO.MemoryStream(qrCodeBytes);
await bot.SendPhotoAsync(
message.Chat.Id,
InputFile.FromStream(stream, "payment_qr.png"),
caption: paymentText,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: _menuBuilder.OrderDetailsMenu(order)
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate QR code for payment view");
// Fall back to text-only
await bot.SendTextMessageAsync(
message.Chat.Id,
paymentText,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: _menuBuilder.OrderDetailsMenu(order)
);
}
}
else
{
await bot.SendTextMessageAsync(
message.Chat.Id,
paymentText,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: _menuBuilder.OrderDetailsMenu(order)
);
}
}
private async Task HandlePrivacySettings(ITelegramBotClient bot, Message message, UserSession session, string? setting) private async Task HandlePrivacySettings(ITelegramBotClient bot, Message message, UserSession session, string? setting)
{ {
if (setting != null) if (setting != null)

View File

@ -206,6 +206,28 @@ namespace TeleBot.UI
return new InlineKeyboardMarkup(buttons); return new InlineKeyboardMarkup(buttons);
} }
public InlineKeyboardMarkup OrderDetailsMenu(Order order)
{
var buttons = new List<InlineKeyboardButton[]>();
// If order has pending payment, show payment details button
if (order.Status == 0 && order.Payments.Any(p => p.Status == 0)) // PendingPayment order with pending payment
{
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData("💳 View Payment Details", $"payment:{order.Id}")
});
}
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData("⬅️ Back to Orders", "orders"),
InlineKeyboardButton.WithCallbackData("🏠 Main Menu", "menu")
});
return new InlineKeyboardMarkup(buttons);
}
public InlineKeyboardMarkup CartMenu(ShoppingCart cart) public InlineKeyboardMarkup CartMenu(ShoppingCart cart)
{ {
var buttons = new List<InlineKeyboardButton[]>(); var buttons = new List<InlineKeyboardButton[]>();

View File

@ -8,24 +8,36 @@ namespace TeleBot.UI
{ {
public static class MessageFormatter public static class MessageFormatter
{ {
public static string FormatWelcome(bool isReturning) public static string FormatWelcome(bool isReturning, ShoppingCart? cart = null)
{ {
var message = "";
if (isReturning) if (isReturning)
{ {
return $"🔒 *Welcome back to {BotConfig.BrandName}*\n\n" + message = $"🔒 *Welcome back to {BotConfig.BrandName}*\n\n" +
"Your privacy is our priority. All sessions are ephemeral by default.\n\n" + "Your privacy is our priority. All sessions are ephemeral by default.\n\n" +
"🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" + "🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" +
"How can I help you today?"; "How can I help you today?";
} }
else
return $"🔒 *Welcome to {BotConfig.BrandName}*\n\n" + {
"🛡️ *Your Privacy Matters:*\n" + message = $"🔒 *Welcome to {BotConfig.BrandName}*\n\n" +
"• No account required\n" + "🛡️ *Your Privacy Matters:*\n" +
"• Ephemeral sessions by default\n" + "• No account required\n" +
"• Optional PGP encryption for shipping\n" + "• Ephemeral sessions by default\n" +
"• Cryptocurrency payments only\n\n" + "• Optional PGP encryption for shipping\n" +
"🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" + "• Cryptocurrency payments only\n\n" +
"Use /help for available commands or choose from the menu below:"; "🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" +
"Use /help for available commands or choose from the menu below:";
}
// Add basket summary if items present
if (cart != null && !cart.IsEmpty())
{
message += $"\n\n🛒 *Basket:* {cart.GetTotalItems()} item(s) - £{cart.GetTotalAmount():F2}";
}
return message;
} }
public static string FormatCategories(List<Category> categories) public static string FormatCategories(List<Category> categories)
@ -271,9 +283,12 @@ namespace TeleBot.UI
public static string FormatOrder(Order order) public static string FormatOrder(Order order)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
// Generate friendly order reference from GUID (last 8 characters)
var orderRef = order.Id.ToString().Substring(order.Id.ToString().Length - 8).ToUpper();
sb.AppendLine($"📦 *Order Details*\n"); sb.AppendLine($"📦 *Order Details*\n");
sb.AppendLine($"*Order ID:* `{order.Id}`"); sb.AppendLine($"*Order Reference:* `#{orderRef}`");
sb.AppendLine($"*Status:* {FormatOrderStatus(order.Status)}"); sb.AppendLine($"*Status:* {FormatOrderStatus(order.Status)}");
sb.AppendLine($"*Total:* £{order.TotalAmount:F2}"); sb.AppendLine($"*Total:* £{order.TotalAmount:F2}");
sb.AppendLine($"*Created:* {order.CreatedAt:yyyy-MM-dd HH:mm} UTC"); sb.AppendLine($"*Created:* {order.CreatedAt:yyyy-MM-dd HH:mm} UTC");