Feature: Add postal address memory with user preference
- Added SavedShippingAddress model to store user addresses - Added Privacy.SaveShippingAddress preference (null = not asked, true/false = user choice) - Enhanced checkout flow to offer using saved address if available - Ask once about saving address, respect user's choice going forward - Automatically save/not save based on user preference in future orders - Added menu options: Use Saved Address, Enter New Address, Save Address (Yes/No) - Enhanced CallbackHandler with save address handlers - Updated MessageHandler to prompt for save preference on first use User will only be asked once about saving addresses. Account reset clears preference.
This commit is contained in:
parent
147e96a084
commit
c8f22c783d
@ -140,6 +140,22 @@ namespace TeleBot.Handlers
|
||||
await HandleCheckout(bot, callbackQuery.Message, session);
|
||||
break;
|
||||
|
||||
case "use_saved_address":
|
||||
await HandleUseSavedAddress(bot, callbackQuery.Message, session);
|
||||
break;
|
||||
|
||||
case "enter_new_address":
|
||||
await HandleEnterNewAddress(bot, callbackQuery.Message, session);
|
||||
break;
|
||||
|
||||
case "save_address_yes":
|
||||
await HandleSaveAddressPreference(bot, callbackQuery.Message, session, true);
|
||||
break;
|
||||
|
||||
case "save_address_no":
|
||||
await HandleSaveAddressPreference(bot, callbackQuery.Message, session, false);
|
||||
break;
|
||||
|
||||
case "confirm_order":
|
||||
await HandleConfirmOrder(bot, callbackQuery.Message, session, callbackQuery.From);
|
||||
break;
|
||||
@ -746,6 +762,27 @@ namespace TeleBot.Handlers
|
||||
|
||||
session.State = SessionState.CheckoutFlow;
|
||||
|
||||
// Check if user has saved address
|
||||
if (session.SavedAddress != null && !string.IsNullOrWhiteSpace(session.SavedAddress.Name))
|
||||
{
|
||||
// Offer to use saved address
|
||||
var savedAddressPreview = $"{session.SavedAddress.Name}\n" +
|
||||
$"{session.SavedAddress.Address}\n" +
|
||||
$"{session.SavedAddress.City}\n" +
|
||||
$"{session.SavedAddress.PostCode}\n" +
|
||||
$"{session.SavedAddress.Country}";
|
||||
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
$"📦 *Checkout - Delivery Details*\n\n" +
|
||||
$"Use your saved address?\n\n" +
|
||||
$"```\n{savedAddressPreview}\n```",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: _menuBuilder.UseSavedAddressMenu()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send new message for checkout - collect all details at once
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
@ -765,6 +802,92 @@ namespace TeleBot.Handlers
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleUseSavedAddress(ITelegramBotClient bot, Message message, UserSession session)
|
||||
{
|
||||
if (session.SavedAddress == null || session.OrderFlow == null)
|
||||
{
|
||||
await HandleEnterNewAddress(bot, message, session);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy saved address to order flow
|
||||
session.OrderFlow.ShippingName = session.SavedAddress.Name;
|
||||
session.OrderFlow.ShippingAddress = session.SavedAddress.Address;
|
||||
session.OrderFlow.ShippingCity = session.SavedAddress.City;
|
||||
session.OrderFlow.ShippingPostCode = session.SavedAddress.PostCode;
|
||||
session.OrderFlow.ShippingCountry = session.SavedAddress.Country;
|
||||
session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder;
|
||||
|
||||
// Show order summary
|
||||
var summary = MessageFormatter.FormatOrderSummary(session.OrderFlow, session.Cart);
|
||||
|
||||
await bot.EditMessageTextAsync(
|
||||
message.Chat.Id,
|
||||
message.MessageId,
|
||||
summary,
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.CheckoutConfirmMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleEnterNewAddress(ITelegramBotClient bot, Message message, UserSession session)
|
||||
{
|
||||
await bot.EditMessageTextAsync(
|
||||
message.Chat.Id,
|
||||
message.MessageId,
|
||||
"📦 *Checkout - Delivery Details*\n\n" +
|
||||
"Please provide all delivery details in one message:\n\n" +
|
||||
"• Full Name\n" +
|
||||
"• Street Address\n" +
|
||||
"• City\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
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleSaveAddressPreference(ITelegramBotClient bot, Message message, UserSession session, bool saveAddress)
|
||||
{
|
||||
// Save user preference
|
||||
session.Privacy.SaveShippingAddress = saveAddress;
|
||||
|
||||
if (saveAddress && session.OrderFlow != null)
|
||||
{
|
||||
// Save the address
|
||||
session.SavedAddress = new SavedShippingAddress
|
||||
{
|
||||
Name = session.OrderFlow.ShippingName,
|
||||
Address = session.OrderFlow.ShippingAddress,
|
||||
City = session.OrderFlow.ShippingCity,
|
||||
PostCode = session.OrderFlow.ShippingPostCode,
|
||||
Country = session.OrderFlow.ShippingCountry,
|
||||
SavedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
// Continue to order summary
|
||||
if (session.OrderFlow != null)
|
||||
{
|
||||
session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder;
|
||||
var summary = MessageFormatter.FormatOrderSummary(session.OrderFlow, session.Cart);
|
||||
|
||||
await bot.EditMessageTextAsync(
|
||||
message.Chat.Id,
|
||||
message.MessageId,
|
||||
summary,
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.CheckoutConfirmMenu()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleConfirmOrder(ITelegramBotClient bot, Message message, UserSession session, User telegramUser)
|
||||
{
|
||||
|
||||
@ -132,6 +132,35 @@ namespace TeleBot.Handlers
|
||||
? lines[4]
|
||||
: "United Kingdom";
|
||||
|
||||
// Check if we need to ask about saving address
|
||||
if (session.Privacy.SaveShippingAddress == null)
|
||||
{
|
||||
// Haven't asked yet - offer to save
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"💾 *Save Address*\n\n" +
|
||||
"Would you like to save this address for future orders?\n\n" +
|
||||
"_You will only be asked this once._",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: new MenuBuilder(null!).SaveAddressConfirmMenu()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already has preference - apply it automatically
|
||||
if (session.Privacy.SaveShippingAddress == true)
|
||||
{
|
||||
session.SavedAddress = new SavedShippingAddress
|
||||
{
|
||||
Name = session.OrderFlow.ShippingName,
|
||||
Address = session.OrderFlow.ShippingAddress,
|
||||
City = session.OrderFlow.ShippingCity,
|
||||
PostCode = session.OrderFlow.ShippingPostCode,
|
||||
Country = session.OrderFlow.ShippingCountry,
|
||||
SavedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
// Skip to review step
|
||||
session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder;
|
||||
|
||||
@ -144,6 +173,7 @@ namespace TeleBot.Handlers
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.CheckoutConfirmMenu()
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
// Legacy steps - redirect to new single-message collection
|
||||
|
||||
@ -25,6 +25,9 @@ namespace TeleBot.Models
|
||||
public bool IsEphemeral { get; set; } = true;
|
||||
public PrivacySettings Privacy { get; set; } = new();
|
||||
|
||||
// Saved shipping address (optional, based on user preference)
|
||||
public SavedShippingAddress? SavedAddress { get; set; }
|
||||
|
||||
// Order flow data (temporary)
|
||||
public OrderFlowData? OrderFlow { get; set; }
|
||||
|
||||
@ -62,6 +65,19 @@ namespace TeleBot.Models
|
||||
|
||||
public bool EnableDisappearingMessages { get; set; } = true;
|
||||
public int DisappearingMessageTTL { get; set; } = 30; // seconds
|
||||
|
||||
// Address memory preference
|
||||
public bool? SaveShippingAddress { get; set; } = null; // null = not asked yet, true = save, false = don't save
|
||||
}
|
||||
|
||||
public class SavedShippingAddress
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? PostCode { get; set; }
|
||||
public string? Country { get; set; }
|
||||
public DateTime? SavedAt { get; set; }
|
||||
}
|
||||
|
||||
public class OrderFlowData
|
||||
|
||||
@ -217,6 +217,35 @@ namespace TeleBot.UI
|
||||
});
|
||||
}
|
||||
|
||||
public InlineKeyboardMarkup UseSavedAddressMenu()
|
||||
{
|
||||
return new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] {
|
||||
InlineKeyboardButton.WithCallbackData("✅ Use Saved Address", "use_saved_address")
|
||||
},
|
||||
new[] {
|
||||
InlineKeyboardButton.WithCallbackData("✏️ Enter New Address", "enter_new_address")
|
||||
},
|
||||
new[] {
|
||||
InlineKeyboardButton.WithCallbackData("❌ Cancel", "cart")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public InlineKeyboardMarkup SaveAddressConfirmMenu()
|
||||
{
|
||||
return new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] {
|
||||
InlineKeyboardButton.WithCallbackData("✅ Yes, Save My Address", "save_address_yes")
|
||||
},
|
||||
new[] {
|
||||
InlineKeyboardButton.WithCallbackData("❌ No, Don't Save", "save_address_no")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static InlineKeyboardMarkup PaymentMethodMenu(List<string> currencies)
|
||||
{
|
||||
var buttons = new List<InlineKeyboardButton[]>();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user