We're having an issue, where some orders are not finalized. It only happens once in a while.
The payments are created and visible in the gateway (Nets Easy), and the customers are getting the order confirmation email.
But the orders are not visible in the backend. They can be found in the database, and are missing values finalizedDate, transactionId, paymentStatus, amountAuthorized and baseCurrencyExchangeRate.
There are no errors or anything in the logs.
At first glance, it can be an issue with:
- Callback from Nets
- Vendr.Contrib.PaymentProviders.Nets not handling callback properly
- Vendr not finalizing order properly
I've contacted Nets support and asked them if they can see anything unusual with the transactions where it happened.
I'm interested in hearing if anyone has had similar issues, or if anyone might point me in the direction of where the issue might be found.
customers are getting the order confirmation email
Is this the Vendr order confirmation email? or one from Nets?
If it's from Vendr then it's interesting because this only occurs from an event handler for OrderFinalizedNotification, which itself can only fire AFTER an order has been finalized and the transaction has completed.
The first thing that I always think about in such scenarios is if there are any other event handlers that fire that modify the order in some way and if those handlers are accessing the order from the notification event handlers args. The key thing is, the order in the event handlers args is considered a "snapshot" of the order at the time the event occurred. But if you have multiple event handlers firing that modify the order, it's possible the order in the DB is more up to date. In these occasions where it's important to be dealing with the latest order, your event handlers should re-fetch the latest order instead of using the one in the args.
The reason this could be an issue is if an event handler has a stale copy of the order from an event handler, but some other process finalizes the order but then the original event handler re-persists the order, it could essentially reset the finalized order fields. I'd just double check any event handlers to make sure this isn't the case.
Other things to consider could be, is the site load balanced? Could one node be updating, but failing to notify others in the cluster?
I'd also check to see if Nets has a log of their notifications so you can see if the webhook callback occurred and if it was successful.
Hopefully these give a few starting options to check first.
Is this the Vendr order confirmation email? or one from Nets?
It's the mail from Vendr, Nets does not send any mails out
Other things to consider could be, is the site load balanced? Could
one node be updating, but failing to notify others in the cluster?
Site is not load balanced
...if there are any other event handlers that fire that modify the
order in some way and if those handlers are accessing the order from
the notification event handlers args...
So if I understand you correctly, having something like this could be an issue, and instead of getting the order from args, I should get the order "fresh" from the orderservice?
using Vendr.Common;
using Vendr.Common.Events;
using Vendr.Core.Events.Notification;
using Vendr.Core.Services;
namespace maxibag.Library.Commerce.Handlers
{
public class OrderFinalizedHandler : NotificationEventHandlerBase<OrderFinalizedNotification>
{
private readonly IOrderService _orderService;
private readonly IUnitOfWorkProvider _uowProvider;
public OrderFinalizedHandler(
IOrderService orderService,
IUnitOfWorkProvider uowProvider)
{
_orderService = orderService;
_uowProvider = uowProvider;
}
public override void Handle(OrderFinalizedNotification evt)
{
var finalizedOrder = evt.Order;
using (var uow = _uowProvider.Create())
{
var order = finalizedOrder.AsWritable(uow);
// Save some additional properties to the order
_orderService.SaveOrder(order);
uow.Complete();
}
}
}
}
I'd also check to see if Nets has a log of their notifications so you
can see if the webhook callback occurred and if it was successful.
There isn't anything available, but as mentioned, I've contacted Nets' support and asked them if they can see anything unusual with the transactions.
RE the code snippet, your understanding is correct and so yes, the code snippet you present could be an a problem. You should re fetch the order before performing any write operations.
And investigating it further, it seems as if the issue is that the order finalization is being overwritten at some point.
To reiterate what happens:
Payments are created fine in the gateway
Order confirmation email is sent out
Orders are not visible in the backend, as they are missing the following information:
- finalizedDate is NULL (should have a datetime)
- transactionId is NULL (should have the transactionid from NETS)
- paymentStatus is 0 (should be 1)
- amountAuthorized is 0 (should have the amount authorized)
- baseCurrencyExchangeRate is NULL (is 1,0000 on all finalized orders)
But in the order confirmation email, both the transactionId and finalizedDate have a value, so orders are getting finalized, but then overwritten afterwards.
We've removed the OrderFinalizedHandler code, so it's not that.
So the only thing else I can think of is, that we have a custom RenderController for the Confirmation page, where we do the following to avoid doing double ecommerce tracking for Analytics.
var finalizedOrder = Request.QueryString["order"] != null ? VendrApi.Instance.GetOrder(store.Id, Request.QueryString["order"]) : VendrApi.Instance.GetCurrentFinalizedOrder(store.Id);
if (finalizedOrder != null)
{
var trackingProperty = finalizedOrder.Properties["IsTracked"];
if (trackingProperty != null)
{
var isTracked = Convert.ToBoolean(trackingProperty.ToString());
if (!isTracked)
{
using (var uow = _uowProvider.Create())
{
var order = finalizedOrder.AsWritable(uow);
order.SetProperty("IsTracked", new PropertyValue("True"));
_orderService.SaveOrder(order);
uow.Complete();
}
}
}
else
{
using (var uow = _uowProvider.Create())
{
var order = finalizedOrder.AsWritable(uow);
order.SetProperty("IsTracked", new PropertyValue("False"));
_orderService.SaveOrder(order);
uow.Complete();
}
}
}
Coming back from the payment, there isn't a value in the querystring, so it's using VendrApi.Instance.GetCurrentFinalizedOrder(store.Id), which should give me the full finalized order should it not, and if the order is not finalized yet, if the callback is delayed or something, it will just returns null, will it not?
I just can't see how the above code, can overwrite the properties from the payment gateway.
We only have this issue on sites using Vendr.Contrib.PaymentProviders.Nets, not with any other gateways, so I don't know, it might be a bug in that.
But before reporting an issue on that, I just want to rule out that it's any of our code.
I'm investigating options for this kind of scenario in some DB work I'm looking at now for a future v3. The plan there is that AsWritable will fetch the entity state directly from the DB so you KNOW you are always working with the latest state. In addition, I'm gonna look at using a version column on entities (or orders at the very least) to ensure that updates don't "overwrite" other changes. I need to test all this though, especially from a performance perspective.
Back to your issue at hand however there is a slight difference in what happens with finalized orders in that, when you hit the continue URL which sends you to the confirmation page, this actually moves the current order into the "current finalized order" slot in the session. This is to ensure that you can display the order on the confirmation page. How this differs from your understanding is that it's the continue URL that moves it to "current finalized order" not actually the act of finalizing the order (which usually happens at the same time but often slightly after via webhook) and so it's possible that in your logic, the order isn't actually finalized yet when you update it's properties, which could indeed cause the order to re-write.
Indeed, which is entirely possible, because the finalized notification is occurring via webhook which will likely hit at the exact same time your are redirected to the confirmation page.
Could your tracking logic be moved to a finalized handler maybe?
Then I'm like 99.9% sure that when the issue happens, it's caused by the callback coming at the exact same time the above code runs.
I guess the reason we're only seeing it with Nets, is the timing of their callback must more closely match the timing of clients coming back to the confirmation page, compared to other gateways. 🤷♂️
Orders not being finalized
We're having an issue, where some orders are not finalized. It only happens once in a while.
The payments are created and visible in the gateway (Nets Easy), and the customers are getting the order confirmation email.
But the orders are not visible in the backend. They can be found in the database, and are missing values finalizedDate, transactionId, paymentStatus, amountAuthorized and baseCurrencyExchangeRate.
There are no errors or anything in the logs.
At first glance, it can be an issue with: - Callback from Nets - Vendr.Contrib.PaymentProviders.Nets not handling callback properly - Vendr not finalizing order properly
I've contacted Nets support and asked them if they can see anything unusual with the transactions where it happened.
I'm interested in hearing if anyone has had similar issues, or if anyone might point me in the direction of where the issue might be found.
Umbraco 8.17.2 Vendr 2.0.4 Vendr.Contrib.PaymentProviders.Nets 2.0.0
Hi Michael,
The interesting thing in what you say is
Is this the Vendr order confirmation email? or one from Nets?
If it's from Vendr then it's interesting because this only occurs from an event handler for
OrderFinalizedNotification
, which itself can only fire AFTER an order has been finalized and the transaction has completed.The first thing that I always think about in such scenarios is if there are any other event handlers that fire that modify the order in some way and if those handlers are accessing the order from the notification event handlers args. The key thing is, the order in the event handlers args is considered a "snapshot" of the order at the time the event occurred. But if you have multiple event handlers firing that modify the order, it's possible the order in the DB is more up to date. In these occasions where it's important to be dealing with the latest order, your event handlers should re-fetch the latest order instead of using the one in the args.
The reason this could be an issue is if an event handler has a stale copy of the order from an event handler, but some other process finalizes the order but then the original event handler re-persists the order, it could essentially reset the finalized order fields. I'd just double check any event handlers to make sure this isn't the case.
Other things to consider could be, is the site load balanced? Could one node be updating, but failing to notify others in the cluster?
I'd also check to see if Nets has a log of their notifications so you can see if the webhook callback occurred and if it was successful.
Hopefully these give a few starting options to check first.
Matt
Hi Matt
It's the mail from Vendr, Nets does not send any mails out
Site is not load balanced
So if I understand you correctly, having something like this could be an issue, and instead of getting the order from args, I should get the order "fresh" from the orderservice?
There isn't anything available, but as mentioned, I've contacted Nets' support and asked them if they can see anything unusual with the transactions.
Hi Michael,
RE the code snippet, your understanding is correct and so yes, the code snippet you present could be an a problem. You should re fetch the order before performing any write operations.
Matt
I've changed it, and we'll see how it goes. Thanks...
Unfortunately this has happened again.
And investigating it further, it seems as if the issue is that the order finalization is being overwritten at some point.
To reiterate what happens: Payments are created fine in the gateway Order confirmation email is sent out Orders are not visible in the backend, as they are missing the following information: - finalizedDate is NULL (should have a datetime) - transactionId is NULL (should have the transactionid from NETS) - paymentStatus is 0 (should be 1) - amountAuthorized is 0 (should have the amount authorized) - baseCurrencyExchangeRate is NULL (is 1,0000 on all finalized orders)
But in the order confirmation email, both the transactionId and finalizedDate have a value, so orders are getting finalized, but then overwritten afterwards.
We've removed the OrderFinalizedHandler code, so it's not that.
So the only thing else I can think of is, that we have a custom RenderController for the Confirmation page, where we do the following to avoid doing double ecommerce tracking for Analytics.
Coming back from the payment, there isn't a value in the querystring, so it's using
VendrApi.Instance.GetCurrentFinalizedOrder(store.Id)
, which should give me the full finalized order should it not, and if the order is not finalized yet, if the callback is delayed or something, it will just returns null, will it not?I just can't see how the above code, can overwrite the properties from the payment gateway.
We only have this issue on sites using Vendr.Contrib.PaymentProviders.Nets, not with any other gateways, so I don't know, it might be a bug in that.
But before reporting an issue on that, I just want to rule out that it's any of our code.
Hi Michael,
I'm investigating options for this kind of scenario in some DB work I'm looking at now for a future v3. The plan there is that
AsWritable
will fetch the entity state directly from the DB so you KNOW you are always working with the latest state. In addition, I'm gonna look at using a version column on entities (or orders at the very least) to ensure that updates don't "overwrite" other changes. I need to test all this though, especially from a performance perspective.Back to your issue at hand however there is a slight difference in what happens with finalized orders in that, when you hit the continue URL which sends you to the confirmation page, this actually moves the current order into the "current finalized order" slot in the session. This is to ensure that you can display the order on the confirmation page. How this differs from your understanding is that it's the continue URL that moves it to "current finalized order" not actually the act of finalizing the order (which usually happens at the same time but often slightly after via webhook) and so it's possible that in your logic, the order isn't actually finalized yet when you update it's properties, which could indeed cause the order to re-write.
Oh, so I can see that I've misunderstood something. Of course it's not the callback that finalizes the order. 🤦♂️
So if the callback comes between this line
and this line
The TransactionInfo could potentially be overwritten.
There must be something with Nets, since it's only with them that it happens 🤷♂️
So I guess we'll look into alternative ways to aither track purchases to Google Analytics, or to avoid double tracking 🤔
Indeed, which is entirely possible, because the finalized notification is occurring via webhook which will likely hit at the exact same time your are redirected to the confirmation page.
Could your tracking logic be moved to a finalized handler maybe?
Now it makes sense, thanks for clarifying 👍
Then I'm like 99.9% sure that when the issue happens, it's caused by the callback coming at the exact same time the above code runs.
I guess the reason we're only seeing it with Nets, is the timing of their callback must more closely match the timing of clients coming back to the confirmation page, compared to other gateways. 🤷♂️
We use Tag Manager to load Analytics and other 3rd party scripts in accordance the users cookie consent. But I guess the best option here, would be to do the purchase tracking server side, during the order finalized notification event, using the measurement protocol https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#enhancedecom
I'll guess we have to look into that.
Thanks for your input, much appreciated 👍
No problem, glad we were able to find the issue and fingers crossed I can provide a more meaningful fix in a future version
is working on a reply...