Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payment via PayPal sometimes results in duplicate purchases #673

Open
icewindow opened this issue Nov 26, 2023 · 1 comment
Open

Payment via PayPal sometimes results in duplicate purchases #673

icewindow opened this issue Nov 26, 2023 · 1 comment

Comments

@icewindow
Copy link
Contributor

Issue description

When a user purchases a ticket via PayPal, sometimes two Purchases are created, resulting in the user gaining an extra ticket they did not pay for.

Details/Observations

It is currently unknown when or why duplicate purchases are created. So far duplicate purchases have only been observed to be created when the user checks out via PayPal, suggesting the problem lies within the PayPal handler or with PayPal themselves. However, this could be a result of almost all of our users using PayPal instead of Stripe, thus biasing the problem towards PayPal.

The creation date of the duplicate purchases are also not identical, but rather a few seconds (usually between 2 and 3) apart, suggesting the callback gets called multiple times. Other than the creation date, the purchases are identical (transaction ID, token, etc.).

Ideas for mitigation

Without having a clear idea why some purchases are duplicated, there are some possible ways to mitigate the problem.

  • The purchase parameters are put into the session.
    After the payment has been processed the params are not immediately forgotten, but rather during the success or failed view.
    Forgetting the parameters right after the purchase has been created might work, but could also potentially break stuff somewhere else.
  • The transaction ID could be made to be unique in the database. Attempting to create a duplicate purchase would (should?) result in a constraint violation.
    The risk here is potentially breaking the payment flow when a duplicate purchase is created.

"Proper" solution

The above described ideas are really only a temporary fix. To properly address the issue the payment flow needs to be investigated and pinned down, why some purchases are duplicated.
Furthermore, some additional measures could be put into place to prevent duplicate purchases from being created. A check if the same transaction ID is already present, similar to the suggestion above, would probably be a good idea. Or perhaps the use of a nonce, attached to the payment processing callback. If the nonce has already been used, meaning the callback was already executed, no further action is taken, instead the result of the previous callback is send again.

@icewindow
Copy link
Contributor Author

Looking in the access logs reveals that multiple calls are being made to the callback:

This one resulted in multiple purchases being created.

194.94.76.66 - - [19/Nov/2023:09:27:48 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-2xxxxxxxxxxxxxxxD&PayerID=Zxxxxxxxxxxx4 HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.76.66 - - [19/Nov/2023:09:27:48 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-2xxxxxxxxxxxxxxxD&PayerID=Zxxxxxxxxxxx4 HTTP/1.1" 301 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.76.66 - - [19/Nov/2023:09:27:50 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-2xxxxxxxxxxxxxxxD&PayerID=Zxxxxxxxxxxx4 HTTP/1.1" 302 414 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.76.66 - - [19/Nov/2023:09:27:51 +0000] "GET /payment/successful/366 HTTP/1.1" 200 3023 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

Of note are the the purchase IDs, which are 365 and 366, the latter being accessed via payment/successful/366, whereas the former presumably would've been used after the request that resulted in a 499 status code.


This one resulted in just a single purchase being created, despite the callback being called multiple times.

194.94.79.249 - - [19/Nov/2023:09:42:47 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-9xxxxxxxxxxxxxxxW&PayerID=5xxxxxxxxxxxU HTTP/1.1" 302 414 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.79.249 - - [19/Nov/2023:09:42:47 +0000] "GET /payment/successful/367 HTTP/1.1" 499 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.79.249 - - [19/Nov/2023:09:42:47 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-9xxxxxxxxxxxxxxxW&PayerID=5xxxxxxxxxxxU HTTP/1.1" 301 162 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.79.249 - - [19/Nov/2023:09:42:48 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-9xxxxxxxxxxxxxxxW&PayerID=5xxxxxxxxxxxU HTTP/1.1" 302 414 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
194.94.79.249 - - [19/Nov/2023:09:42:48 +0000] "GET /payment/successful/367 HTTP/1.1" 302 322 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

Presumably the payment/successful/ request removed the payment parameters from the session and further requests to the callback route couldn't due to this.


There are instances where only a single request to the callback route is made though:

194.94.76.66 - - [19/Nov/2023:09:43:49 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-8xxxxxxxxxxxxxxx3&PayerID=JxxxxxxxxxxxC HTTP/1.1" 302 430 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
194.94.76.66 - - [19/Nov/2023:09:43:50 +0000] "GET /payment/successful/368 HTTP/1.1" 200 3990 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"

There's also this one, which calls the callback route twice but with different return codes. This one also resulted in just a single purchase being created...

194.94.79.249 - - [19/Nov/2023:10:35:59 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-5xxxxxxxxxxxxxxxW&PayerID=Vxxxxxxxxxxx2 HTTP/1.1" 301 162 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1.1 Mobile/15E148 Safari/604.1"
194.94.79.249 - - [19/Nov/2023:10:36:03 +0000] "GET /payment/callback?gate=paypal_express&type=return&token=EC-5xxxxxxxxxxxxxxxW&PayerID=Vxxxxxxxxxxx2 HTTP/1.1" 302 414 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1.1 Mobile/15E148 Safari/604.1"
194.94.79.249 - - [19/Nov/2023:10:36:03 +0000] "GET /payment/successful/371 HTTP/1.1" 200 3107 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1.1 Mobile/15E148 Safari/604.1"

This seems to be the normal behavior for the site, a 301 redirect followed by the 302 to (presumably) the success page.

I think this might actually be a misconfiguration of our web server, or rather the proxy in front of it, and the payment callback URLs are generated with the HTTP scheme instead of HTTPS. Could this also play a part in the duplicate entries being generated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant