diff --git a/LittleShop/Controllers/CurrencyController.cs b/LittleShop/Controllers/CurrencyController.cs index 57df2fc..5a5e728 100644 --- a/LittleShop/Controllers/CurrencyController.cs +++ b/LittleShop/Controllers/CurrencyController.cs @@ -32,23 +32,27 @@ public class CurrencyController : ControllerBase // Get SilverPay supported currencies var silverPayCurrencies = await _silverPayService.GetSupportedCurrenciesAsync(); - // Production currencies (always enabled if supported by SilverPay) - var productionCurrencies = new[] { "BTC", "ETH" }; - foreach (var currency in productionCurrencies) - { - if (silverPayCurrencies.Contains(currency)) - { - availableCurrencies.Add(currency); - } - } + // Test currencies that should be excluded unless explicitly enabled + var testCurrencies = new[] { "TBTC", "TETH", "TLTC", "TESTBTC", "TESTNET" }; - // Test currencies (enabled via admin settings) - var testCurrencies = new[] { "TBTC", "TLTC" }; - foreach (var currency in testCurrencies) + // Add all SilverPay supported currencies except test ones + foreach (var currency in silverPayCurrencies) { - if (silverPayCurrencies.Contains(currency) && - await _systemSettingsService.IsTestCurrencyEnabledAsync(currency)) + var isTestCurrency = testCurrencies.Any(tc => + currency.Contains(tc, StringComparison.OrdinalIgnoreCase) || + currency.StartsWith("T", StringComparison.OrdinalIgnoreCase)); + + if (isTestCurrency) { + // Only add test currencies if explicitly enabled in settings + if (await _systemSettingsService.IsTestCurrencyEnabledAsync(currency)) + { + availableCurrencies.Add(currency); + } + } + else + { + // Add all production currencies availableCurrencies.Add(currency); } } diff --git a/LittleShop/Services/OrderService.cs b/LittleShop/Services/OrderService.cs index 4eabb2c..bc74b3a 100644 --- a/LittleShop/Services/OrderService.cs +++ b/LittleShop/Services/OrderService.cs @@ -511,7 +511,18 @@ public class OrderService : IOrderService public async Task> GetOrdersRequiringActionAsync() { - return await GetOrdersByStatusAsync(OrderStatus.PaymentReceived); + var orders = await _context.Orders + .Include(o => o.Customer) + .Include(o => o.Items) + .ThenInclude(oi => oi.Product) + .Include(o => o.Items) + .ThenInclude(oi => oi.ProductMultiBuy) + .Include(o => o.Payments) + .Where(o => o.Status == OrderStatus.PendingPayment || o.Status == OrderStatus.PaymentReceived) + .OrderByDescending(o => o.CreatedAt) + .ToListAsync(); + + return orders.Select(MapToDto); } public async Task> GetOrdersForPackingAsync() diff --git a/TeleBot/TeleBot/Handlers/CallbackHandler.cs b/TeleBot/TeleBot/Handlers/CallbackHandler.cs index 0f8a84d..29f2b00 100644 --- a/TeleBot/TeleBot/Handlers/CallbackHandler.cs +++ b/TeleBot/TeleBot/Handlers/CallbackHandler.cs @@ -649,9 +649,13 @@ namespace TeleBot.Handlers return; } - // Clear cart after successful order + // Backup cart in case payment fails - we'll need to restore it + var cartBackup = System.Text.Json.JsonSerializer.Serialize(session.Cart); + session.TempData["cart_backup"] = cartBackup; + + // Clear cart after successful order (will restore if payment fails) session.Cart.Clear(); - + // Store order ID for payment session.TempData["current_order_id"] = order.Id; @@ -700,6 +704,20 @@ namespace TeleBot.Handlers if (payment == null) { + // Restore cart from backup since payment failed + if (session.TempData.TryGetValue("cart_backup", out var cartBackupObj) && cartBackupObj is string cartBackupJson) + { + try + { + session.Cart = System.Text.Json.JsonSerializer.Deserialize(cartBackupJson) ?? new ShoppingCart(); + session.TempData.Remove("cart_backup"); + } + catch + { + // If restoration fails, cart remains empty + } + } + await SafeEditMessageAsync( bot, message.Chat.Id, @@ -709,22 +727,39 @@ namespace TeleBot.Handlers $"This might be due to:\n" + $"• Payment gateway temporarily unavailable\n" + $"• Network connectivity issues\n\n" + - $"Please try again in a few minutes.", + $"Your cart has been restored. Please try again.", Telegram.Bot.Types.Enums.ParseMode.Markdown, - MenuBuilder.MainMenu() + MenuBuilder.CartMenu(session.Cart) ); return; } - // Payment created successfully, continue with display + // Payment created successfully, remove cart backup + session.TempData.Remove("cart_backup"); + + // Continue with display var paymentText = MessageFormatter.FormatPayment(payment); - + await DisplayPaymentInfo(bot, message, payment, paymentText); } catch (Exception ex) { _logger.LogError(ex, "Failed to create payment for order {OrderId} with currency {Currency}", orderId, currency); - + + // Restore cart from backup since payment failed + if (session.TempData.TryGetValue("cart_backup", out var cartBackupObj) && cartBackupObj is string cartBackupJson) + { + try + { + session.Cart = System.Text.Json.JsonSerializer.Deserialize(cartBackupJson) ?? new ShoppingCart(); + session.TempData.Remove("cart_backup"); + } + catch + { + // If restoration fails, cart remains empty + } + } + await SafeEditMessageAsync( bot, message.Chat.Id, @@ -732,9 +767,9 @@ namespace TeleBot.Handlers $"āŒ *Payment System Error*\n\n" + $"Sorry, there was a technical issue creating your {currency} payment.\n\n" + $"Our payment system may be undergoing maintenance.\n" + - $"Please try again later or contact support.", + $"Your cart has been restored. Please try again later.", Telegram.Bot.Types.Enums.ParseMode.Markdown, - MenuBuilder.MainMenu() + MenuBuilder.CartMenu(session.Cart) ); return; } diff --git a/TeleBot/TeleBot/Handlers/MessageHandler.cs b/TeleBot/TeleBot/Handlers/MessageHandler.cs index 8f0ffc7..568af87 100644 --- a/TeleBot/TeleBot/Handlers/MessageHandler.cs +++ b/TeleBot/TeleBot/Handlers/MessageHandler.cs @@ -98,104 +98,46 @@ namespace TeleBot.Handlers switch (session.OrderFlow.CurrentStep) { case OrderFlowStep.CollectingName: - if (string.IsNullOrWhiteSpace(input)) + // Parse multi-line address input + var lines = input.Split('\n', StringSplitOptions.TrimEntries); + + if (lines.Length < 4) { await bot.SendTextMessageAsync( message.Chat.Id, - "āŒ Name cannot be empty. Please enter your shipping name:" + "šŸ“¦ *Checkout - Delivery Details*\n\n" + + "Please provide all delivery details in one message:\n\n" + + "• Shipping Name\n" + + "• Street Address\n" + + "• City Name\n" + + "• Post/Zip Code\n" + + "• Country (or leave blank for UK)\n\n" + + "_Example:_\n" + + "`John Smith\n" + + "123 Main Street\n" + + "London\n" + + "SW1A 1AA\n" + + "United Kingdom`", + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown ); return; } - - session.OrderFlow.ShippingName = input; - session.OrderFlow.CurrentStep = OrderFlowStep.CollectingAddress; - - await bot.SendTextMessageAsync( - message.Chat.Id, - "šŸ“¦ *Checkout - Step 2/5*\n\n" + - "Please enter your shipping address:\n\n" + - "_Reply with your street address_", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown - ); - break; - - case OrderFlowStep.CollectingAddress: - if (string.IsNullOrWhiteSpace(input)) - { - await bot.SendTextMessageAsync( - message.Chat.Id, - "āŒ Address cannot be empty. Please enter your shipping address:" - ); - return; - } - - session.OrderFlow.ShippingAddress = input; - session.OrderFlow.CurrentStep = OrderFlowStep.CollectingCity; - - await bot.SendTextMessageAsync( - message.Chat.Id, - "šŸ“¦ *Checkout - Step 3/5*\n\n" + - "Please enter your city:\n\n" + - "_Reply with your city name_", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown - ); - break; - - case OrderFlowStep.CollectingCity: - if (string.IsNullOrWhiteSpace(input)) - { - await bot.SendTextMessageAsync( - message.Chat.Id, - "āŒ City cannot be empty. Please enter your city:" - ); - return; - } - - session.OrderFlow.ShippingCity = input; - session.OrderFlow.CurrentStep = OrderFlowStep.CollectingPostCode; - - await bot.SendTextMessageAsync( - message.Chat.Id, - "šŸ“¦ *Checkout - Step 4/5*\n\n" + - "Please enter your postal/ZIP code:\n\n" + - "_Reply with your postal code_", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown - ); - break; - - case OrderFlowStep.CollectingPostCode: - if (string.IsNullOrWhiteSpace(input)) - { - await bot.SendTextMessageAsync( - message.Chat.Id, - "āŒ Postal code cannot be empty. Please enter your postal code:" - ); - return; - } - - session.OrderFlow.ShippingPostCode = input; - session.OrderFlow.CurrentStep = OrderFlowStep.CollectingCountry; - - await bot.SendTextMessageAsync( - message.Chat.Id, - "šŸ“¦ *Checkout - Step 5/5*\n\n" + - "Please enter your country (or press Enter for United Kingdom):\n\n" + - "_Reply with your country name_", - parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown - ); - break; - - case OrderFlowStep.CollectingCountry: - if (!string.IsNullOrWhiteSpace(input)) - { - session.OrderFlow.ShippingCountry = input; - } - + + // Parse the address fields + session.OrderFlow.ShippingName = lines[0]; + session.OrderFlow.ShippingAddress = lines[1]; + session.OrderFlow.ShippingCity = lines[2]; + session.OrderFlow.ShippingPostCode = lines[3]; + session.OrderFlow.ShippingCountry = lines.Length > 4 && !string.IsNullOrWhiteSpace(lines[4]) + ? lines[4] + : "United Kingdom"; + + // Skip to review step session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder; - + // Show order summary var summary = MessageFormatter.FormatOrderSummary(session.OrderFlow, session.Cart); - + await bot.SendTextMessageAsync( message.Chat.Id, summary, @@ -204,6 +146,32 @@ namespace TeleBot.Handlers ); break; + // Legacy steps - redirect to new single-message collection + case OrderFlowStep.CollectingAddress: + case OrderFlowStep.CollectingCity: + case OrderFlowStep.CollectingPostCode: + case OrderFlowStep.CollectingCountry: + // Reset to name collection with new format + session.OrderFlow.CurrentStep = OrderFlowStep.CollectingName; + await bot.SendTextMessageAsync( + message.Chat.Id, + "šŸ“¦ *Checkout - Delivery Details*\n\n" + + "Please provide all delivery details in one message:\n\n" + + "• Shipping Name\n" + + "• Street Address\n" + + "• City Name\n" + + "• Post/Zip Code\n" + + "• Country (or leave blank for UK)\n\n" + + "_Example:_\n" + + "`John Smith\n" + + "123 Main Street\n" + + "London\n" + + "SW1A 1AA\n" + + "United Kingdom`", + parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown + ); + break; + case OrderFlowStep.CollectingNotes: session.OrderFlow.Notes = string.IsNullOrWhiteSpace(input) ? null : input; session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder; diff --git a/TeleBot/TeleBot/UI/MessageFormatter.cs b/TeleBot/TeleBot/UI/MessageFormatter.cs index e51f77d..2b69ac8 100644 --- a/TeleBot/TeleBot/UI/MessageFormatter.cs +++ b/TeleBot/TeleBot/UI/MessageFormatter.cs @@ -59,7 +59,7 @@ namespace TeleBot.UI foreach (var product in products.Items) { sb.AppendLine($"*{product.Name}*"); - sb.AppendLine($"šŸ’° Price: ${product.Price:F2}"); + sb.AppendLine($"šŸ’° Price: Ā£{product.Price:F2}"); if (!string.IsNullOrEmpty(product.Description)) { @@ -122,7 +122,7 @@ namespace TeleBot.UI var sb = new StringBuilder(); sb.AppendLine($"šŸ›ļø *{product.Name}*\n"); - sb.AppendLine($"šŸ’° *Price:* ${product.Price:F2}"); + sb.AppendLine($"šŸ’° *Price:* Ā£{product.Price:F2}"); sb.AppendLine($"āš–ļø *Weight:* {product.Weight} {product.WeightUnit}"); sb.AppendLine($"šŸ“ *Category:* {product.CategoryName ?? "Uncategorized"}"); @@ -203,12 +203,12 @@ namespace TeleBot.UI foreach (var item in cart.Items) { sb.AppendLine($"• *{item.ProductName}*"); - sb.AppendLine($" Qty: {item.Quantity} Ɨ ${item.UnitPrice:F2} = *${item.TotalPrice:F2}*"); + sb.AppendLine($" Qty: {item.Quantity} Ɨ Ā£{item.UnitPrice:F2} = *Ā£{item.TotalPrice:F2}*"); } sb.AppendLine($"\nšŸ“Š *Summary:*"); sb.AppendLine($"Items: {cart.GetTotalItems()}"); - sb.AppendLine($"*Total: ${cart.GetTotalAmount():F2}*"); + sb.AppendLine($"*Total: Ā£{cart.GetTotalAmount():F2}*"); return sb.ToString(); } @@ -238,7 +238,7 @@ namespace TeleBot.UI sb.AppendLine($"Notes: {orderFlow.Notes}"); } - sb.AppendLine($"\n*Order Total: ${cart.GetTotalAmount():F2}*"); + sb.AppendLine($"\n*Order Total: Ā£{cart.GetTotalAmount():F2}*"); sb.AppendLine($"Items: {cart.GetTotalItems()}"); sb.AppendLine("\nPlease confirm your order:"); @@ -253,7 +253,7 @@ namespace TeleBot.UI sb.AppendLine($"šŸ“¦ *Order Details*\n"); sb.AppendLine($"*Order ID:* `{order.Id}`"); 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"); if (order.PaidAt.HasValue) @@ -276,7 +276,7 @@ namespace TeleBot.UI sb.AppendLine("\n*Items:*"); foreach (var item in order.Items) { - sb.AppendLine($"• {item.ProductName} - Qty: {item.Quantity} - ${item.TotalPrice:F2}"); + sb.AppendLine($"• {item.ProductName} - Qty: {item.Quantity} - Ā£{item.TotalPrice:F2}"); } } diff --git a/TeleBot/TeleBot/appsettings.json b/TeleBot/TeleBot/appsettings.json index 0cc6393..21711e4 100644 --- a/TeleBot/TeleBot/appsettings.json +++ b/TeleBot/TeleBot/appsettings.json @@ -70,9 +70,9 @@ }, "Cryptocurrencies": [ "BTC", - "XMR", + "ETH", "LTC", - "DASH" + "DOGE" ], "Kestrel": { "Endpoints": {