diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs
index 47d4e6349..ba0a992d3 100644
--- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs
+++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs
@@ -392,7 +392,9 @@ public virtual async Task DeleteShoppingCartItem(Customer customer, ShoppingCart
///
/// From customer
/// To customer
- /// Store whose selected checkout attributes move along
+ ///
+ /// Store whose selected checkout attributes move along, when the source customer has selected any there
+ ///
///
/// A value indicating whether to coupon codes (discount and gift voucher) should be also
/// re-applied
@@ -440,10 +442,13 @@ await AddToCart(toCustomer, sci.ProductId, sci.ShoppingCartTypeId, sci.StoreId,
await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.GiftVoucherCoupons, resultGift);
}
- //move selected checkout attributes
+ //move selected checkout attributes; the source customer's selection is the most recent one,
+ //but when it has none, the target keeps its own, so logging in from a fresh browser
+ //does not clear a choice saved on the account
var checkoutAttributes = fromCustomer.GetUserFieldFromEntity>(SystemCustomerFieldNames.CheckoutAttributes, storeId);
- await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, checkoutAttributes,
- storeId);
+ if (checkoutAttributes is { Count: > 0 })
+ await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, checkoutAttributes,
+ storeId);
}
#endregion
diff --git a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs
index 9c883d8dc..7dabe6e62 100644
--- a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs
+++ b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs
@@ -11,9 +11,11 @@
using Grand.Domain.Stores;
using Grand.Domain.Tax;
using Grand.Infrastructure;
+using Grand.Infrastructure.TypeConverters;
using Grand.Mediator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
+using System.ComponentModel;
namespace Grand.Business.Checkout.Tests.Services.Orders;
@@ -27,6 +29,13 @@ public class ShoppingCartServiceTests
private ShoppingCartSettings _shoppingCartSettings;
private Mock _shoppingCartValidatorMock;
+ [ClassInitialize]
+ public static void InitClass(TestContext context)
+ {
+ //user fields are stored as strings; the converters read them back as typed values
+ new WebTypeConverter().Register();
+ }
+
[TestInitialize]
public void Init()
{
@@ -283,6 +292,7 @@ public async Task MigrateShoppingCart_MovesCheckoutAttributesOfTheGivenStore()
{
//Arrange
var fromCustomer = new Customer();
+ AddCheckoutAttributes(fromCustomer, "store-b", "gift-wrapping", "yes");
var toCustomer = new Customer();
//Act
@@ -290,6 +300,68 @@ public async Task MigrateShoppingCart_MovesCheckoutAttributesOfTheGivenStore()
//Assert
_customerServiceMock.Verify(x => x.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes,
- It.IsAny>(), "store-b"), Times.Once);
+ It.Is>(a => a.Count == 1 && a[0].Key == "gift-wrapping" && a[0].Value == "yes"),
+ "store-b"), Times.Once);
+ }
+
+ [TestMethod]
+ public async Task MigrateShoppingCart_GuestWithoutCheckoutAttributes_KeepsTheAccountAttributes()
+ {
+ //Arrange
+ var guest = new Customer();
+ var customer = new Customer();
+ AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes");
+
+ //Act
+ await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false);
+
+ //Assert
+ _customerServiceMock.Verify(x => x.UpdateUserField(It.IsAny(),
+ SystemCustomerFieldNames.CheckoutAttributes, It.IsAny>(), It.IsAny()),
+ Times.Never);
+ }
+
+ [TestMethod]
+ public async Task MigrateShoppingCart_GuestWithCheckoutAttributes_ReplacesTheAccountAttributes()
+ {
+ //Arrange
+ var guest = new Customer();
+ AddCheckoutAttributes(guest, "store", "gift-wrapping", "no");
+ var customer = new Customer();
+ AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes");
+
+ //Act
+ await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false);
+
+ //Assert
+ _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes,
+ It.Is>(a => a.Count == 1 && a[0].Value == "no"), "store"), Times.Once);
+ }
+
+ [TestMethod]
+ public async Task MigrateShoppingCart_AccountWithAttributesInAnotherStore_TakesTheGuestAttributes()
+ {
+ //Arrange
+ var guest = new Customer();
+ AddCheckoutAttributes(guest, "store-b", "gift-wrapping", "yes");
+ var customer = new Customer();
+ AddCheckoutAttributes(customer, "store-a", "gift-wrapping", "no");
+
+ //Act
+ await _shoppingCartService.MigrateShoppingCart(guest, customer, "store-b", false);
+
+ //Assert
+ _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes,
+ It.Is>(a => a.Count == 1 && a[0].Value == "yes"), "store-b"), Times.Once);
+ }
+
+ private static void AddCheckoutAttributes(Customer customer, string storeId, string key, string value)
+ {
+ customer.UserFields.Add(new UserField {
+ Key = SystemCustomerFieldNames.CheckoutAttributes,
+ Value = TypeDescriptor.GetConverter(typeof(List))
+ .ConvertToInvariantString(new List { new() { Key = key, Value = value } }),
+ StoreId = storeId
+ });
}
}
\ No newline at end of file