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.
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
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.
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.
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.
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?
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?
We got the support case reported by you - we will work on it as soon as we have someone available.
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.
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;
if (cartHelper.Cart.OrderForms.Shipments.Count == 0)
var shipment = new Shipment();
Before calling the AddLineItem. However this is just a workaround - for long term solution I would suggest to move to abstraction APIs entirely.
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.
You must be using 10.5.0 or later.
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:" ???
Here's something you can try: http://vimvq1987.com/2017/06/merging-carts-customer-logs/