"Fix-BUTTON_DATA_INVALID-and-add-multi-buy-buttons"

This commit is contained in:
sysadmin 2025-10-03 15:26:52 +01:00
parent c961dfa47a
commit 694ce15549
7 changed files with 203 additions and 61 deletions

View File

@ -29,6 +29,8 @@ namespace TeleBot.Handlers
private readonly IBotActivityTracker _activityTracker; private readonly IBotActivityTracker _activityTracker;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILogger<CallbackHandler> _logger; private readonly ILogger<CallbackHandler> _logger;
private readonly MenuBuilder _menuBuilder;
private readonly CallbackDataMapper _mapper;
public CallbackHandler( public CallbackHandler(
ISessionManager sessionManager, ISessionManager sessionManager,
@ -37,7 +39,9 @@ namespace TeleBot.Handlers
IProductCarouselService carouselService, IProductCarouselService carouselService,
IBotActivityTracker activityTracker, IBotActivityTracker activityTracker,
IConfiguration configuration, IConfiguration configuration,
ILogger<CallbackHandler> logger) ILogger<CallbackHandler> logger,
MenuBuilder menuBuilder,
CallbackDataMapper mapper)
{ {
_sessionManager = sessionManager; _sessionManager = sessionManager;
_shopService = shopService; _shopService = shopService;
@ -46,6 +50,8 @@ namespace TeleBot.Handlers
_activityTracker = activityTracker; _activityTracker = activityTracker;
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_menuBuilder = menuBuilder;
_mapper = mapper;
} }
public async Task HandleCallbackAsync(ITelegramBotClient bot, CallbackQuery callbackQuery) public async Task HandleCallbackAsync(ITelegramBotClient bot, CallbackQuery callbackQuery)
@ -115,7 +121,11 @@ namespace TeleBot.Handlers
break; break;
case "remove": case "remove":
await HandleRemoveFromCart(bot, callbackQuery, session, Guid.Parse(data[1])); {
var (_, productId, _, _) = _mapper.ParseCallback(callbackQuery.Data);
if (productId.HasValue)
await HandleRemoveFromCart(bot, callbackQuery, session, productId.Value);
}
break; break;
case "clear_cart": case "clear_cart":
@ -372,32 +382,40 @@ namespace TeleBot.Handlers
private async Task HandleQuantityChange(ITelegramBotClient bot, Message message, UserSession session, string[] data) private async Task HandleQuantityChange(ITelegramBotClient bot, Message message, UserSession session, string[] data)
{ {
// Format: qty:productId:newQuantity // Parse callback data using mapper
var productId = Guid.Parse(data[1]); var (_, productId, quantity, _) = _mapper.ParseCallback(string.Join(":", data));
var quantity = int.Parse(data[2]);
var product = await _shopService.GetProductAsync(productId); if (!productId.HasValue || !quantity.HasValue)
return;
var product = await _shopService.GetProductAsync(productId.Value);
if (product == null) if (product == null)
return; return;
session.TempData["current_quantity"] = quantity; session.TempData["current_quantity"] = quantity.Value;
await bot.EditMessageReplyMarkupAsync( await bot.EditMessageReplyMarkupAsync(
message.Chat.Id, message.Chat.Id,
message.MessageId, message.MessageId,
MenuBuilder.ProductDetailMenu(product, quantity) _menuBuilder.ProductDetailMenu(product, quantity.Value)
); );
} }
private async Task HandleAddToCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data) private async Task HandleAddToCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data)
{ {
// Format: add:productId:quantity or add:productId:quantity:multiBuyId or add:productId:quantity:multiBuyId:variant // Parse callback data using mapper
var productId = Guid.Parse(data[1]); var (_, productId, quantity, multiBuyId) = _mapper.ParseCallback(string.Join(":", data));
var quantity = int.Parse(data[2]);
Guid? multiBuyId = data.Length > 3 && !data[3].Contains(":") ? Guid.Parse(data[3]) : null;
string? selectedVariant = data.Length > 4 ? data[4] : (data.Length > 3 && data[3].Contains(":") ? data[3] : null);
var product = await _shopService.GetProductAsync(productId); if (!productId.HasValue || !quantity.HasValue)
{
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Invalid product data", showAlert: true);
return;
}
// Check for variant in data (old format compatibility)
string? selectedVariant = data.Length > 4 ? data[4] : null;
var product = await _shopService.GetProductAsync(productId.Value);
if (product == null) if (product == null)
{ {
await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true); await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true);
@ -407,13 +425,15 @@ namespace TeleBot.Handlers
// If product has variants but none selected, show variant selection // If product has variants but none selected, show variant selection
if (selectedVariant == null && product.Variants?.Any() == true) if (selectedVariant == null && product.Variants?.Any() == true)
{ {
await ShowVariantSelection(bot, callbackQuery.Message!, session, product, quantity, multiBuyId); await ShowVariantSelection(bot, callbackQuery.Message!, session, product, quantity.Value, multiBuyId);
return; return;
} }
// Get price based on multi-buy or base product // Get price based on multi-buy or base product
decimal price = product.Price; decimal price = product.Price;
string itemName = product.Name; string itemName = product.Name;
int finalQuantity = quantity.Value;
if (multiBuyId.HasValue && product.MultiBuys != null) if (multiBuyId.HasValue && product.MultiBuys != null)
{ {
var multiBuy = product.MultiBuys.FirstOrDefault(mb => mb.Id == multiBuyId); var multiBuy = product.MultiBuys.FirstOrDefault(mb => mb.Id == multiBuyId);
@ -421,7 +441,7 @@ namespace TeleBot.Handlers
{ {
price = multiBuy.Price; price = multiBuy.Price;
itemName = $"{product.Name} ({multiBuy.Name})"; itemName = $"{product.Name} ({multiBuy.Name})";
quantity = multiBuy.Quantity; // Use multi-buy quantity finalQuantity = multiBuy.Quantity; // Use multi-buy quantity
} }
} }
@ -431,7 +451,7 @@ namespace TeleBot.Handlers
itemName += $" - {selectedVariant}"; itemName += $" - {selectedVariant}";
} }
session.Cart.AddItem(productId, itemName, price, quantity, multiBuyId, selectedVariant); session.Cart.AddItem(productId.Value, itemName, price, finalQuantity, multiBuyId, selectedVariant);
// Track add to cart action // Track add to cart action
await _activityTracker.TrackActivityAsync( await _activityTracker.TrackActivityAsync(
@ -439,13 +459,13 @@ namespace TeleBot.Handlers
ActivityTypes.AddToCart, ActivityTypes.AddToCart,
$"Added to cart: {itemName}", $"Added to cart: {itemName}",
product, product,
price * quantity, price * finalQuantity,
quantity finalQuantity
); );
await bot.AnswerCallbackQueryAsync( await bot.AnswerCallbackQueryAsync(
callbackQuery.Id, callbackQuery.Id,
$"✅ Added {quantity}x {itemName} to cart", $"✅ Added {finalQuantity}x {itemName} to cart",
showAlert: false showAlert: false
); );
@ -469,7 +489,7 @@ namespace TeleBot.Handlers
message.MessageId, message.MessageId,
MessageFormatter.FormatCart(session.Cart), MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
session.State = SessionState.ViewingCart; session.State = SessionState.ViewingCart;
} }
@ -480,7 +500,7 @@ namespace TeleBot.Handlers
chatId, chatId,
MessageFormatter.FormatCart(session.Cart), MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
session.State = SessionState.ViewingCart; session.State = SessionState.ViewingCart;
} }
@ -565,7 +585,7 @@ namespace TeleBot.Handlers
callbackQuery.Message!.Chat.Id, callbackQuery.Message!.Chat.Id,
MessageFormatter.FormatCart(session.Cart), MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
// Immediately proceed to checkout // Immediately proceed to checkout
@ -634,7 +654,7 @@ namespace TeleBot.Handlers
callbackQuery.Message!.Chat.Id, callbackQuery.Message!.Chat.Id,
MessageFormatter.FormatCart(session.Cart), MessageFormatter.FormatCart(session.Cart),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
// Immediately proceed to checkout // Immediately proceed to checkout
@ -738,7 +758,7 @@ namespace TeleBot.Handlers
message.Chat.Id, message.Chat.Id,
message.MessageId, message.MessageId,
"❌ Failed to create order. Please try again.", "❌ Failed to create order. Please try again.",
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
return; return;
} }
@ -823,7 +843,7 @@ namespace TeleBot.Handlers
$"• Network connectivity issues\n\n" + $"• Network connectivity issues\n\n" +
$"Your cart has been restored. Please try again.", $"Your cart has been restored. Please try again.",
Telegram.Bot.Types.Enums.ParseMode.Markdown, Telegram.Bot.Types.Enums.ParseMode.Markdown,
MenuBuilder.CartMenu(session.Cart) _menuBuilder.CartMenu(session.Cart)
); );
return; return;
} }
@ -863,7 +883,7 @@ namespace TeleBot.Handlers
$"Our payment system may be undergoing maintenance.\n" + $"Our payment system may be undergoing maintenance.\n" +
$"Your cart has been restored. Please try again later.", $"Your cart has been restored. Please try again later.",
Telegram.Bot.Types.Enums.ParseMode.Markdown, Telegram.Bot.Types.Enums.ParseMode.Markdown,
MenuBuilder.CartMenu(session.Cart) _menuBuilder.CartMenu(session.Cart)
); );
return; return;
} }

View File

@ -20,19 +20,22 @@ namespace TeleBot.Handlers
private readonly IPrivacyService _privacyService; private readonly IPrivacyService _privacyService;
private readonly IProductCarouselService _carouselService; private readonly IProductCarouselService _carouselService;
private readonly ILogger<CommandHandler> _logger; private readonly ILogger<CommandHandler> _logger;
private readonly MenuBuilder _menuBuilder;
public CommandHandler( public CommandHandler(
ISessionManager sessionManager, ISessionManager sessionManager,
ILittleShopService shopService, ILittleShopService shopService,
IPrivacyService privacyService, IPrivacyService privacyService,
IProductCarouselService carouselService, IProductCarouselService carouselService,
ILogger<CommandHandler> logger) ILogger<CommandHandler> logger,
MenuBuilder menuBuilder)
{ {
_sessionManager = sessionManager; _sessionManager = sessionManager;
_shopService = shopService; _shopService = shopService;
_privacyService = privacyService; _privacyService = privacyService;
_carouselService = carouselService; _carouselService = carouselService;
_logger = logger; _logger = logger;
_menuBuilder = menuBuilder;
} }
public async Task HandleCommandAsync(ITelegramBotClient bot, Message message, string command, string? args) public async Task HandleCommandAsync(ITelegramBotClient bot, Message message, string command, string? args)
@ -202,7 +205,7 @@ namespace TeleBot.Handlers
message.Chat.Id, message.Chat.Id,
text, text,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.CartMenu(session.Cart) replyMarkup: _menuBuilder.CartMenu(session.Cart)
); );
session.State = Models.SessionState.ViewingCart; session.State = Models.SessionState.ViewingCart;

View File

@ -16,6 +16,7 @@ using TeleBot;
using TeleBot.Handlers; using TeleBot.Handlers;
using TeleBot.Services; using TeleBot.Services;
using TeleBot.Http; using TeleBot.Http;
using TeleBot.UI;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var BrandName = "Little Shop"; var BrandName = "Little Shop";
@ -90,6 +91,10 @@ builder.Services.AddSingleton<ICommandHandler, CommandHandler>();
builder.Services.AddSingleton<ICallbackHandler, CallbackHandler>(); builder.Services.AddSingleton<ICallbackHandler, CallbackHandler>();
builder.Services.AddSingleton<IMessageHandler, MessageHandler>(); builder.Services.AddSingleton<IMessageHandler, MessageHandler>();
// Callback Data Mapper (for short IDs to avoid Telegram's 64-byte limit)
builder.Services.AddSingleton<CallbackDataMapper>();
builder.Services.AddSingleton<MenuBuilder>();
// Bot Manager Service (for registration and metrics) - Single instance with direct connection (internal API) // Bot Manager Service (for registration and metrics) - Single instance with direct connection (internal API)
builder.Services.AddHttpClient<BotManagerService>() builder.Services.AddHttpClient<BotManagerService>()
.ConfigurePrimaryHttpMessageHandler(sp => .ConfigurePrimaryHttpMessageHandler(sp =>

View File

@ -0,0 +1,100 @@
using System.Collections.Concurrent;
namespace TeleBot.Services;
/// <summary>
/// Maps long GUIDs to short IDs for Telegram callback data (64-byte limit)
/// </summary>
public class CallbackDataMapper
{
private readonly ConcurrentDictionary<string, Guid> _shortToGuid = new();
private readonly ConcurrentDictionary<Guid, string> _guidToShort = new();
private int _nextId = 1;
private readonly object _lock = new();
/// <summary>
/// Get or create a short ID for a GUID
/// </summary>
public string GetShortId(Guid guid, string prefix = "id")
{
if (_guidToShort.TryGetValue(guid, out var existing))
return existing;
lock (_lock)
{
// Double-check after acquiring lock
if (_guidToShort.TryGetValue(guid, out existing))
return existing;
var shortId = $"{prefix}{_nextId++}";
_shortToGuid[shortId] = guid;
_guidToShort[guid] = shortId;
return shortId;
}
}
/// <summary>
/// Decode a short ID back to GUID
/// </summary>
public Guid? DecodeShortId(string shortId)
{
return _shortToGuid.TryGetValue(shortId, out var guid) ? guid : null;
}
/// <summary>
/// Build callback data with short IDs
/// Format: action:shortProductId[:quantity[:shortMultiBuyId]]
/// </summary>
public string BuildCallback(string action, Guid productId, int? quantity = null, Guid? multiBuyId = null)
{
var parts = new List<string> { action, GetShortId(productId, "p") };
if (quantity.HasValue)
parts.Add(quantity.Value.ToString());
if (multiBuyId.HasValue)
parts.Add(GetShortId(multiBuyId.Value, "mb"));
var result = string.Join(":", parts);
// Ensure we don't exceed Telegram's 64-byte limit
if (result.Length > 63)
throw new InvalidOperationException($"Callback data too long ({result.Length} bytes): {result}");
return result;
}
/// <summary>
/// Parse callback data with short IDs
/// </summary>
public (string action, Guid? productId, int? quantity, Guid? multiBuyId) ParseCallback(string callbackData)
{
var parts = callbackData.Split(':');
var action = parts[0];
Guid? productId = null;
int? quantity = null;
Guid? multiBuyId = null;
if (parts.Length > 1)
productId = DecodeShortId(parts[1]);
if (parts.Length > 2 && int.TryParse(parts[2], out var qty))
quantity = qty;
if (parts.Length > 3)
multiBuyId = DecodeShortId(parts[3]);
return (action, productId, quantity, multiBuyId);
}
/// <summary>
/// Clear old mappings (call periodically to prevent memory buildup)
/// </summary>
public void ClearMappings()
{
_shortToGuid.Clear();
_guidToShort.Clear();
_nextId = 1;
}
}

View File

@ -29,15 +29,18 @@ namespace TeleBot.Services
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILogger<ProductCarouselService> _logger; private readonly ILogger<ProductCarouselService> _logger;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly MenuBuilder _menuBuilder;
private readonly string _imageCachePath; private readonly string _imageCachePath;
public ProductCarouselService( public ProductCarouselService(
IConfiguration configuration, IConfiguration configuration,
ILogger<ProductCarouselService> logger, ILogger<ProductCarouselService> logger,
HttpClient httpClient) HttpClient httpClient,
MenuBuilder menuBuilder)
{ {
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_menuBuilder = menuBuilder;
_httpClient = httpClient; _httpClient = httpClient;
_imageCachePath = Path.Combine(Environment.CurrentDirectory, "image_cache"); _imageCachePath = Path.Combine(Environment.CurrentDirectory, "image_cache");
@ -208,7 +211,7 @@ namespace TeleBot.Services
image, image,
caption: FormatProductCaption(product), caption: FormatProductCaption(product),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.ProductDetailMenu(product) replyMarkup: _menuBuilder.ProductDetailMenu(product)
); );
} }
else else
@ -218,7 +221,7 @@ namespace TeleBot.Services
chatId, chatId,
MessageFormatter.FormatProductDetail(product), MessageFormatter.FormatProductDetail(product),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.ProductDetailMenu(product) replyMarkup: _menuBuilder.ProductDetailMenu(product)
); );
} }
} }
@ -231,7 +234,7 @@ namespace TeleBot.Services
chatId, chatId,
MessageFormatter.FormatProductDetail(product), MessageFormatter.FormatProductDetail(product),
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: MenuBuilder.ProductDetailMenu(product) replyMarkup: _menuBuilder.ProductDetailMenu(product)
); );
} }
} }

View File

@ -20,8 +20,10 @@ namespace TeleBot
var config = new ConfigurationBuilder().Build(); var config = new ConfigurationBuilder().Build();
var logger = NullLogger<ProductCarouselService>.Instance; var logger = NullLogger<ProductCarouselService>.Instance;
var httpClient = new System.Net.Http.HttpClient(); var httpClient = new System.Net.Http.HttpClient();
var mapper = new TeleBot.Services.CallbackDataMapper();
var menuBuilder = new TeleBot.UI.MenuBuilder(mapper);
var carouselService = new ProductCarouselService(config, logger, httpClient); var carouselService = new ProductCarouselService(config, logger, httpClient, menuBuilder);
// Test image URL validation // Test image URL validation
var validUrls = new[] var validUrls = new[]
@ -43,8 +45,10 @@ namespace TeleBot
var config = new ConfigurationBuilder().Build(); var config = new ConfigurationBuilder().Build();
var logger = NullLogger<ProductCarouselService>.Instance; var logger = NullLogger<ProductCarouselService>.Instance;
var httpClient = new System.Net.Http.HttpClient(); var httpClient = new System.Net.Http.HttpClient();
var mapper = new TeleBot.Services.CallbackDataMapper();
var menuBuilder = new TeleBot.UI.MenuBuilder(mapper);
var carouselService = new ProductCarouselService(config, logger, httpClient); var carouselService = new ProductCarouselService(config, logger, httpClient, menuBuilder);
// Create a test product with image // Create a test product with image
var testProduct = new Product var testProduct = new Product

View File

@ -4,11 +4,18 @@ using System.Linq;
using LittleShop.Client.Models; using LittleShop.Client.Models;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
using TeleBot.Models; using TeleBot.Models;
using TeleBot.Services;
namespace TeleBot.UI namespace TeleBot.UI
{ {
public static class MenuBuilder public class MenuBuilder
{ {
private readonly CallbackDataMapper _mapper;
public MenuBuilder(CallbackDataMapper mapper)
{
_mapper = mapper;
}
public static InlineKeyboardMarkup MainMenu() public static InlineKeyboardMarkup MainMenu()
{ {
return new InlineKeyboardMarkup(new[] return new InlineKeyboardMarkup(new[]
@ -90,7 +97,7 @@ namespace TeleBot.UI
return new InlineKeyboardMarkup(buttons); return new InlineKeyboardMarkup(buttons);
} }
public static InlineKeyboardMarkup ProductDetailMenu(Product product, int quantity = 1) public InlineKeyboardMarkup ProductDetailMenu(Product product, int quantity = 1)
{ {
var buttons = new List<InlineKeyboardButton[]>(); var buttons = new List<InlineKeyboardButton[]>();
@ -106,14 +113,14 @@ namespace TeleBot.UI
foreach (var multiBuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity)) foreach (var multiBuy in product.MultiBuys.Where(mb => mb.IsActive).OrderBy(mb => mb.Quantity))
{ {
var label = $"{multiBuy.Name} - ${multiBuy.Price:F2}"; var label = $"{multiBuy.Name} - £{multiBuy.Price:F2}";
if (multiBuy.Quantity > 1) if (multiBuy.Quantity > 1)
label += $" (${multiBuy.PricePerUnit:F2}/each)"; label += $" (£{multiBuy.PricePerUnit:F2}/each)";
// If has variants, need variant selection first // Use short callback data
var callbackData = hasVariants var callbackData = hasVariants
? $"selectvar:{product.Id}:{multiBuy.Quantity}:{multiBuy.Id}" ? _mapper.BuildCallback("selectvar", product.Id, multiBuy.Quantity, multiBuy.Id)
: $"add:{product.Id}:{multiBuy.Quantity}:{multiBuy.Id}"; : _mapper.BuildCallback("add", product.Id, multiBuy.Quantity, multiBuy.Id);
buttons.Add(new[] buttons.Add(new[]
{ {
@ -123,12 +130,12 @@ namespace TeleBot.UI
// Add regular single item option // Add regular single item option
var singleCallbackData = hasVariants var singleCallbackData = hasVariants
? $"selectvar:{product.Id}:1" ? _mapper.BuildCallback("selectvar", product.Id, 1)
: $"add:{product.Id}:1"; : _mapper.BuildCallback("add", product.Id, 1);
buttons.Add(new[] { buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData( InlineKeyboardButton.WithCallbackData(
$"🛒 Single Item - ${product.Price:F2}", $"🛒 Single Item - £{product.Price:F2}",
singleCallbackData singleCallbackData
) )
}); });
@ -138,21 +145,21 @@ namespace TeleBot.UI
// No multi-buys, show quantity selector // No multi-buys, show quantity selector
var quantityButtons = new List<InlineKeyboardButton>(); var quantityButtons = new List<InlineKeyboardButton>();
if (quantity > 1) if (quantity > 1)
quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", $"qty:{product.Id}:{quantity - 1}")); quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", _mapper.BuildCallback("qty", product.Id, quantity - 1)));
quantityButtons.Add(InlineKeyboardButton.WithCallbackData($"Qty: {quantity}", "noop")); quantityButtons.Add(InlineKeyboardButton.WithCallbackData($"Qty: {quantity}", "noop"));
if (quantity < 10) if (quantity < 10)
quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", $"qty:{product.Id}:{quantity + 1}")); quantityButtons.Add(InlineKeyboardButton.WithCallbackData("", _mapper.BuildCallback("qty", product.Id, quantity + 1)));
buttons.Add(quantityButtons.ToArray()); buttons.Add(quantityButtons.ToArray());
// Add to cart button // Add to cart button
var addCallbackData = hasVariants var addCallbackData = hasVariants
? $"selectvar:{product.Id}:{quantity}" ? _mapper.BuildCallback("selectvar", product.Id, quantity)
: $"add:{product.Id}:{quantity}"; : _mapper.BuildCallback("add", product.Id, quantity);
buttons.Add(new[] { buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData( InlineKeyboardButton.WithCallbackData(
$"🛒 Add to Cart - ${product.Price * quantity:F2}", $"🛒 Add to Cart - £{product.Price * quantity:F2}",
addCallbackData addCallbackData
) )
}); });
@ -184,7 +191,7 @@ namespace TeleBot.UI
return new InlineKeyboardMarkup(buttons); return new InlineKeyboardMarkup(buttons);
} }
public static InlineKeyboardMarkup CartMenu(ShoppingCart cart) public InlineKeyboardMarkup CartMenu(ShoppingCart cart)
{ {
var buttons = new List<InlineKeyboardButton[]>(); var buttons = new List<InlineKeyboardButton[]>();
@ -196,7 +203,7 @@ namespace TeleBot.UI
buttons.Add(new[] { buttons.Add(new[] {
InlineKeyboardButton.WithCallbackData( InlineKeyboardButton.WithCallbackData(
$"❌ Remove {item.ProductName}", $"❌ Remove {item.ProductName}",
$"remove:{item.ProductId}" _mapper.BuildCallback("remove", item.ProductId)
) )
}); });
} }