Unit Of Work pattern and when changes are really saved
Hi,
I'm trying to do one-step-checkout. Products are added to the cart via VueJS/WebAPI.
Customer data, payment method and shipping method are being set up in a SurfaceController method, which redirects afterwards to the Review page, which uses Html.BeginPaymentForm(Mode.Order) to render Payment form/button.
I'm using a UnitOfWork pattern as described in documentation.
public ActionResult PreprocessOrder()
{
try
{
using (var uow = _uowProvider.Create())
{
var store = CurrentPage.GetStore();
var order = _sessionManager.GetCurrentOrder(store.Id)
.AsWritable(uow);
if (order.PaymentInfo.CountryId == null
|| order.ShippingInfo.CountryId == null)
{
var country = _countryService.GetCountry(store.Id, "DK");
order.SetPaymentCountryRegion(country);
order.SetShippingCountryRegion(country);
}
if (order.CustomerInfo.CustomerReference == null)
{
var currentMember = Members.GetCurrentMember();
if (currentMember is MsParent memberParent)
{
order.SetProperties(new Dictionary<string, string>
{
{ Constants.Properties.Customer.EmailPropertyAlias, Members.GetCurrentMemberProfileModel().Email },
{ Constants.Properties.Customer.FirstNamePropertyAlias, memberParent.FirstName },
{ Constants.Properties.Customer.LastNamePropertyAlias, memberParent.LastName }
});
}
order.AssignToCustomer(currentMember.Key.ToString());
}
if (order.PaymentInfo.PaymentMethodId == null)
{
var paymentAlias = "bambora";
if (Request.Url.AbsoluteUri.Contains("localhost"))
{
paymentAlias = "invoicing";
}
var pm = _paymentMethodService.GetPaymentMethod(store.Id, paymentAlias);
order.SetPaymentMethod(pm);
}
if (order.ShippingInfo.ShippingMethodId == null)
{
var shm = _shippingMethodService.GetShippingMethod(store.Id, "electronic");
order.SetShippingMethod(shm);
}
_orderService.SaveOrder(order);
uow.Complete();
}
}
catch (ValidationException ex)
{
ModelState.AddModelError("productReference", "Failed to preprocess order");
Log.Error(ex, "PreprocessOrder exception");
return CurrentUmbracoPage();
}
var checkOutPage = CurrentPage.GetCheckoutPage();
return RedirectToUmbracoPage(checkOutPage.Id);
}
(The default shipping/payment method on the order would saved all that hassle, but I can't get it now... Frustrating...)
The problem is, that when CheckoutPage gets the current order, both paymentId and shippingIt are still null, so Html.BeginPaymentForm(Model.Order) raises exception.
public partial class CheckoutPage : IOrderReviewPage
{
public OrderReadOnly Order => this.GetCurrentOrder();
}
It turned out, that there is a time gap right after uow.Complete when the saved values are accessible via GetCurrentOrder.
F.x.
uow.Complete();
}
//test check
var testStore = CurrentPage.GetStore();
var testOrder = _sessionManager.GetCurrentOrder(testStore.Id);
testOrder.PaymentInfo.PaymentMethodId is still null (same with shipping), but it was OK, if I waited 15 seconds (but not always).
Do I need to call some method before I call GetCurrentOrder again?
I don't have any event handlers from Demo store implemented in my project.
But I can see, that GetCurrentOrder extension on IPublishedContent is using VendrApi.Instance to get current order:
public static OrderReadOnly GetCurrentOrder(this IPublishedContent content)
{
return VendrApi.Instance.GetCurrentOrder(content.GetStore().Id);
}
(Implemented as in DemoStore)
Surface controller uses DI.
There shouldn't be any timing issue as all changes are committed as soon as the last open UoW has it's Complete() method called and as mentioned in the other issue, I do think the setting of default payment method should actually be working.
When you say it appears after 15 seconds, could this actually be after an app pool recycle? If so, there may be a problem with the caching layer not getting updated, but the order is actually persisting. I'm not sure why this would be the case though.
If you don't have the demo store events setup, then they won't be being used at all so that shouldn't be the problem. Though I'm not currently sure why you are seeing the issues you are.
If you recycle the app pool, is there order as you would expect it to be?
Hmm, that's interesting. I can't see anything in our code that would be resetting that. We don't have any internal event handlers that are firing when either of those change that could be setting it back so not entirely sure why it's failing. It really shouldn't have a problem setting them in the same session. π€
Glad you've got it working, but would love to figure out exactly what's going on as like I say, that really shouldn't be hapenning.
Yes, it's very strange.
I'm trying to find the pattern.
When both shipping and payment are null and I'm setting them in a one uow "session", only shipping Id is saved, but when call this piece of code once more (reload) having shipping not null and payment null, payment is being saved as well. And I'm not even checking if I already have payment/shipment ID:
public ActionResult PreprocessOrder()
{
try
{
using (var uow = _uowProvider.Create())
{
var store = CurrentPage.GetStore();
var order = _sessionManager.GetCurrentOrder(store.Id)
.AsWritable(uow);
if (order.CustomerInfo.CustomerReference == null)
{
var currentMember = Members.GetCurrentMember();
if (currentMember is MsParent memberParent)
{
order.SetProperties(new Dictionary<string, string>
{
{ Constants.Properties.Customer.EmailPropertyAlias, Members.GetCurrentMemberProfileModel().Email },
{ Constants.Properties.Customer.FirstNamePropertyAlias, memberParent.FirstName },
{ Constants.Properties.Customer.LastNamePropertyAlias, memberParent.LastName }
});
}
order.AssignToCustomer(currentMember.Key.ToString());
}
var shm = _shippingMethodService.GetShippingMethod(store.Id, "electronic");
order.SetShippingMethod(shm);
var paymentAlias = "bambora";
if (Request.Url.AbsoluteUri.Contains("localhost"))
{
paymentAlias = "invoicing";
}
var pm = _paymentMethodService.GetPaymentMethod(store.Id, paymentAlias);
order.SetPaymentMethod(pm);
_orderService.SaveOrder(order);
uow.Complete();
}
}
catch (Exception ex)
{
ModelState.AddModelError("productReference", "Failed to preprocess order");
Log.Error(ex, "PreprocessOrder exception");
return CurrentUmbracoPage();
}
var checkOutPage = CurrentPage.GetCheckoutPage();
return RedirectToUmbracoPage(checkOutPage.Id);
}
But I have another strange behaviour. It looks like my WebApi controller (UmbracoApiController) resets both shippingMethodId and paymentMethodId to null with _orderService.SaveOrder(...)
The only thing I can think that we have that would prevent something from being set is validation event handlers, which we do have to prevent payment / shipping methods being set that aren't allowed in the orders country, but these should throw validation exceptions and shouldn't fail silently.
The later issue could potentially be a caching issue if the cache isn't being updated, but I can't see how this would be the issue for initial issue where you are setting two things at the same time. This should be completely fine.
Do you have any reliable/repeatable replication steps? Have you validated if that's true in the demo store too?
I intended to use Vendr.Checkout in the project. And I had to test Bambora payment.
But the current project can have more simple checkout, so I can just uninstall Vendr.Checkout. But I can imagine that we could use this package in future projects. It may be fine if we just can use it as it is, without shortcuts, as I'm making now :)
Ok, cool, well at least it's not blocking you for this project.
I think I'll give it some thought and see if there is a way to only run those event handlers if it knows it's a Vendr.Checkout checkout process, that way if you do decide to do something custom, it shouldn't affect you.
Thanks for reporting this and spending the time to debug it. It's really valuable information π
Unit Of Work pattern and when changes are really saved
Hi, I'm trying to do one-step-checkout. Products are added to the cart via VueJS/WebAPI.
Customer data, payment method and shipping method are being set up in a SurfaceController method, which redirects afterwards to the Review page, which uses Html.BeginPaymentForm(Mode.Order) to render Payment form/button.
I'm using a UnitOfWork pattern as described in documentation.
(The default shipping/payment method on the order would saved all that hassle, but I can't get it now... Frustrating...)
The problem is, that when CheckoutPage gets the current order, both paymentId and shippingIt are still null, so
Html.BeginPaymentForm(Model.Order)
raises exception.It turned out, that there is a time gap right after uow.Complete when the saved values are accessible via GetCurrentOrder.
F.x.
testOrder.PaymentInfo.PaymentMethodId
is still null (same with shipping), but it was OK, if I waited 15 seconds (but not always).Do I need to call some method before I call GetCurrentOrder again?
I don't have any event handlers from Demo store implemented in my project.
But I can see, that GetCurrentOrder extension on IPublishedContent is using VendrApi.Instance to get current order:
(Implemented as in DemoStore) Surface controller uses DI.
Am I missing something?
Regards
Tomasz
Hi Thomasz,
There shouldn't be any timing issue as all changes are committed as soon as the last open UoW has it's
Complete()
method called and as mentioned in the other issue, I do think the setting of default payment method should actually be working.When you say it appears after 15 seconds, could this actually be after an app pool recycle? If so, there may be a problem with the caching layer not getting updated, but the order is actually persisting. I'm not sure why this would be the case though.
If you don't have the demo store events setup, then they won't be being used at all so that shouldn't be the problem. Though I'm not currently sure why you are seeing the issues you are.
If you recycle the app pool, is there order as you would expect it to be?
Hi Matt,
I tried something else. I removed setting shipping method from this piece of code:
And it seems to be working now. Could it be that setting the shippingMethodId i same transaction as paymentMethodId, removes the latter???
I will test it more tomorrow, I'm done for today...
Anyway, thank You for help :)
/Tomasz
Hmm, that's interesting. I can't see anything in our code that would be resetting that. We don't have any internal event handlers that are firing when either of those change that could be setting it back so not entirely sure why it's failing. It really shouldn't have a problem setting them in the same session. π€
Glad you've got it working, but would love to figure out exactly what's going on as like I say, that really shouldn't be hapenning.
/Matt
Yes, it's very strange. I'm trying to find the pattern.
When both shipping and payment are null and I'm setting them in a one uow "session", only shipping Id is saved, but when call this piece of code once more (reload) having shipping not null and payment null, payment is being saved as well. And I'm not even checking if I already have payment/shipment ID:
But I have another strange behaviour. It looks like my WebApi controller (UmbracoApiController) resets both shippingMethodId and paymentMethodId to null with _orderService.SaveOrder(...)
Hmmmm...
/Tomasz
The only thing I can think that we have that would prevent something from being set is validation event handlers, which we do have to prevent payment / shipping methods being set that aren't allowed in the orders country, but these should throw validation exceptions and shouldn't fail silently.
The later issue could potentially be a caching issue if the cache isn't being updated, but I can't see how this would be the issue for initial issue where you are setting two things at the same time. This should be completely fine.
Do you have any reliable/repeatable replication steps? Have you validated if that's true in the demo store too?
/Matt
AAAAArrrrgggghhhhhh!!!!
You asked me if I implemented events as in DemoStore. I didn't.
But all my problems looked like been caused byt them. So I did a little more investigation, as my workarounds looked unnecessary.
So... I didn't registered DemoStore events. Vendr.Checkout did!!!
Aaargh!!!
I don't know if I can just unregister these event handlers, but now I've just uninstalled this package. And everythis is working now!!! YAY!
Anyway thanks for help.
I don't now if I should delete this post or leave it for others?
/Tomasz
Aaaah, of course!
Are you using Vendr.Checkout? Or did you just install it for testing? I may need to think about some fine grained control over this then π€
Really glad you were able to find the culprit at least.
I think itβs worth leaving this issue open in case someone else has the same problem.
I intended to use Vendr.Checkout in the project. And I had to test Bambora payment.
But the current project can have more simple checkout, so I can just uninstall Vendr.Checkout. But I can imagine that we could use this package in future projects. It may be fine if we just can use it as it is, without shortcuts, as I'm making now :)
/Tomasz
Ok, cool, well at least it's not blocking you for this project.
I think I'll give it some thought and see if there is a way to only run those event handlers if it knows it's a Vendr.Checkout checkout process, that way if you do decide to do something custom, it shouldn't affect you.
Thanks for reporting this and spending the time to debug it. It's really valuable information π
/Matt
But I'm sure of one thing now: event handlers works pretty good, when registered :)
LOL
/Tomasz
Silver linings π
is working on a reply...