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);
|
await HandleCheckout(bot, callbackQuery.Message, session);
|
||||||
break;
|
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":
|
case "confirm_order":
|
||||||
await HandleConfirmOrder(bot, callbackQuery.Message, session, callbackQuery.From);
|
await HandleConfirmOrder(bot, callbackQuery.Message, session, callbackQuery.From);
|
||||||
break;
|
break;
|
||||||
@ -746,9 +762,81 @@ namespace TeleBot.Handlers
|
|||||||
|
|
||||||
session.State = SessionState.CheckoutFlow;
|
session.State = SessionState.CheckoutFlow;
|
||||||
|
|
||||||
// Send new message for checkout - collect all details at once
|
// Check if user has saved address
|
||||||
await bot.SendTextMessageAsync(
|
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,
|
||||||
|
"📦 *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 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.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" +
|
"📦 *Checkout - Delivery Details*\n\n" +
|
||||||
"Please provide all delivery details in one message:\n\n" +
|
"Please provide all delivery details in one message:\n\n" +
|
||||||
"• Full Name\n" +
|
"• Full Name\n" +
|
||||||
@ -766,6 +854,41 @@ namespace TeleBot.Handlers
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private async Task HandleConfirmOrder(ITelegramBotClient bot, Message message, UserSession session, User telegramUser)
|
||||||
{
|
{
|
||||||
if (session.OrderFlow == null || session.Cart.IsEmpty())
|
if (session.OrderFlow == null || session.Cart.IsEmpty())
|
||||||
|
|||||||
@ -132,18 +132,48 @@ namespace TeleBot.Handlers
|
|||||||
? lines[4]
|
? lines[4]
|
||||||
: "United Kingdom";
|
: "United Kingdom";
|
||||||
|
|
||||||
// Skip to review step
|
// Check if we need to ask about saving address
|
||||||
session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder;
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Show order summary
|
// Skip to review step
|
||||||
var summary = MessageFormatter.FormatOrderSummary(session.OrderFlow, session.Cart);
|
session.OrderFlow.CurrentStep = OrderFlowStep.ReviewingOrder;
|
||||||
|
|
||||||
await bot.SendTextMessageAsync(
|
// Show order summary
|
||||||
message.Chat.Id,
|
var summary = MessageFormatter.FormatOrderSummary(session.OrderFlow, session.Cart);
|
||||||
summary,
|
|
||||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
await bot.SendTextMessageAsync(
|
||||||
replyMarkup: MenuBuilder.CheckoutConfirmMenu()
|
message.Chat.Id,
|
||||||
);
|
summary,
|
||||||
|
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||||
|
replyMarkup: MenuBuilder.CheckoutConfirmMenu()
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Legacy steps - redirect to new single-message collection
|
// Legacy steps - redirect to new single-message collection
|
||||||
|
|||||||
@ -25,6 +25,9 @@ namespace TeleBot.Models
|
|||||||
public bool IsEphemeral { get; set; } = true;
|
public bool IsEphemeral { get; set; } = true;
|
||||||
public PrivacySettings Privacy { get; set; } = new();
|
public PrivacySettings Privacy { get; set; } = new();
|
||||||
|
|
||||||
|
// Saved shipping address (optional, based on user preference)
|
||||||
|
public SavedShippingAddress? SavedAddress { get; set; }
|
||||||
|
|
||||||
// Order flow data (temporary)
|
// Order flow data (temporary)
|
||||||
public OrderFlowData? OrderFlow { get; set; }
|
public OrderFlowData? OrderFlow { get; set; }
|
||||||
|
|
||||||
@ -62,6 +65,19 @@ namespace TeleBot.Models
|
|||||||
|
|
||||||
public bool EnableDisappearingMessages { get; set; } = true;
|
public bool EnableDisappearingMessages { get; set; } = true;
|
||||||
public int DisappearingMessageTTL { get; set; } = 30; // seconds
|
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
|
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)
|
public static InlineKeyboardMarkup PaymentMethodMenu(List<string> currencies)
|
||||||
{
|
{
|
||||||
var buttons = new List<InlineKeyboardButton[]>();
|
var buttons = new List<InlineKeyboardButton[]>();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user