Adding Product to Cart Issues (EPiServer 10 Webforms to EPiServer 11 MVC)

Vote:
 

Hello,

Apologies for how long this post is.  We are in the process of converting from EPiServer 10 (CMS 10.10.4 and Commerce 10.8.0) and Webforms over to EPiServer 11 (CMS 11.9.1 and Commerce 11.8.5) and MVC and we are having issues with adding a product to the cart with the way we did in the previous version (we are still using the legacy cart as the change over to the new cart system will be done at a later time).  The code that we used before was in a block, and I've kept it in the block since there is no actual postback being done that would required moving it out into a page.  The below code is what was in the block code behind before and what I have converted to on the Index.cshtml for the block now.

AddToCartBlock Webforms

// Product
string sku = GetQueryStringValue("item");
if (string.IsNullOrEmpty(sku))
{
	PanelAddToCart.Visible = false;
	PanelProductNotFound.Visible = true;
	return;
}
CatalogEntry catEntry = CatalogEntry.Create(sku);
if (catEntry == null)
{
	PanelAddToCart.Visible = false;
	PanelProductNotFound.Visible = true;
	return;
}
// if product disabled, display message and return
if (!catEntry.entry.IsActive || !catEntry.GetDisplayPricing())
{
	PanelAddToCart.Visible = false;
	PanelProductDisabled.Visible = true;
	return;
}

// Quantity
int iQuantity;
string sQuantity = GetQueryStringValue("qty");
if (string.IsNullOrEmpty(sQuantity))
	sQuantity = "1";
Int32.TryParse(sQuantity, out iQuantity);

int maxAllowed = decimal.ToInt32(catEntry.entry.ItemAttributes.MaxQuantity);
if (iQuantity > maxAllowed)
{
	PanelAddToCart.Visible = false;
	PanelProductNotFound.Visible = false;
	PanelProductOverMax.Visible = true;
	maxQty = maxAllowed;
}

// Add to cart
if (!IsPostBack)
{
	extCartHelper.AddToCart(catEntry, iQuantity, false);
	try
	{
		LineItem newLineItem = extCartHelper.GetLineItems().First(l => l.CatalogEntryId == catEntry.entry.ID);
		newLineItem.SetPartNumber(catEntry.GetPartNumber());
		newLineItem.SetShippingNote(catEntry.GetShippingNote());
		extCartHelper.AcceptCartChanges();
		extCartHelper.ValidateCart();
	}
	catch
	{
		PanelAddToCart.Visible = false;
		PanelProductOverMax.Visible = true;
		return;
	}
}

// Get product Line Item
LineItem lineItem = extCartHelper.GetLineItems().Where(l => l.CatalogEntryId == sku).FirstOrDefault();
if (lineItem == null)
{
	PanelAddToCart.Visible = false;
	PanelProductNotFound.Visible = true;
	return;
}
// Set units per package on lineitem
lineItem.SetUnitsPerPackage(catEntry.GetUnitsPerPackage());
lineItem.AcceptChanges();
// set shipping address at lineitem if one exists
if (string.IsNullOrEmpty(lineItem.ShippingAddressId))
{
	OrderAddress shippingAddress = extCartHelper.GetOrderShippingAddress();
	if (shippingAddress != null)
	{
		lineItem.ShippingAddressId = shippingAddress.Name;
		lineItem.AcceptChanges();
	}
}

// Display info
ProductLineItem = new CartLineItem()
{
	DisplayName = catEntry.GetDisplayName(),
	PartNumber = catEntry.GetPartNumber(),
	Quantity = lineItem.Quantity.ToString("#"),
	Price = lineItem.PlacedPrice.ToString("C"),
	TotalPrice = lineItem.ExtendedPrice.ToString("C"),
	ImageUrl = catEntry.GetAssetImageLink(CurrentBlock.ImageGroup, CurrentBlock.DefaultImage != null ? CurrentBlock.DefaultImage.ToString() : ""),
	Savings = ((lineItem.PlacedPrice * lineItem.Quantity) - lineItem.ExtendedPrice).ToString("C")
};

List discounts = new List();

discounts = discounts.Union(lineItem.Discounts.OfType()).ToList();

discounts = discounts.Union(extCartHelper.cartHelper.OrderForm.Discounts.OfType()).ToList();

var promotions = discounts.Select(d => PromotionHelper.GetPromotionDisplayName(d.DiscountId, EPiServer.Globalization.ContentLanguage.PreferredCulture.Name));
if (promotions.Count() == 0)
{
	rptDiscounts.Visible = false;
	pnlSavings.Visible = false;
}
rptDiscounts.DataSource = promotions;
rptDiscounts.DataBind();

AddToCartBlock MVC

// Product
string sku = GetQueryStringValue("item");
CatalogEntry catEntry = CatalogEntry.Create(sku);
// if product disabled, display message and return

// Quantity
int iQuantity;
string sQuantity = GetQueryStringValue("qty");
if (string.IsNullOrEmpty(sQuantity))
{
	sQuantity = "1";
}
Int32.TryParse(sQuantity, out iQuantity);

if (catEntry != null)
{
	int maxAllowed = decimal.ToInt32(catEntry.entry.ItemAttributes.MaxQuantity);
	if (iQuantity > maxAllowed)
	{
		maxQty = maxAllowed;
	}
}

// Add to cart
// This was a !IsPostBack
if (catEntry != null && Request.HttpMethod != "POST")
{
	extCartHelper.AddToCart(catEntry, iQuantity, false);
	failedToAdd = false;
	try
	{
		LineItem newLineItem = extCartHelper.GetLineItems().First(l => l.Code == catEntry.entry.ID);
		newLineItem.SetPartNumber(catEntry.GetPartNumber());
		newLineItem.SetShippingNote(catEntry.GetShippingNote());
		extCartHelper.AcceptCartChanges();
		extCartHelper.ValidateCart();
	}
	catch
	{
		failedToAdd = true;
		return;
	}
}
// End of the !IsPostBack

// Get product Line Item
LineItem lineItem = extCartHelper.GetLineItems().Where(l => l.Code == sku).FirstOrDefault();

// Set units per package on lineitem
if (catEntry != null)
{
	lineItem.SetUnitsPerPackage(catEntry.GetUnitsPerPackage());
	lineItem.AcceptChanges();
}

// set shipping address at lineitem if one exists
if (lineItem != null)
{
	if (string.IsNullOrEmpty(lineItem.ShippingAddressId))
	{
		OrderAddress shippingAddress = extCartHelper.GetOrderShippingAddress();
		if (shippingAddress != null)
		{
			lineItem.ShippingAddressId = shippingAddress.Name;
			lineItem.AcceptChanges();
		}
	}
}

// Display info
if (catEntry != null)
{
	ProductLineItem = new CartLineItem()
	{
		DisplayName = catEntry.GetDisplayName(),
		PartNumber = catEntry.GetPartNumber(),
		Quantity = lineItem.Quantity.ToString("#"),
		Price = lineItem.PlacedPrice.ToString("C"),
		TotalPrice = lineItem.ExtendedPrice.ToString("C"),
		ImageUrl = catEntry.GetAssetImageLink(Model.ImageGroup, Model.DefaultImage != null ? Model.DefaultImage.ToString() : ""),
		Savings = ((lineItem.PlacedPrice * lineItem.Quantity) - lineItem.ExtendedPrice).ToString("C")
	};
}

if (lineItem != null)
{
	List discounts = new List();

	discounts = discounts.Union(lineItem.Discounts.OfType()).ToList();

	discounts = discounts.Union(extCartHelper.cartHelper.OrderForm.Discounts.OfType()).ToList();

	promotions = discounts.Select(d => PromotionHelper.GetPromotionDisplayName(d.DiscountId, EPiServer.Globalization.ContentLanguage.PreferredCulture.Name));
}

ExtCartHelper AddToCart method

public void AddToCart(CatalogEntry catalogEntry, int quantity, bool fixedQuantity)
{
	if (catalogEntry == null)
		throw new InvalidDataException("catalogEntry");

	var maxQuantity = CalculateMaximumQuantity(catalogEntry);

	// If not valid, exit
	if (!catalogEntry.entry.IsActive)
		return;
	if (!(catalogEntry.entry.StartDate.CompareTo(DateTime.UtcNow) <= 0 && catalogentry.entry.enddate.compareto(datetime.utcnow)>= 0))
		return;
	if (maxQuantity > 0 && quantity > maxQuantity)
		return;

	// Add to cart
	cartHelper.AddEntry(catalogEntry.entry, quantity, fixedQuantity);
}

This still adds the Line Item in the LineItem table and creates a new shipment/adds the Line Item to the shipment, but there are things that are missing.  However, Catalog and ParentCatalogEntryId are missing, and if more than a quantity of one is added for that product the ExtendedPrice column is not caluclated properly.  In the Shipment table the LineItemIds are set properly, but the SubTotal column is set at 0.00.

I've also converted the cart block that we used to have over to a cart page, and for updating the product quantity, I had to use a combination of our old code with the new cart code (shown below).  I modified this from looking at the code at https://world.episerver.com/documentation/developer-guides/commerce/orders/shopping-carts/

New Cart Update Quantity Code

bool validQuantity = int.TryParse(Request.Form["qty-cart" + lineItemId], out var quantity);

if (!validQuantity)
{
	return RedirectToAction("Index");
}

if (quantity < 1)
{
	DeleteLineItem(lineItemId);
	return RedirectToAction("Index");
}
var cart = _orderRepository.LoadOrCreateCart(UserHelper.GetCurrentContactId(), "Default");
var extCartHelper = new ExtCartHelper();
var lineItemList = cart.GetAllLineItems().ToList();
var lineItemCode = lineItemList[lineItemId].Code;
var lineItem = extCartHelper.GetLineItems().FirstOrDefault(li => li.Code == lineItemCode);
if (lineItem != null && quantity <= lineitem.maxquantity) { var shipment="cart.GetFirstShipment();" cart.updatelineitemquantity(shipment, lineitem, quantity); _orderrepository.save(cart); extcarthelper.validatecart(); } else if (quantity> lineItem.MaxQuantity)
{
	string maxQty = lineItem.MaxQuantity.ToString("N0");
	TempData["maxQty"] = maxQty;
	return RedirectToAction("Index");
}
return RedirectToAction("Index");

When updating a quantity on the page it does update the quantity, but it also fixes the issues with both the LineItem and Shipment tables.  I tried to implement a similar way to how the quantity is updated on the add to cart block, but run into almost an opposite situation where the Catalog and ParentCatalogIntryId rows in the LineItem table are populated, but the PlacedPrice, ListPrice, and ExtendedPrice are all set to 0, the Description row is null, and the MaxQuantity row has the incorrect number set.

I'm at a loss with trying to figure out what to fix.  I'm unsure if this is an issue that is completely because of the conversion over to MVC or if we're also running into an EPiServer 11 upgrade issue at the same time.

Any help would be greatly appreciated.

Thank you!

Kevin Larsen

#197585
Edited, Oct 08, 2018 17:22
Vote:
 

Please see order-processing and order-manipulation.  You need to call cart.UpdatePlacedPrice to get the prices set.   There is no more automatic procoessing.  Everything must be explictly called now. 

#197596
Oct 08, 2018 18:17
Vote:
 

Hello Mark,

Thanks for your response.  I'm trying to work my way through the new order-processing/order-manipulation and have the below working for me for the most part.

// Product
string sku = GetQueryStringValue("item");
CatalogEntry catEntry = CatalogEntry.Create(sku);

// Quantity
int iQuantity;
string sQuantity = GetQueryStringValue("qty");
if (string.IsNullOrEmpty(sQuantity))
{
	sQuantity = "1";
}
Int32.TryParse(sQuantity, out iQuantity);

if (catEntry != null)
{
	int maxAllowed = decimal.ToInt32(catEntry.entry.ItemAttributes.MaxQuantity);
	if (iQuantity > maxAllowed)
	{
		maxQty = maxAllowed;
	}
}

int.TryParse(sQuantity, out iQuantity);
PriceExt priceExt = catEntry.GetPrice(iQuantity, ExtCartHelper.currentMarket);
var orderRepository = ServiceLocator.Current.GetInstance<IOrderRepository>();
var orderGroupFactory = ServiceLocator.Current.GetInstance<IOrderGroupFactory>();
var cart = orderRepository.LoadOrCreateCart<ICart>(UserHelper.GetCurrentContactId(), "Default");
var newLineItem = orderGroupFactory.CreateLineItem(sku, cart);
newLineItem.Quantity = iQuantity;
newLineItem.PlacedPrice = (decimal)priceExt.SalePrice;
newLineItem.DisplayName = catEntry.GetDisplayName();
cart.AddLineItem(newLineItem, orderGroupFactory);
orderRepository.Save(cart);

One thing that I noticed is that the description is still set to null and there doesn't seem to be anything that I can set for it using the newLineItem (ILineItem).  Also, the MaxQuantity field is being set to what looks like a default 100, while the actual MaxQuantity that is set on the product is at 1000.  Is there a separate way of setting those fields/updating those fields?  I looked through both pieces of documentation you sent and didn't see anything about them.

Thank you,

Kevin Larsen

#197599
Oct 08, 2018 23:49
Vote:
 

You can always try the Properties dictionary. There are a couple of fields from the old implementations that aren't present directly on the new interfaces. Probably because they stored redundant data that can be fetched from elsewhere when needed.

But these fields can still be accessed through Properties if you need to have them set for legacy reasons (like in your case).

So something like this:

newLineItem.Properties["MaxQuantity"] = product.MaxQuantity;
newLineItem.Properties["Description"] = product.Description;

"product" being a placeholder for wherever you get that information from.

#197602
Edited, Oct 09, 2018 1:34
Vote:
 

Hi Jafet,

I tried the Properties dictionary but it didn't seem to actually update the line item in the database.  Below is what I added , with the description set to just a string for testing.

int.TryParse(sQuantity, out iQuantity);
PriceExt priceExt = catEntry.GetPrice(iQuantity, ExtCartHelper.currentMarket);
var orderRepository = ServiceLocator.Current.GetInstance<IOrderRepository>();
var orderGroupFactory = ServiceLocator.Current.GetInstance<IOrderGroupFactory>();
var cart = orderRepository.LoadOrCreateCart<ICart>(UserHelper.GetCurrentContactId(), "Default");
var newLineItem = orderGroupFactory.CreateLineItem(sku, cart);
newLineItem.Quantity = iQuantity;
newLineItem.PlacedPrice = (decimal)priceExt.SalePrice;
newLineItem.DisplayName = catEntry.GetDisplayName();
newLineItem.Properties["Description"] = "test";
newLineItem.Properties["ListPrice"] = (decimal)priceExt.SalePrice;
newLineItem.Properties["MaxQuantity"] = catEntry.entry.ItemAttributes.MaxQuantity;
cart.AddLineItem(newLineItem, orderGroupFactory);
orderRepository.Save(cart);

In the database the description is still set to null, the PlacedPrice is still 0, and the MaxQuantity is a different number (seems to always default to 100).  It doesn't seem like I'm doing anything wrong there.  Am I calling it in the wrong order?  Does the line item need to be added to the cart first and then have those properties set?

Thank you,

Kevin Larsen

#197658
Oct 09, 2018 16:16
Vote:
 

Hello,

I think I found a roundabout solution to my problem.  I was also having an issue with the below line coming back as null after adding the new line item.

// Get product Line Item
LineItem lineItem = extCartHelper.GetLineItems().Where(l => l.Code == sku).FirstOrDefault();

I found that the extCartHelper was not "refreshing" to include the new line item that was being added to the cart in the lines above it, so it would never find the new line item.  I created a new cart helper after the new product was add (newExtCartHelper) that then when called in that same way did find the new line.

// Get product Line Item
// Create new ExtCartHelper as it needs to be refreshed after new line item added
ExtCartHelper newExtCartHelper = new ExtCartHelper();
LineItem lineItem = newExtCartHelper.GetLineItems().FirstOrDefault(l => l.Code == sku);

From there I was able to set/modify the additional fields that I needed to do without using the Properties dictionary.

// Set additional fields for lineitem
if (catEntry != null)
{
    lineItem.Description = catEntry.GetDescription();
    lineItem.ListPrice = (decimal)priceExt.SalePrice;
    lineItem.MaxQuantity = catEntry.entry.ItemAttributes.MaxQuantity;
    lineItem.AcceptChanges();
}

I'm guessing there is a better way of doing this, but this seemed to be the only way I was able to get it to work.

Thank you,

Kevin Larsen

#197661
Oct 09, 2018 17:10
Vote:
 

Yes, mixing old api:s and new api:s is generally not a good idea and can result in unexpected bugs that require some juggling back and forth like you do there to fix.

If you're at a stage where you aren't focusing on porting over to the new order apis and still relying on CartHelper, a better approach would probably be to keep using the old API:s as much as possible until you can start porting to the new API:s.

So instead of doing:

var cart = orderRepository.LoadOrCreateCart<ICart>(UserHelper.GetCurrentContactId(), "Default")

You'd do:

cartHelper.Cart

And do your operations with the old API:s from there.

(Documentation here: https://world.episerver.com/documentation/developer-guides/commerce/orders/order-management/

It's of course preferable to port over like you've started doing with the code you pasted above, but I understand that it's not always that simple to just roll with it all the way.

#197665
Oct 09, 2018 18:02
Vote:
 

I would suggest to do the way around - start with QuickSilver CartController/CartService, as can be found here: https://github.com/episerver/Quicksilver/blob/master/Sources/EPiServer.Reference.Commerce.Site/Features/Cart/Services/CartService.cs 

Something starting fresh is the best way 

#197691
Oct 10, 2018 16:30
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.