Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Claudiu Bria 34 posts 146 karma points
    Mar 09, 2017 @ 09:22
    Claudiu Bria
    0

    The order of Invoice items seems to be unstable

    Hi Rusty,

    I just noticed the following behavior at runtime:

    Line 94 in Merchello.Core/Gateways/Notification/Triggering/ShipmentResultNotifyModelFactory.cs

    var currencyCode = invoice.Items.First()

    will return sometimes the Product Line, other times the Shipping Line.

    The entire local code there:

                            var currencyCode =
                                invoice.Items.First()
                                    .ExtendedData.GetValue(Core.Constants.ExtendedDataKeys.CurrencyCode);
                            var currency = _storeSettingService.GetCurrencyByCode(currencyCode);
                            notifyModel.CurrencySymbol = currency.Symbol;
    

    returns a currency object based on the currencyCode detected in the First() item of the invoice. If the first item of the invoice does not contain the currencyCode, then the currency object becomes null, so we have a nice breaking exception on the last line up here when trying to fetch the Symbol out of a null object.

    The interesting part for me is that this happens not regularly but somewhat arbitrarily on subsequentially runs. Although the code below creates the invoice items always in the same order, the invoice.Items.First() line in ShipmentResultNotifyModelFactory.cs will find sometimes the Product Line and run without errors, and sometimes the Shipping Line and break on exception, since the Shipping Line does not contain specifically the currencyCode.

        [HttpGet]
        public HttpResponseMessage Buy(Guid productKey, Guid customerKey, string currencyCode = "NOK", string ajourLicenceKey = "")
        {
            var logData = new ExtendedLoggerData();
            logData.AddCategory("Merchello");
            logData.AddCategory("AjourCMSApi");
    
            var buyResponse = new BuyResponse();
    
            Mandate.ParameterNotNullOrEmpty(productKey.ToString(), "productKey");
            Mandate.ParameterNotNullOrEmpty(customerKey.ToString(), "customerKey");
    
            var umbracoServices = ApplicationContext.Current.Services;
            var memberService = umbracoServices.MemberService;
    
            var merchelloServices = MerchelloContext.Current.Services;
    
            var customerResponse = GetCustomer(customerKey, logData);
            buyResponse.CustomerResponse = customerResponse;
            var customerService = merchelloServices.CustomerService;
            var icustomer = customerResponse.Customer;
    
            if (icustomer.Addresses.All(x => x.AddressType != AddressType.Billing))
            {
                var invalidOp = new InvalidOperationException("Billing address not found");
                MultiLogHelper.Error<BuyResponse>("Could not find billing address", invalidOp, logData);
                throw invalidOp;
            }
    
            var productService = merchelloServices.ProductService;
            var product = productService.GetByKey(productKey);
            if (product == null)
            {
                throw new Exception("Product does not exists", null);
            }
    
            buyResponse.Product = product;
            buyResponse.ProductFound = true;
    
            var defaultCatalogKey = MerchelloConstants.Warehouse.DefaultWarehouseCatalogKey;
    
            var extendedData = new ExtendedDataCollection();
            // this is used to determine where a shipment originates
            extendedData.SetValue(MerchelloConstants.ExtendedDataKeys.WarehouseCatalogKey, defaultCatalogKey.ToString());
            // items set to shippable
            extendedData.SetValue(MerchelloConstants.ExtendedDataKeys.TrackInventory, "false");
            extendedData.SetValue(MerchelloConstants.ExtendedDataKeys.Shippable, "true");
            extendedData.SetValue(MerchelloConstants.ExtendedDataKeys.CurrencyCode, currencyCode);
            extendedData.SetValue(AjourConstants.ExtendedDataKeys.LicenceKey, ajourLicenceKey);
    
    
            var customerBasket = icustomer.Basket();
            customerBasket.Empty();
            customerBasket.AddItem(product, product.Name, 1, extendedData);
            customerBasket.Save();
    
            buyResponse.Basket = customerBasket;
            buyResponse.BasketCreated = true;
    
            var checkoutMgr = customerBasket.GetCheckoutManager();
            var checkoutCustomer = (ICustomer)checkoutMgr.Customer.Context.Customer;
    
            var customerExtendedDataRefreshed = RefreshCustomerExtendedData(ref checkoutCustomer);
    
            buyResponse.CheckoutCustomer = checkoutCustomer;
            buyResponse.CheckoutCustomerFound = true;
    
            var checkoutCustomerExtendedDataShippingAddress = checkoutCustomer.ExtendedData.GetAddress(AddressType.Shipping);
            var customerCountryCode = checkoutCustomerExtendedDataShippingAddress.CountryCode;
    
            if (!checkoutMgr.Payment.IsReadyToInvoice())
            {
                var invalidOp = new InvalidOperationException("CheckoutManager is not ready to invoice");
                MultiLogHelper.Error<BuyResponse>("Not ready to invoice", invalidOp, logData);
                throw invalidOp;
            }
    
            var paymentGatewayMethod = checkoutMgr.Payment.GetPaymentGatewayMethods().FirstOrDefault();
    
            if (paymentGatewayMethod == null)
            {
                var invalidOp = new InvalidOperationException("Payment Gateway method not found");
                MultiLogHelper.Error<BuyResponse>("Could not find payment gateway method", invalidOp, logData);
                throw invalidOp;
            }
    
            if (paymentGatewayMethod.PaymentMethod == null)
            {
                var invalidOp = new InvalidOperationException("Payment method not found");
                MultiLogHelper.Error<BuyResponse>("Could not find payment method", invalidOp, logData);
                throw invalidOp;
            }
    
            buyResponse.PaymentMethod = paymentGatewayMethod.PaymentMethod;
            buyResponse.PaymentMethodSet = true;
    
            checkoutMgr.Payment.SavePaymentMethod(paymentGatewayMethod.PaymentMethod);
    
            var authorizePaymentResult = checkoutMgr.Payment.AuthorizePayment(paymentGatewayMethod.PaymentMethod.Key);
    
            var invoice = authorizePaymentResult.Invoice;
    
            var invoiceService = merchelloServices.InvoiceService;
    
            ((Invoice)invoice).CustomerKey = icustomer.Key;
    
            invoice.VersionKey = Guid.NewGuid();
    
            //Preparing shipment
            var gatewayproviderService = merchelloServices.GatewayProviderService;
            var shipmentService = merchelloServices.ShipmentService;
            var catalog = merchelloServices.WarehouseService.GetAllWarehouseCatalogs().FirstOrDefault();
            var shipCountry = gatewayproviderService.GetShipCountry(catalog.Key, customerCountryCode);
            var warehouse = merchelloServices.WarehouseService.GetDefaultWarehouse();
            var shipmentStatus = merchelloServices.ShipmentService.GetShipmentStatusByKey(MerchelloConstants.ShipmentStatus.Quoted);
            var shipment = merchelloServices.ShipmentService.CreateShipment(shipmentStatus, warehouse.AsAddress(), checkoutCustomerExtendedDataShippingAddress, invoice.Items);
    
            //Get rates and quotes
            var key = MerchelloConstants.ProviderKeys.Shipping.FixedRateShippingProviderKey;
    
            var rateTableProvider = (FixedRateShippingGatewayProvider)MerchelloContext.Current.Gateways.Shipping.GetProviderByKey(key);
            var shipMethods = rateTableProvider.ShipMethods.ToArray();
            var shipMethod = rateTableProvider.ShipMethods.FirstOrDefault(x => x.ShipCountryKey == shipCountry.Key);
            shipment.ShipMethodKey = shipMethod.Key;
    
            buyResponse.Shipment = shipment;
            var quote = rateTableProvider.QuoteShipMethodForShipment(shipment, rateTableProvider.GetShippingGatewayMethod(shipMethod.Key, shipCountry.Key));
    
            //Add shipping quotes to invoice
            var quoteItem = quote.AsLineItemOf<InvoiceLineItem>();
            quoteItem.ExtendedData.AddShipment(shipment);
            invoice.Items.Add(quoteItem);
            invoiceService.Save(invoice);
    
            buyResponse.Invoice = invoice;
            buyResponse.InvoiceCreated = true;
    
    
            var payment = gatewayproviderService.CreatePayment(PaymentMethodType.Cash, invoice.Total, paymentGatewayMethod.PaymentMethod.Key);
            buyResponse.Payment = payment;
            var capturePaymentResult = ((Invoice) invoice).CapturePayment(payment, paymentGatewayMethod, payment.Amount);
            buyResponse.PaymentResult = capturePaymentResult;
    
            if (capturePaymentResult.Payment.Success)
            {
                var approved = capturePaymentResult.ApproveOrderCreation;
                if (approved)
                {
    
                    var order = ((Invoice) invoice).PrepareOrder();
                    order.VersionKey = Guid.NewGuid();
                    var orderService = merchelloServices.OrderService;
                    orderService.Save(order);
                    buyResponse.Order = order;
                    buyResponse.OrderCreated = true;
    
                    foreach (var shipmentItem in shipment.Items)
                    {
                        shipmentItem.ContainerKey = order.Key;
                    }
                    shipment.VersionKey = Guid.NewGuid();
                    shipmentService.Save(shipment);
                    shipment.AuditCreated();
    
                    foreach (var orderShippingItem in order.Items)
                    {
                        ((OrderLineItem)orderShippingItem).ShipmentKey = shipment.Key;
                    }
                    order.VersionKey = Guid.NewGuid();
                    orderService.Save(order);
                    order.AuditCreated();
    
                    var shipmentStatusFinal = merchelloServices.ShipmentService.GetShipmentStatusByKey(MerchelloConstants.ShipmentStatus.Delivered);
                    shipment.ShipmentStatus = shipmentStatusFinal;
                    shipment.VersionKey = Guid.NewGuid();
                    shipmentService.Save(shipment);
                    shipment.AuditStatusChanged();
                    buyResponse.ShipmentDelivered = true;
    
                    var orderStatusKey = MerchelloConstants.OrderStatus.Fulfilled;
                    var orderStatus = orderService.GetOrderStatusByKey(orderStatusKey);
                    order.OrderStatus = orderStatus;
                    order.VersionKey = Guid.NewGuid();
                    orderService.Save(order);
                    buyResponse.OrderFulfilled = true;
    
                }
            }
    
            var response = new HttpResponseMessage
            {
                Content = new ObjectContent<BuyResponse>(buyResponse, Configuration.Formatters.JsonFormatter),
                StatusCode = HttpStatusCode.OK
            };
            return response;
        }
    

    The error, when triggerred, is generated by line

    shipmentService.Save(shipment);

    in the lower part of the code:

                    var shipmentStatusFinal = merchelloServices.ShipmentService.GetShipmentStatusByKey(MerchelloConstants.ShipmentStatus.Delivered);
                    shipment.ShipmentStatus = shipmentStatusFinal;
                    shipment.VersionKey = Guid.NewGuid();
                    shipmentService.Save(shipment);
                    shipment.AuditStatusChanged();
                    buyResponse.ShipmentDelivered = true;
    

    where i have Notification set up for OrderShipped status change.

    So, to recap, I add currencyCode to the product extended data:

            extendedData.SetValue(AjourConstants.ExtendedDataKeys.LicenceKey, ajourLicenceKey);
    

    then I add the product to the basket, then I create the payment from the basket using the CheckoutManager, then I obtain the invoice from the authorized payment:

            var invoice = authorizePaymentResult.Invoice;
    

    then I create the shipment from the invoice:

            var shipment = merchelloServices.ShipmentService.CreateShipment(shipmentStatus, warehouse.AsAddress(), checkoutCustomerExtendedDataShippingAddress, invoice.Items);
    

    then the quote from the shipment, shipmentMethod and shipmentCountry:

            var quote = rateTableProvider.QuoteShipMethodForShipment(shipment, rateTableProvider.GetShippingGatewayMethod(shipMethod.Key, shipCountry.Key));
    

    then I add the quote to the invoice and save the invoice:

            //Add shipping quotes to invoice
            var quoteItem = quote.AsLineItemOf<InvoiceLineItem>();
            quoteItem.ExtendedData.AddShipment(shipment);
            invoice.Items.Add(quoteItem);
            invoiceService.Save(invoice);
    

    At this moment the invoice items should have the same order everytime, right ?.... Looks like... wrong. :)

    The invoice.Items.First() sometimes is the Product Line which is fine because it contains the curencyCode, but sometimes is the Shipping Line, which does not contain the currencyCode and breaks the execution. I noticed for instance that mostly every first time after I logged in on the web - so the Application re-starts anew - the invoice.Items.First() is the Product Line, and mostly every second time right after the first time, the invoice.Items.First() is the Shipping Line.

    I found a quick fix for my code:

            var invoiceShippingLineItems = ((Invoice)invoice).ShippingLineItems();
            if (invoiceShippingLineItems.Any())
            {
                foreach (var invoiceShippingLineItem in invoiceShippingLineItems)
                {
                    if (!invoiceShippingLineItem.ExtendedData.ContainsKey(MerchelloConstants.ExtendedDataKeys.CurrencyCode))
                    {
                        invoiceShippingLineItem.ExtendedData.SetValue(MerchelloConstants.ExtendedDataKeys.CurrencyCode, currencyCode);
                    }
                }
            }
    

    which I put right before I save the invoice:

            //Add shipping quotes to invoice
            var quoteItem = quote.AsLineItemOf<InvoiceLineItem>();
            quoteItem.ExtendedData.AddShipment(shipment);
            invoice.Items.Add(quoteItem);
            var invoiceShippingLineItems = ((Invoice)invoice).ShippingLineItems();
            if (invoiceShippingLineItems.Any())
            {
                foreach (var invoiceShippingLineItem in invoiceShippingLineItems)
                {
                    if (!invoiceShippingLineItem.ExtendedData.ContainsKey(MerchelloConstants.ExtendedDataKeys.CurrencyCode))
                    {
                        invoiceShippingLineItem.ExtendedData.SetValue(MerchelloConstants.ExtendedDataKeys.CurrencyCode, currencyCode);
                    }
                }
            }
    
            invoiceService.Save(invoice);
    

    but I will need your feedback on this @Rusty.

    Is the invoice.Items.First() the culprit here ? Is it the .net collection order preservation the culprit ? Or am I doing something wrong in my code ?

    Thanks.

Please Sign in or register to post replies

Write your reply to:

Draft