Favorites using “Named Carts” in Sitecore Commerce

Introduction:

The wish list isn’t the only type feature you should consider implementing. Favorites are a unique way to go because instead of adding the items to a specified list, users can quickly toggle something as one of their favorite items, immediately putting it onto a separate “Favorites” list. These items would stay on this list even after purchase, making it incredibly useful for purchasing the same item over and over.

Solution:

In Sitecore Commerce, there is no out of the box functionality for having products as favorites list.

We can implement this by creating “Named Cart” and storing the favorite products into it. Later we can read the Named Cart and display it in my favorites section.

If we add any products to cart in Sitecore Commerce by default it will create a “Default” cart and store product information.

In this case, we need to extend “Sitecore.Commerce.XA.Foundation.Connect.Managers.CartManager” and override “GetCurrentCart”, “MergeCarts” methods to allow us to pass “Cart Name”.

Refer the below code to achieve this,

ExtendedCartManager.cs

public class ExtendedCartManager : Sitecore.Commerce.XA.Foundation.Connect.Managers.CartManager, IExtendedCartManager

{

public ExtendedCartManager(IConnectServiceProvider connectServiceProvider,

IStorefrontContext storefrontContext,

ISearchManager searchManager,

IExtendedSearchManager extendedSearchManager

)

: base(connectServiceProvider, storefrontContext, searchManager)

{

this.ExtendedSearchManager = extendedSearchManager;

}

public IExtendedSearchManager ExtendedSearchManager { get; protected set; }

public ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> GetCurrentCart(IVisitorContext visitorContext, IStorefrontContext storefrontContext, string cartName = “Default”, bool recalculateTotals = false, StringPropertyCollection propertyBag = null)

{

Assert.ArgumentNotNull(visitorContext, “visitorContext”);

Assert.ArgumentNotNull(storefrontContext, “storefrontContext”);

CartResult cartResult = LoadCart(visitorContext, storefrontContext.CurrentStorefront.ShopName, cartName, visitorContext.CustomerId, recalculateTotals, propertyBag);

return new ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart>(cartResult, cartResult.Cart);

}

public ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> MergeCarts(CommerceStorefront storefront, IVisitorContext visitorContext, string anonymousVisitorId, Sitecore.Commerce.Entities.Carts.Cart anonymousVisitorCart, string cartName, StringPropertyCollection propertyBag = null)

{

Assert.ArgumentNotNull(storefront, “storefront”);

Assert.ArgumentNotNull(visitorContext, “visitorContext”);

Assert.ArgumentNotNullOrEmpty(anonymousVisitorId, “anonymousVisitorId”);

Assert.ArgumentNotNull(anonymousVisitorCart, “anonymousVisitorCart”);

string userId = visitorContext.UserId;

CartResult cartResult = LoadCart(visitorContext, storefront.ShopName, cartName, userId, recalculateTotals: true, propertyBag);

if (!cartResult.Success || cartResult.Cart == null)

{

string systemMessage = StorefrontContext.GetSystemMessage(“Cart Not Found Error”);

cartResult.SystemMessages.Add(new SystemMessage

{

Message = systemMessage

});

return new ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart>(cartResult, cartResult.Cart);

}

CommerceCart commerceCart = (CommerceCart)cartResult.Cart;

CartResult cartResult2 = new CartResult

{

Cart = commerceCart,

Success = true

};

if (!string.Equals(userId, anonymousVisitorId, StringComparison.Ordinal))

{

bool flag = (anonymousVisitorCart as CommerceCart)?.OrderForms.Any((CommerceOrderForm of) => of.PromoCodes.Any()) ?? false;

if (anonymousVisitorCart != null && (anonymousVisitorCart.Lines.Any() || flag) && (string.Equals(commerceCart.ShopName, anonymousVisitorCart.ShopName, StringComparison.Ordinal) || !string.Equals(commerceCart.ExternalId, anonymousVisitorCart.ExternalId, StringComparison.Ordinal)))

{

MergeCartRequest request = new MergeCartRequest(anonymousVisitorCart, commerceCart);

request.CopyPropertyBag(propertyBag);

cartResult2 = CartServiceProvider.MergeCart(request);

}

}

return new ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart>(cartResult2, cartResult2.Cart);

}

}

IExtendedCartManager.cs

  public interface IExtendedCartManager

{

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> GetCurrentCart(IVisitorContext visitorContext, IStorefrontContext storefrontContext, string cartName = “Default”, bool recalculateTotals = false, StringPropertyCollection propertyBag = null);

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> MergeCarts(CommerceStorefront storefront, IVisitorContext visitorContext, string anonymousVisitorId, Sitecore.Commerce.Entities.Carts.Cart anonymousVisitorCart, string cartName, StringPropertyCollection propertyBag = null);

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> AddLineItemsToCart(CommerceStorefront storefront, IVisitorContext visitorContext, Sitecore.Commerce.Entities.Carts.Cart cart, IEnumerable<CartLineArgument> cartLines, StringPropertyCollection propertyBag = null);

}

Whenever we add products to cart, the cart will be created and prefixed as “Favorite_”.

Also, We need to merge anonymous cart (Named Cart) to once the user authenticated. For this “Sitecore.Commerce.XA.Foundation.Connect.Managers.AccountManager” to be extended and override the “Login” method.

Inside login method, you can find the default cart to be merged to the customer after successful login. In the same we have to merge our “Favorite_” (Named Cart) also.

Refer the below code to achieve this,

AccountManager.cs

public class AccountManager : Sitecore.Commerce.XA.Foundation.Connect.Managers.AccountManager

{

public IExtendedCartManager ExtendedCartManager { get; set; }

public AccountManager(IConnectServiceProvider connectServiceProvider,

Sitecore.Commerce.XA.Foundation.Connect.Managers.ICartManager cartManager,

IStorefrontContext storefrontContext, IModelProvider modelProvider,

IExtendedCartManager extendedCartManager) :

base(connectServiceProvider, cartManager, storefrontContext, modelProvider)

{

this.ExtendedCartManager = extendedCartManager;

}

public override bool Login(IStorefrontContext storefront, IVisitorContext visitorContext, string userName, string password, bool persistent, StringPropertyCollection propertyBag = null)

{

Assert.ArgumentNotNullOrEmpty(userName, “userName”);

Assert.ArgumentNotNullOrEmpty(password, “password”);

Assert.ArgumentNotNull(visitorContext, “visitorContext”);

Assert.ArgumentNotNull(storefront, “storefront”);

string customerId = visitorContext.CustomerId;

Sitecore.Commerce.Entities.Carts.Cart result = CartManager.GetCurrentCart(visitorContext, storefront).Result;

Sitecore.Commerce.Entities.Carts.Cart result2 = ExtendedCartManager.GetCurrentCart(visitorContext, storefront, “Favorite_”).Result;

bool flag = AuthenticationManager.Login(userName, password, persistent);

if (flag)

{

try

{

CommerceTracker.Current.IdentifyAs(“CommerceUser”, userName);

}

catch (Sitecore.Analytics.DataAccess.XdbUnavailableException)

{

Log.Warn(“The Storefront has detected that xDB is no longer available during the login.  This has not stopped the processing and the user was allowed to continue.”, this);

}

visitorContext.UserJustLoggedIn();

CartManager.MergeCarts(storefront.CurrentStorefront, visitorContext, customerId, result, propertyBag);

ExtendedCartManager.MergeCarts(storefront.CurrentStorefront, visitorContext, customerId, result2, “Favorite_”, propertyBag);

}

return flag;

}

}

Finally, we need to register our ExtendedCartManager & AccountManager using patch config

<configuration xmlns:patch=”http://www.sitecore.net/xmlconfig/”>

<sitecore>

<services>

<register serviceType=”Sitecore.Foundation.Cart.Managers.IExtendedCartManager, Sitecore.Foundation.Cart” implementationType=”Sitecore.Foundation.Cart.Managers.ExtendedCartManager, Sitecore.Foundation.Cart” lifetime=”Singleton”/>

<register serviceType=”Sitecore.Commerce.XA.Foundation.Connect.Managers.IAccountManager, Sitecore.Commerce.XA.Foundation.Connect” lifetime=”Singleton”>

<patch:attribute name=”implementationType”>Sitecore.Foundation.Cart.Managers.AccountManager, Sitecore.Foundation.Cart</patch:attribute>

</register>

</services>

</sitecore>

</configuration

Adding products to Named Cart:

Create a AddFavorite Action Method in your FavoritesController class,

It will have parameters (catalog name, productid, variant id, quantity, cart name) for adding products to cart. ( cart name – “Favorite_”).

[AllowAnonymous]

[HttpPost]

[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]

public JsonResult AddFavorite(string addToCart_CatalogName, string addToCart_ProductId, string addToCart_VariantId, decimal quantity, string cartName = “Favorite_”)

{

CartJsonResult cartJsonResult;

try

{

cartJsonResult = ExtendedAddToCartRepository.AddLineItemsToCart(base.StorefrontContext, VisitorContext, addToCart_CatalogName, addToCart_ProductId, addToCart_VariantId, quantity, cartName);

}

catch (Exception exception)

{

cartJsonResult = ModelProvider.GetModel<CartJsonResult>();

cartJsonResult.SetErrors(“AddFavorite”, exception);

}

return Json(cartJsonResult);

}

Create a “ExtendedAddToCartRepository” and add AddLineItemsToCart method to call ExtendedCartManager to add products to Cart.

public CartJsonResult AddLineItemsToCart(IStorefrontContext storefrontContext, IVisitorContext visitorContext, string catalogName, string productId, string variantId, decimal quantity, string cartName = ” Favorite _”)

{

Assert.ArgumentNotNull(storefrontContext, “storefrontContext”);

Assert.ArgumentNotNull(visitorContext, “visitorContext”);

Assert.ArgumentNotNullOrEmpty(catalogName, “catalogName”);

Assert.ArgumentNotNullOrEmpty(productId, “productId”);

Assert.IsTrue(quantity > 0m, “quantity > 0”);

CartJsonResult model = base.ModelProvider.GetModel<CartJsonResult>();

CommerceStorefront currentStorefront = storefrontContext.CurrentStorefront;

CommerceCart cart = (Factory.CreateObject(“entityFactory”, assert: true) as IEntityFactory).Create<CommerceCart>(“Cart”);

cart.BuildMinimalCart(currentStorefront.ShopName, cartName, visitorContext.CustomerId, visitorContext.CustomerId);

List<CartLineArgument> list = new List<CartLineArgument>();

list.Add(new CartLineArgument

{

CatalogName = catalogName,

ProductId = productId,

VariantId = variantId,

Quantity = quantity

});

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> managerResponse = ExtendedCartManager.AddLineItemsToCart(currentStorefront, visitorContext, cart, list);

if (!managerResponse.ServiceProviderResult.Success)

{

model.SetErrors(managerResponse.ServiceProviderResult);

return model;

}

model.Initialize(managerResponse.Result);

model.Success = true;

return model;

}

To Get All Favorites: (to get all favorite items cart we have to pass cart name as “Favorite_”.)

public CartJsonResult GetFavoriteLines(IStorefrontContext storefrontContext, IVisitorContext visitorContext, StringPropertyCollection propertyBag = null, bool recalculate = true, string cartName = “Favorite_”)

{

Condition.Requires(storefrontContext, “storefrontContext”).IsNotNull();

Condition.Requires(visitorContext, “visitorContext”).IsNotNull();

CartJsonResult model = ModelProvider.GetModel<CartJsonResult>();

try

{

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> currentCart = ExtendedCartManager.GetCurrentCart(visitorContext, storefrontContext, cartName, recalculate, propertyBag);

if (!currentCart.ServiceProviderResult.Success || currentCart.Result == null)

{

model.Success = false;

string systemMessage = storefrontContext.GetSystemMessage(“Cart Not Found Error”);

currentCart.ServiceProviderResult.SystemMessages.Add(new SystemMessage

{

Message = systemMessage

});

model.SetErrors(currentCart.ServiceProviderResult);

}

model.Initialize(currentCart.Result);

return model;

}

catch (Exception exception)

{

model.SetErrors(“GetCurrentCart”, exception);

return model;

}

}

To Remove Favorite (remove car lines we can use existing method from CartController by passing line numbers and cart name as ”Favorite_”. It will remove items from named cart.

public CartJsonResult RemoveLineItemsFromCart(IStorefrontContext storefrontContext, IVisitorContext visitorContext, IEnumerable<string> lineNumbers, string cartName = “Favorite_”, StringPropertyCollection propertyBag = null)

{

Condition.Requires(storefrontContext, “storefrontContext”).IsNotNull();

Condition.Requires(visitorContext, “visitorContext”).IsNotNull();

Condition.Requires(lineNumbers, “lineNumbers”).IsNotNull();

CartJsonResult model = ModelProvider.GetModel<CartJsonResult>();

CommerceStorefront currentStorefront = storefrontContext.CurrentStorefront;

IEntityFactory entityFactory = Factory.CreateObject(“entityFactory”, assert: true) as IEntityFactory;

CommerceCart cart = entityFactory.Create<CommerceCart>(“Cart”);

cart.BuildMinimalCart(currentStorefront.ShopName, cartName, visitorContext.CustomerId, visitorContext.CustomerId);

List<CartLine> list = new List<CartLine>();

foreach (string lineNumber in lineNumbers)

{

CommerceCartLine commerceCartLine = entityFactory.Create<CommerceCartLine>(“CartLine”);

commerceCartLine.BuildMinimalCartLine(lineNumber, 0m);

list.Add(commerceCartLine);

}

ManagerResponse<CartResult, Sitecore.Commerce.Entities.Carts.Cart> managerResponse = CartManager.RemoveLineItemsFromCart(currentStorefront, visitorContext, cart, list, propertyBag);

if (!managerResponse.ServiceProviderResult.Success)

{

model.SetErrors(managerResponse.ServiceProviderResult);

return model;

}

model.Initialize(managerResponse.Result);

model.Success = true;

return model;

}

Conclusion & Key Features:

Having the easy option to simply check your favorites list before checkout to ensure nothing is missing can be very useful to buyers. It also helps out the vendors, as it encourages happy customers to become repeat customers.

This is a little bit different than a wish list or another form of list, like a purchasing list. This is actually a generic kind of list, but that does not mean it isn’t beneficial. These lists are incredibly user friendly, as it’s really simple to add items to this list. Typically, there’s a little heart icon that shows in the item’s detail view and this allows the end user to quickly toggle something and add to that list.

The favorites list is users should be able to move items, possibly into another list, like a wish list or purchase list, or into the shopping cart. Ultimately, the idea is to make it really easy for the end user to add items to their favorites.

Another key feature of the favorites list is that users should be able to move items, possibly into another list, like a wish list or purchase list, or into the shopping cart. Ultimately, the idea is to make it really easy for the end user to add items to their favorites.

Again, it is quite possible to send transactional emails regarding items a buyer has favorited. This can be an individual email to an individual user letting them know that something’s on sale or if there’s a special promotion, such as buy one get one. It could also be used to inform them if an item on their list is nearly out of stock, or maybe if something was just restocked. Offering users these capabilities allow them to have an easy way to track the items they care about, providing a beneficial tool when making purchases.


Leave a Reply

Your email address will not be published. Required fields are marked *