duracelltomi / gtm4wp

Google Tag Manager plugin for WordPress
https://gtm4wp.com/
GNU General Public License v3.0
147 stars 95 forks source link

Possible solution for duplicate transaction tracking #118

Closed duracelltomi closed 3 years ago

duracelltomi commented 5 years ago

According to the test of a plugin user on some mobile devices, JS codes re-execute on revisiting an already opened tab causing some transactions to be tracked multiple times:

https://wordpress.org/support/topic/duplicate-transactions-from-mobile-devices/

The proposed code change should be tested and added to the core

vincentkoc commented 5 years ago

@duracelltomi seems like a clean fix but needs testing. Could be expanded to use HTML5 storage as cookies sometimes an issue with some devices but not a major issue as its the same methodology as before.

duracelltomi commented 5 years ago

I will check this and commit the code this week

duracelltomi commented 5 years ago

This case needed a bit more complex coding as order ID checking had to be done in the frontend since on mobile devices where the order received page is reopened from local cache, no backend execution is done

luukfiets commented 5 years ago

Hi,

I would like to get back on this one. I'm setting up a webshop in the Netherlands and we're using iDeal. I've enabled the option to not flag orders as being tracked, since it doesn't track orders payed by iDeal with this option disabled. I've found this new solution you mentioned above to be very useful in order to prevent duplicate transactions. However, I think you might be able to improve it even further.

I can see that the new cookie gtm4wp_orderid_tracked doesn't have an expiration date which makes it a session cookie instead of persistent cookie. First question: would it be possible to set an expiration date so that even when the browser is closed and the order confirmation page is opened a week later, the cookie still is in place (unless the user deleted his cookies of course) and prevents the transaction from being sent again?

Second question: would it be possible to store multiple order IDs in the cookie? After the first order my cookie was set to the right order ID (6 in this case). When making a new purchase in the same browser session the cookie is set to the latest order ID (7). In theory, I would be able to get duplicate transactions from the order with ID 6 because it isn't stored in the cookie anymore.

Sorry for the detailed answer, but hopefully it makes sense.

vincentkoc commented 5 years ago

If we’re going to do that, localstorage is the way forward. I hate cookies at the moment, not to mention GDPR compliance

On Sat, 5 Oct 2019 at 09:09, Luuk notifications@github.com wrote:

Hi,

I would like to get back on this one. I'm setting up a webshop in the Netherlands and we're using iDeal. I've enabled the option to not flag orders as being tracked, since it doesn't track orders payed by iDeal with this option disabled. I've found this new solution you mentioned above to be very useful in order to prevent duplicate transactions. However, I think you might be able to improve it even further.

I can see that the new cookie gtm4wp_orderid_tracked doesn't have an expiration date which makes it a session cookie instead of persistent cookie. First question: would it be possible to set an expiration date so that even when the browser is closed and the order confirmation page is opened a week later, the cookie still is in place (unless the user deleted his cookies of course) and prevents the transaction from being sent again?

Second question: would it be possible to store multiple order IDs in the cookie? After the first order my cookie was set to the right order ID (6 in this case). When making a new purchase in the same browser session the cookie is set to the latest order ID (7). In theory, I would be able to get duplicate transactions from the order with ID 6 because it isn't stored in the cookie anymore.

Sorry for the detailed answer, but hopefully it makes sense.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/duracelltomi/gtm4wp/issues/118?email_source=notifications&email_token=AAAGD3FQHQ6DKCGNR2SVQS3QM7ELPA5CNFSM4HE44KSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEANDE7Y#issuecomment-538587775, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAGD3DBZLIXK2JS5JH4UDTQM7ELPANCNFSM4HE44KSA .

-- Sent from Gmail Mobile

duracelltomi commented 5 years ago

I can see that the new cookie gtm4wp_orderid_tracked doesn't have an expiration date

Could you point me to where you have this seen? This is the code that places the cookie:

var gtm4wp_orderid_cookie_expire = new Date();
gtm4wp_orderid_cookie_expire.setTime( gtm4wp_orderid_cookie_expire.getTime() + **(365*24*60*60*1000)** );
var gtm4wp_orderid_cookie_expires = "expires="+ gtm4wp_orderid_cookie_expire.toUTCString();
document.cookie = "gtm4wp_orderid_tracked=" + gtm4wp_orderid_tracked + ";" + gtm4wp_orderid_cookie_expire + ";path=/";

But I had some more reports about this issue and for some reason this does not seem to work 100% accurate.

One case I was able to identify is the Safari browser which cuts of the expiration date of this cookie to 1 day. But this is a "by design" issue, to make it work the cookie should be created in the HTTP response header but this is not possible since this would remove tracking of the first page load as well.

Local storage could be one possible way to go as @koconder mentioned, but I have no doubt this will be on the desk very soon as Safari and Firefox is releasing an updated and even more restrictive "protection" many times in a year.

The real and most clear solution would be if vendors would have an option in their products to drop incoming data if the order ID was already tracked previously.

luukfiets commented 5 years ago

Here are two images which show that the cookie is only for this session. I'm using chrome by the way. The first one is in Dutch, but the selected text says it's only for this session.

Image 1 Image 2

If local storage is an option, would it be worth it to look further into the iDeal issue? The only way I'm able to get ecommerce data to Analytics is by disabling the option of flagging orders. That makes it easy to get duplicate transactions, so this new solution with the cookie would be great workaround. However, if it's not possible to add the suggested enhancements, it might be easier to find a solution for the iDeal issue. That wouldn't solve this exact issue, but it would help a lot of iDeal users, which are forced to drop flagging for tracked orders.

duracelltomi commented 5 years ago

Yeah, finally I found the problem: there was a typo in the code that created the cookie which made it a session cookie.

Now the first approach will be to use localStorage, if this is not available, the plugin will create a cookie but now with a 1 year long expiration date

luukfiets commented 5 years ago

"Should be work with Safari at least for now." made me laugh. Thanks for adding this fix!

Do you have any plans on adding multiple order IDs in the localStorage/Cookie in order to prevent duplicate transactions when there's more then one order done and someone visits the thank you page of the previous order? I'm just curious :)

duracelltomi commented 5 years ago

Sure, I just want to have a working solution with one order ID :-) Currently I have a report where it seems that the fixed and enhanced version is not working for a website owner :-(

luukfiets commented 5 years ago

Ah perfect, I didn't want to rush you :)

Should I open another issue so that we can continue there or is this one still one still on the radar eventhough it's marked as closed?

duracelltomi commented 5 years ago

I have some open threads regarding this, believe me, it is on my radar :-)

gbvaz commented 4 years ago

Hey, sorry I know this is already closed, but I've been reading all the threads about this problem here and in WP forum, and I know it's been a long time since all this happened.

The thing is, I've been having this problem with desktop users with cookies disabled. I was thinking if this has been implemented with the local storage solution mentioned, and if browsers now a days are also not storing this info anymore due to GDPR.

You probably already thought about this, and have the possible arguments against this, but bear with me, what if we could just store this in the database making an AJAX call or something like that to the backend, and move this control over to the server side of things, and when page reloads it pulls this info form the backend and prevents this from happening again once and for all?

duracelltomi commented 4 years ago

It is interesting that you see this on desktop since the cookie based prevention is not the only solution for multiple tracked transactions. Actually the cookie based protection was added much later and there is a server side solution active by default that flags all orders as being tracked. So on desktop, if the user reloads the page, there should not be any extra ecommerce tracking because of the server side tracking.

The cookie based prevention was introduced because especially on mobile devices users left the browser tab open with the order received page. Some days later, when this tab was reopened, the content was re-rendered and javascript codes were re-executed without the browser touching the backend.

I like the idea of having a backend AJAX call instead of or next to the cookie protection as this would be re-executed as well. I just do not know whether in that case the AJAX call would be returned from browser cache or from the backend. But perhaps a cache buster parameter could help.

Could you help me implementing this within GTM4WP?

erikmolenaarnl commented 3 years ago

I might have a good idea to prevent duplicate transaction data:

Only fire the ecommerce data when the order-received page is viewed within XX (e.g. 5) minutes after payment. Combined with the existing protection by the cookie (which prevents page reloads within this time limit), it could be a 100% watertight fix?

I believe this is it:

$order->get_date_paid()

As shown in Woocommerce order backend:

image

Looking forward to your thoughts.

P.s. I also posted this suggestion on the WP.org support forum, sry for the double post.

duracelltomi commented 3 years ago

Hi,

This could be one more level of protection, yes. What I have experienced is that mostly in mobile browsers, JS codes on the order received page can rerun without real page reload when the mobile browser app is awakened from sleep. Perhaps checking the time stamp will see the actual time in those cases and prevent double tracking.

erikmolenaarnl commented 3 years ago

Do you accept donations/paid work to include this feature asap in the next stable release? I have multiple e-commerce stores for which this is a real problem in their sales reporting.

Looking forward to your reply. Thanks :-)

duracelltomi commented 3 years ago

It is not a question of money :-)

What you can do to include this in the next release is to clone this repo, add the necessary code (you or your programmer) and then send a PR . I will then review and include it asap.

erikmolenaarnl commented 3 years ago

Sure no problem, I'll get someone started on it asap! I am glad to be of any help to help you out :-)

I have thought about it some more and I think it's better to use $order->get_date_created()for this, don't you agree? In case orders will be paid later. GTM4WP also transmits ecommerce data when the order-received page is hit, which is when the order is created.

vincentkoc commented 3 years ago
WC get_date_created() https://github.com/woocommerce/woocommerce/blob/master/includes/abstracts/abstract-wc-order.php#L315 has datetime stamp so if a person has ordered more than once in a day it will work. You probably need to use this in combination with cookies and other things to try an stop the hits. This wont solve the problem as a whole. Serviceworkers + HTML5 Storage/Localstorage are probably a better solution over the cookies as it gives us control over the default refresh behaviour especially on the mobie services. There are quite a few PWA plugins which we could try to nativley interface with on the checkout comfirmation page behaviour. From: Erik Molenaar ***@***.***>Date: Thursday, 18 March 2021 at 7:39 amTo: duracelltomi/gtm4wp ***@***.***>Cc: Vincent Koc ***@***.***>, Mention ***@***.***>Subject: Re: [duracelltomi/gtm4wp] Possible solution for duplicate transaction tracking (#118)Sure no problem, I'll get someone started on it asap! I am glad to be of any help to help you out :-)I have thought about it some more and I think it's better to use $order->get_date_created() for this, don't you agree? In case orders will be paid later. GTM4WP also transmits ecommerce data when the order-received page is hit, which is when the order is created.—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or unsubscribe.
erikmolenaarnl commented 3 years ago

Hi @duracelltomi,

I would like to hear your thoughts on what I had in mind how to further prevent duplicate transaction tracking in my fork.

What we have:

So primary check will be:

✅ At is_order_received_page(): is $order->get_date_created() <= 25 minutes? If yes, fire transaction tracking. If not, do nothing.

However, some orders possibly get paid many days later after being created while being stuck on "Pending payment" before finally hitting the order received page.

So secondary check will be:

✅ At is_order_received_page(): is $order->get_date_paid() available? If yes, is it <= 25 minutes? If yes, fire transaction tracking. Else, do nothing.

What do you think? Does this make sense? Please hit me back so I can get my fork started :-)

vincentkoc commented 3 years ago

I like this approach, would say 30mins, or ideally (30 minus 1) so 29 minutes to fit just below the average Google Analytics session length.

Additional tules to look at include:

If no date/timestamp exists then ignore. If purchase cookie/localstorage exists then check the purchase id not equal to current purchase id (incase of repurchase in 30mins, forgot an item etc)

I still feel this wont be 100% as the way mobile browsers cache pages/requests but should meet 90% of use cases.

Vince

On Sat, 20 Mar 2021 at 08:18, Erik Molenaar @.***> wrote:

Hi @duracelltomi https://github.com/duracelltomi,

I would like to hear your thoughts on what I had in mind how to further prevent duplicate transaction tracking in my fork.

What we have:

  • GTM4WP transaction tracking happens only at is_order_received_page()
  • Cheque or BACS go straight to is_order_received_page()
  • Most users using a 3rd party payment method hit this page within a few minutes after submitting the checkout form when the order was created.
  • Some users get delayed but 99% get there within 25 minutes. Possible causes: having trouble with authenticating their payment, can't find credit card, having trouble logging into Paypal, unexpected toilet break etc.

So primary check will be:

✅ At is_order_received_page(): is $order->get_date_created() <= 25 minutes? If yes, fire transaction tracking. If not, do nothing.

However, some orders possibly get paid many days later after being created while being stuck on "Pending payment" before finally hitting the order received page.

So secondary check will be:

✅ At is_order_received_page(): is $order->get_date_paid() available? If yes, is it <= 25 minutes? If yes, fire transaction tracking. Else, do nothing.

What do you think? Does this make sense? Please hit me back so I can get my fork started :-)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/duracelltomi/gtm4wp/issues/118#issuecomment-803139477, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAGD3FJV2UKLXK6JWNN2LTTEO5TNANCNFSM4HE44KSA .

-- Sent from Gmail Mobile

duracelltomi commented 3 years ago

I like this approach, one addition that I am sure will be requested if not included in the first iteration: there should be an option under the "Advanced" tab where users can change the 30 min value. I am pretty sure some users will prefer 60 minutes, others 20 min, with that, we can keep this set to 30 by default for most users but more advanced users can adjust based on their figures.

duracelltomi commented 3 years ago

added in v1.13

believethehyped commented 2 years ago

Hello,

I use this settings because on my ecommerce site we have BNPL payment gateways, which might return payment confirmation within 24 hours. Unfortunately, these orders are duplicated in GA.

image image image

Is there any solution that can fix this issue?