- Fixed CryptoAmount property that doesn't exist - Display £ amounts with note about needing conversion - Show actual crypto amounts when less than 1.0
437 lines
17 KiB
C#
437 lines
17 KiB
C#
using System;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using LittleShop.Client.Models;
|
||
using TeleBot.Models;
|
||
|
||
namespace TeleBot.UI
|
||
{
|
||
public static class MessageFormatter
|
||
{
|
||
public static string FormatWelcome(bool isReturning)
|
||
{
|
||
if (isReturning)
|
||
{
|
||
return $"🔒 *Welcome back to {BotConfig.BrandName}*\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" +
|
||
"How can I help you today?";
|
||
}
|
||
|
||
return $"🔒 *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:";
|
||
}
|
||
|
||
public static string FormatCategories(List<Category> categories)
|
||
{
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("**PRODUCT CATEGORIES**\n");
|
||
sb.AppendLine("Choose a category to start shopping:");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatProductList(PagedResult<Product> products, string? categoryName = null)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
if (!string.IsNullOrEmpty(categoryName))
|
||
{
|
||
sb.AppendLine($"📦 *Products in {categoryName}*\n");
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine("📦 *All Products*\n");
|
||
}
|
||
|
||
if (!products.Items.Any())
|
||
{
|
||
sb.AppendLine("No products available in this category.");
|
||
return sb.ToString();
|
||
}
|
||
|
||
foreach (var product in products.Items)
|
||
{
|
||
sb.AppendLine($"*{product.Name}*");
|
||
sb.AppendLine($"💰 Price: £{product.Price:F2}");
|
||
|
||
if (!string.IsNullOrEmpty(product.Description))
|
||
{
|
||
var desc = product.Description.Length > 100
|
||
? product.Description.Substring(0, 97) + "..."
|
||
: product.Description;
|
||
sb.AppendLine($"_{desc}_");
|
||
}
|
||
|
||
sb.AppendLine();
|
||
}
|
||
|
||
if (products.TotalPages > 1)
|
||
{
|
||
sb.AppendLine($"Page {products.PageNumber} of {products.TotalPages}");
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatSingleProduct(Product product)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
sb.AppendLine($"🛍️ *{product.Name}*");
|
||
|
||
// Show multi-buys if available
|
||
if (product.MultiBuys?.Any() == true)
|
||
{
|
||
var lowestPrice = product.MultiBuys.Min(mb => mb.PricePerUnit);
|
||
sb.AppendLine($"💰 From £{lowestPrice:F2}");
|
||
sb.AppendLine($"📦 _{product.MultiBuys.Count} multi-buy options_");
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine($"💰 £{product.Price:F2}");
|
||
}
|
||
|
||
// Show variants if available
|
||
if (product.Variants?.Any() == true)
|
||
{
|
||
var variantTypes = product.Variants.Select(v => v.VariantType).Distinct().FirstOrDefault() ?? "options";
|
||
sb.AppendLine($"🎨 _{product.Variants.Count} {variantTypes.ToLower()} available_");
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(product.Description))
|
||
{
|
||
// Truncate description for bubble format
|
||
var desc = product.Description.Length > 100
|
||
? product.Description.Substring(0, 100) + "..."
|
||
: product.Description;
|
||
sb.AppendLine($"\n_{desc}_");
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatProductDetail(Product product)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
sb.AppendLine($"🛍️ *{product.Name}*\n");
|
||
sb.AppendLine($"💰 *Price:* £{product.Price:F2}");
|
||
sb.AppendLine($"⚖️ *Weight:* {product.Weight} {product.WeightUnit}");
|
||
sb.AppendLine($"📁 *Category:* {product.CategoryName ?? "Uncategorized"}");
|
||
|
||
if (!string.IsNullOrEmpty(product.Description))
|
||
{
|
||
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
|
||
}
|
||
|
||
if (product.Photos.Any())
|
||
{
|
||
sb.AppendLine($"\n🖼️ _{product.Photos.Count} photo(s) available_");
|
||
}
|
||
|
||
sb.AppendLine("\nSelect quantity and add to cart:");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatProductWithMultiBuys(Product product)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
sb.AppendLine($"🛍️ *{product.Name}*\n");
|
||
|
||
if (product.MultiBuys?.Any() == true)
|
||
{
|
||
sb.AppendLine("📦 *Multi-Buy Options:*\n");
|
||
foreach (var multiBuy in product.MultiBuys.OrderBy(mb => mb.Quantity))
|
||
{
|
||
var savings = multiBuy.Quantity > 1
|
||
? $" (£{multiBuy.PricePerUnit:F2} each)"
|
||
: "";
|
||
sb.AppendLine($"• *{multiBuy.Name}*: £{multiBuy.Price:F2}{savings}");
|
||
if (!string.IsNullOrEmpty(multiBuy.Description))
|
||
{
|
||
sb.AppendLine($" _{multiBuy.Description}_");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine($"💰 *Price:* £{product.Price:F2}");
|
||
}
|
||
|
||
if (product.Variants?.Any() == true)
|
||
{
|
||
var variantTypes = product.Variants.Select(v => v.VariantType).Distinct().FirstOrDefault() ?? "Variant";
|
||
sb.AppendLine($"\n🎨 *{variantTypes} Options:*");
|
||
var variantNames = string.Join(", ", product.Variants.OrderBy(v => v.SortOrder).Select(v => v.Name));
|
||
sb.AppendLine($"_{variantNames}_");
|
||
}
|
||
|
||
sb.AppendLine($"\n⚖️ *Weight:* {product.Weight} {product.WeightUnit}");
|
||
sb.AppendLine($"📁 *Category:* {product.CategoryName ?? "Uncategorized"}");
|
||
|
||
if (!string.IsNullOrEmpty(product.Description))
|
||
{
|
||
sb.AppendLine($"\n📝 *Description:*\n{product.Description}");
|
||
}
|
||
|
||
sb.AppendLine("\n*Select an option to add to cart:*");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatCart(ShoppingCart cart)
|
||
{
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("🛒 *Shopping Cart*\n");
|
||
|
||
if (cart.IsEmpty())
|
||
{
|
||
sb.AppendLine("Your cart is empty.\n");
|
||
sb.AppendLine("Browse products to add items to your cart.");
|
||
return sb.ToString();
|
||
}
|
||
|
||
foreach (var item in cart.Items)
|
||
{
|
||
sb.AppendLine($"• *{item.ProductName}*");
|
||
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}*");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatOrderSummary(OrderFlowData orderFlow, ShoppingCart cart)
|
||
{
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("📋 *Order Summary*\n");
|
||
|
||
sb.AppendLine("*Shipping Information:*");
|
||
|
||
if (orderFlow.UsePGPEncryption)
|
||
{
|
||
sb.AppendLine("🔐 _Shipping details will be PGP encrypted_");
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine($"Name: {orderFlow.ShippingName}");
|
||
sb.AppendLine($"Address: {orderFlow.ShippingAddress}");
|
||
sb.AppendLine($"City: {orderFlow.ShippingCity}");
|
||
sb.AppendLine($"Post Code: {orderFlow.ShippingPostCode}");
|
||
sb.AppendLine($"Country: {orderFlow.ShippingCountry}");
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(orderFlow.Notes))
|
||
{
|
||
sb.AppendLine($"Notes: {orderFlow.Notes}");
|
||
}
|
||
|
||
sb.AppendLine($"\n*Order Total: £{cart.GetTotalAmount():F2}*");
|
||
sb.AppendLine($"Items: {cart.GetTotalItems()}");
|
||
|
||
sb.AppendLine("\nPlease confirm your order:");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatOrder(Order order)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
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($"*Created:* {order.CreatedAt:yyyy-MM-dd HH:mm} UTC");
|
||
|
||
if (order.PaidAt.HasValue)
|
||
{
|
||
sb.AppendLine($"*Paid:* {order.PaidAt.Value:yyyy-MM-dd HH:mm} UTC");
|
||
}
|
||
|
||
if (order.ShippedAt.HasValue)
|
||
{
|
||
sb.AppendLine($"*Shipped:* {order.ShippedAt.Value:yyyy-MM-dd HH:mm} UTC");
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(order.TrackingNumber))
|
||
{
|
||
sb.AppendLine($"*Tracking:* `{order.TrackingNumber}`");
|
||
}
|
||
|
||
if (order.Items.Any())
|
||
{
|
||
sb.AppendLine("\n*Items:*");
|
||
foreach (var item in order.Items)
|
||
{
|
||
sb.AppendLine($"• {item.ProductName} - Qty: {item.Quantity} - £{item.TotalPrice:F2}");
|
||
}
|
||
}
|
||
|
||
if (order.Payments.Any())
|
||
{
|
||
sb.AppendLine("\n*Payments:*");
|
||
foreach (var payment in order.Payments)
|
||
{
|
||
sb.AppendLine($"• {FormatCurrency(payment.Currency)}: {FormatPaymentStatus(payment.Status)}");
|
||
if (!string.IsNullOrEmpty(payment.WalletAddress))
|
||
{
|
||
sb.AppendLine($" Address: `{payment.WalletAddress}`");
|
||
}
|
||
// Check if amount looks like a crypto amount (less than 1 for typical orders)
|
||
var isCryptoAmount = payment.RequiredAmount < 1.0m;
|
||
var amountDisplay = isCryptoAmount
|
||
? $"{payment.RequiredAmount:F8} {FormatCurrency(payment.Currency)}"
|
||
: $"£{payment.RequiredAmount:F2} (needs conversion to {FormatCurrency(payment.Currency)})";
|
||
sb.AppendLine($" Amount: {amountDisplay}");
|
||
}
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatPayment(CryptoPayment payment)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
sb.AppendLine($"💰 *Payment Instructions*\n");
|
||
sb.AppendLine($"*Currency:* {FormatCurrency(payment.Currency)}");
|
||
// Check if amount looks like a crypto amount (less than 1 for typical orders)
|
||
var isCryptoAmount = payment.RequiredAmount < 1.0m;
|
||
if (isCryptoAmount)
|
||
{
|
||
sb.AppendLine($"*Amount:* `{payment.RequiredAmount:F8} {FormatCurrency(payment.Currency)}`");
|
||
}
|
||
else
|
||
{
|
||
// This looks like a GBP amount that needs conversion
|
||
sb.AppendLine($"*Order Total:* £{payment.RequiredAmount:F2}");
|
||
sb.AppendLine($"*Note:* Amount needs conversion to {FormatCurrency(payment.Currency)}");
|
||
sb.AppendLine($"*Please check payment link for actual crypto amount*");
|
||
}
|
||
sb.AppendLine($"*Status:* {FormatPaymentStatus(payment.Status)}");
|
||
sb.AppendLine($"*Expires:* {payment.ExpiresAt:yyyy-MM-dd HH:mm} UTC");
|
||
|
||
if (isCryptoAmount)
|
||
{
|
||
sb.AppendLine($"\n*Send exactly {payment.RequiredAmount:F8} {FormatCurrency(payment.Currency)} to:*");
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine($"\n*Wallet Address:*");
|
||
}
|
||
sb.AppendLine($"`{payment.WalletAddress}`");
|
||
|
||
if (!string.IsNullOrEmpty(payment.BTCPayCheckoutUrl))
|
||
{
|
||
sb.AppendLine($"\n*Alternative Payment Link:*");
|
||
sb.AppendLine(payment.BTCPayCheckoutUrl);
|
||
}
|
||
|
||
sb.AppendLine("\n⚠️ *Important:*");
|
||
sb.AppendLine("• Send the exact amount shown");
|
||
sb.AppendLine("• Payment must be received before expiry");
|
||
sb.AppendLine("• Save this information before closing");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static string FormatHelp()
|
||
{
|
||
return "*Available Commands:*\n\n" +
|
||
"/start - Start shopping\n" +
|
||
"/browse - Browse categories\n" +
|
||
"/products - View all products\n" +
|
||
"/cart - View shopping cart\n" +
|
||
"/orders - View your orders\n" +
|
||
"/review - Review delivered products\n" +
|
||
"/reviews - View all product reviews\n" +
|
||
"/support - Chat with support\n" +
|
||
"/privacy - Privacy policy\n" +
|
||
"/clear - Clear shopping cart\n" +
|
||
"/cancel - Cancel current operation\n" +
|
||
"/delete - Delete all your data\n" +
|
||
"/help - Show this help message\n\n";
|
||
}
|
||
|
||
public static string FormatPrivacyPolicy()
|
||
{
|
||
return "🔒 *Privacy Policy*\n\n" +
|
||
"*Data Collection:*\n" +
|
||
"• We store minimal data necessary for orders\n" +
|
||
"• No personal identifiers are stored\n" +
|
||
"• All sessions are ephemeral by default\n\n" +
|
||
"*Data Retention:*\n" +
|
||
"• Ephemeral sessions: 30 minutes\n" +
|
||
"• Order data: As required for fulfillment\n" +
|
||
"• No tracking or analytics without consent\n\n" +
|
||
"*Your Rights:*\n" +
|
||
"• Delete all data at any time (/delete)\n" +
|
||
"• Use PGP encryption for shipping\n" +
|
||
"• Access via Tor network\n" +
|
||
"• Cryptocurrency payments only\n\n" +
|
||
"*Security:*\n" +
|
||
"• All data encrypted at rest\n" +
|
||
"• Optional Tor routing\n" +
|
||
"• No third-party tracking\n" +
|
||
"• Open source and auditable";
|
||
}
|
||
|
||
private static string FormatOrderStatus(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
0 => "⏳ Pending Payment",
|
||
1 => "💰 Payment Received",
|
||
2 => "⚙️ Processing",
|
||
3 => "📦 Picking & Packing",
|
||
4 => "🚚 Shipped",
|
||
5 => "✅ Delivered",
|
||
6 => "❌ Cancelled",
|
||
7 => "💸 Refunded",
|
||
_ => $"Status {status}"
|
||
};
|
||
}
|
||
|
||
private static string FormatPaymentStatus(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
0 => "⏳ Pending",
|
||
1 => "✅ Paid",
|
||
2 => "❌ Failed",
|
||
3 => "⏰ Expired",
|
||
4 => "❌ Cancelled",
|
||
_ => $"Status {status}"
|
||
};
|
||
}
|
||
|
||
private static string FormatCurrency(int currency)
|
||
{
|
||
return currency switch
|
||
{
|
||
0 => "BTC",
|
||
1 => "XMR",
|
||
2 => "USDT",
|
||
3 => "LTC",
|
||
4 => "ETH",
|
||
5 => "ZEC",
|
||
6 => "DASH",
|
||
7 => "DOGE",
|
||
_ => $"Currency {currency}"
|
||
};
|
||
}
|
||
}
|
||
} |