cart.SaveAsPurchaseOrder throw new InvalidOperationException("The cart is empty")

Vote:
 

Hi,

We are using Episerver Commerce version 10.2.3.

Often we can create our orders without problems, but for some orders we get the exception, InvalidOperationException("The cart is empty"), when calling the SaveAsPurchaseOrder method on the cart in the DIBSPayment.aspx.cs class from the EPiServer.Business.Commerce.Payment.DIBS provider.

We have added some logging of the cart id and the number of line items returned by cart.GetAllLineItems() right before calling the SaveAsPurchaseOrder. The number of line items from the logging is not 0 and also if we look up the cart id in the Commer Manager we can also see that the cart contains line items.

Right now we have no clue why this is happening, so hope someone can help.

What we have seen so far, it is only happening for authenticated users, but not 100% sure this has something to do with it.

----------------------------------------------------------------

UPDATE

An example from the log:

cart.GetAllLineItems().Count() // returns the value of 8

cart.OrderForms.Count // returns the value of 1

cart.OrderForms.Sum(x => x.LineItems.Count) // return the value 0

cart.OrderForms.Any(order => order.LineItems.Any()) // return false

#179172
Edited, Jun 01, 2017 22:12
Vote:
 

This is probably caused as you have your lineitems in your orderform, but you yet to add them to a shipment. LineItems should belong to shipments, otherwise it can cause the problem as you saw. 

#179178
Jun 02, 2017 10:07
Vote:
 

Thank you Quan.

Yes I was also looking at something about the shipment, but strange that we only get the error sometimes and we only have one way of adding products to the cart.

I tried decompiling the GetAllLineItems method, which in our case returned the value of 8 and I can see the method is looking at the shipment.

But if I decompile the SaveAsPurchaseOrder method I can see it's calling a private method called IsEmpty and the implemetation of the method is:

private bool IsEmpty()
    {
      return !this.OrderForms.Any<OrderForm>((Func<OrderForm, bool>) (order => order.LineItems.Any<LineItem>()));
    }

So as I can see it this method is not lokking at the shipments?

And still it's only happening sometimes.

#179180
Edited, Jun 02, 2017 10:17
Vote:
 
<p>They way we add products to the cart&nbsp;like this:</p> <p></p> <pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">cart.AddLineItem(lineItem, _orderGroupFactory);</pre> <p></p> <p>By looking at the decompiled version of AddLineItem it seems like this is adding the line items to the shipment:</p> <pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public static void AddLineItem(this IOrderGroup orderGroup, ILineItem lineItem, IOrderGroupFactory orderGroupFactory) { Validator.ThrowIfNull("lineItem", (object) lineItem); IOrderForm orGetFirstForm = IOrderGroupExtensions.CreateOrGetFirstForm(orderGroup, orderGroupFactory); IOrderGroupExtensions.CreateOrGetFirstShipment(orderGroup, orGetFirstForm, orderGroupFactory).LineItems.Add(lineItem); }</pre> <p></p>
#179184
Jun 02, 2017 11:38
Vote:
 

Hi,

Do you use the new cart system (serializable carts) or still the old one? (If the SaveAsPurchaseOrder method calls the IsEmpty() method then I guess you're using the old cart system).

About cart.GetAllLineItems(), yes it only gets line items in shipments in 10.2.3 version. It was updated in 10.3.0 version, so it gets line items of order forms if there's not any shipment in the cart.

#179261
Jun 07, 2017 6:28
Vote:
 

Hi Bien,

Yes we are still using the old cart system and not the new serializable carts.

In our case the cart.GetAllLineItems() returns the correct number of items and we can also see from our log that the cart has a shipment with line items.

The IsEmpty() method on the other hand is returning true and from our log we can see that the cart has one order form but that order form does not hold any line items.

By looking at the decompiled IsEmpty() method from my post above I can see that this method is not looking for line items on the shipment but only on the order forms?

#179265
Jun 07, 2017 8:25
Vote:
 

That's true, IsEmpty() is looking for line items on order forms only. In the old cart system, the cart.AddLineItem() method adds the line item to the first shipment and then it syncs back to the line item collection of the order form containing the shipment. So normally OrderForm.LineItems should contain the newly added line item as well. But it seems that does not happen in your case. Could you check the the parent of the shipment to see if it's null? Is there any other operation on the cart before adding the line item?

#179268
Jun 07, 2017 9:24
Vote:
 

We got the support case reported by you - we will work on it as soon as we have someone available.

#179295
Jun 07, 2017 15:31
Vote:
 

Hi,

I was able to spend some time to look into this case, and this is my initial finding. Without any ability to reproduce/debug this issue, this is largely my suspection, so you'll probably need to confirm yourself.

To explain things, we need to know that there are two APIs for the catalog system: the concrete classes (CartHelper, Cart, OrderGroup,...), and the (new) abstraction APIs (IOrderForm, IShipment, IOrderGroupFactory, ...). We - as Episerver - worked hard to ensure that these two systems are in sync, i.e., if you create an order using the concrete classes, you should be able to load it again using the abstraction APIs. However, it's not recommended to mix both of them. In your case, it seems that you are using CartHelper to create a cart, but then use the abstraction APIs to add new items to them. This can be problematic.

Why?

In the concrete classes the LineItems belong to the OrderForm, and then distributed to the Shipments. This has been problematic, so in the new abstraction APIs, the LineItems belong to Shipment, which allows much easier and robust approach to the LineItem-Shipment relationship. However, because the underlying/default implementation, we need to do some "magic" to sync between the abstraction, and the implementation. It works just fine if it's new abstraction APIs all the way (since you load the cart, add items to it, and save it back). Or concrete classes all the way. Mixing them might break the synchronization (or "magic") between these two APIs. If your cart was properly "initialized" - aka it already contains an OrderForm and a Shipment, then things might work for you. However if it's empty, then AddLineItem will add "in-memory" instances of those, and that can be out-of-sync with underlying data, causing the problem.

So what is the fix? The best way is that choose one way and stick with it. You might use the CartHelper.AddEntry method to add the entry to cart. Or you move entirely to the new abstraction APIs (IOrderRepository all the way) - which is the preferred way. 

A quick fix should be this (again, untested, as I was unable to reproduce the problem)

if (cartHelper.Cart.OrderForms.Count == 0) 

// create a new one

{     

orderForm = new OrderForm();     

orderForm.Name = this.Cart.Name;     

cartHelper.Cart.OrderForms.Add(orderForm);

}

if (cartHelper.Cart.OrderForms[0].Shipments.Count == 0)

{     

var shipment = new Shipment();     

cartHelper.Cart.OrderForms[0].Shipments.Add(shipment);

}

Before calling the AddLineItem. However this is just a workaround - for long term solution I would suggest to move to abstraction APIs entirely.

#179300
Edited, Jun 07, 2017 17:50
Vote:
 
<p>Okay. We have found one problem somehow relating to basket merge at login, but it might just be that we tried to merge with a null basket. That fixe a problem with shipment method id not being set, and that causing the missing lineitems after running the&nbsp;<span>CartCheckOutWorkflow.,</span><br /><br />But now we have seen some problems with the same result, that&nbsp;<br />OrderGroupWorkflowManager.RunWorkflow(cart, OrderGroupWorkflowManager.CartCheckOutWorkflowName, true, isIgnoreProcessPayment);<br />causes lineitems to disapear. It is properly relatede to some wrong data in the basket before that call, like the previous invalid shipment method id (even though that did not cause an exception in the flow!)<br />Now we see that CustomerName is empty, but we don't know what could cause that, because the basket was loaded correctly with the userid. Does this tell you anything?</p> <p>We are using the abstract api to all basket modifications, and we are only using the code you provided in&nbsp;DIBSPayment with CartHelper - isn't that what you recommend?</p>
#179314
Jun 08, 2017 8:15
Vote:
 

If you are using abstraction APIs everywhere - then it's good (you probably want to upgrade and use the version of DIBS which uses abstraction APIs as well). I asked our developer support engineer to ask for your code and database to investigate further. 

#179320
Edited, Jun 08, 2017 10:58
Vote:
 
<p>Where do I get a version of the DIBS with the abstraction api?</p>
#179342
Jun 08, 2017 15:00
Vote:
 

http://world.episerver.com/download2/

You must be using 10.5.0 or later. 

#179343
Jun 08, 2017 15:05
Vote:
 

Is there any way to disable auto basket merge after login?
We have so many problems that seems to be somehow related to that.

I have this thread but how do I add the event?
http://world.episerver.com/Forum/Developer-forum/EPiServer-Commerce/Thread-Container/2014/3/Profile_MigrateAnonymous/
"Given you already register the event in Global.asax, you need to add a method like this:" ???

#179425
Jun 12, 2017 13:48
Vote:
 

Here's something you can try: http://vimvq1987.com/2017/06/merging-carts-customer-logs/ 

#179437
Jun 12, 2017 15:11
* 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.