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:
parent
9432ae8ccb
commit
99bb083bd6
@ -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)
|
||||||
|
|||||||
@ -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[]>();
|
||||||
|
|||||||
@ -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
|
||||||
|
{
|
||||||
|
message = $"🔒 *Welcome to {BotConfig.BrandName}*\n\n" +
|
||||||
|
"🛡️ *Your Privacy Matters:*\n" +
|
||||||
|
"• No account required\n" +
|
||||||
|
"• Ephemeral sessions by default\n" +
|
||||||
|
"• Optional PGP encryption for shipping\n" +
|
||||||
|
"• Cryptocurrency payments only\n\n" +
|
||||||
|
"🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" +
|
||||||
|
"Use /help for available commands or choose from the menu below:";
|
||||||
|
}
|
||||||
|
|
||||||
return $"🔒 *Welcome to {BotConfig.BrandName}*\n\n" +
|
// Add basket summary if items present
|
||||||
"🛡️ *Your Privacy Matters:*\n" +
|
if (cart != null && !cart.IsEmpty())
|
||||||
"• No account required\n" +
|
{
|
||||||
"• Ephemeral sessions by default\n" +
|
message += $"\n\n🛒 *Basket:* {cart.GetTotalItems()} item(s) - £{cart.GetTotalAmount():F2}";
|
||||||
"• Optional PGP encryption for shipping\n" +
|
}
|
||||||
"• Cryptocurrency payments only\n\n" +
|
|
||||||
"🖼️ *New Feature:* Browse products with beautiful image carousels!\n\n" +
|
return message;
|
||||||
"Use /help for available commands or choose from the menu below:";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatCategories(List<Category> categories)
|
public static string FormatCategories(List<Category> categories)
|
||||||
@ -272,8 +284,11 @@ namespace TeleBot.UI
|
|||||||
{
|
{
|
||||||
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");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user