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;
}
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 ?
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:
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.
The error, when triggerred, is generated by line
shipmentService.Save(shipment);
in the lower part of the code:
where i have Notification set up for OrderShipped status change.
So, to recap, I add currencyCode to the product extended data:
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:
then I create the shipment from the invoice:
then the quote from the shipment, shipmentMethod and shipmentCountry:
then I add the quote to the invoice and save the 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:
which I put right before I save the 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.
is working on a reply...