Our app's checkout screen has a button that submits to myserver/payment.awp - which maps to the Payment method on our process object.
One of our customers is reporting occasional users submitting duplicate orders by clicking the button once, then again before the first process has completed. This despite having javascript in place to disable the button when it's first clicked - we frankly can't figure out how the hell the are doing this, but our transaction logs clearly show multiple instances of the Payment method running simultaneously in the same session. (FYI they are running under COM with multiple servers active.)
I'd like to add code on the back end to prevent that from happening. I thought something like this would work if coded at the outset of the method:
lcPayStatus = Session.GetSessionvar('PayStatus')
IF lcPayStatus = [INPROCESS]
oUtil.HTMLMsg([Please Be Patient],[Payment is already in process])
This.trace([PAY],[Starting checkout attempt when payment already in process]) && Generate Log entry
RETURN
ELSE
This.trace([PAY],[Starting checkout process. Prior status: ]+lcPayStatus)
SESSION.SetSessionVar([PayStatus], [INPROCESS])
ENDIF
This.trace([PAY],[PayStatus=]+Session.getsessionvar([PayStatus]))
* PayStatus will be cleared once the Payment process is completed
But this is not behaving as I expected. The logs verify that he PayStatus session var was explicitly set to "INPROCESS" the first time the Payment method runs. But when the second instance of the method is invoked, PayStatus comes up blank again - it's somehow not recognizing the fact the INPROCESS setting.
Is there some buffering going on here? If so, is there a way to work around it? Another approach that would prevent launching a second instances of the Payment method until the first one is completed?
--stein
By default, the wwSession table gets updated when transaction completes, and session is closed (changes held in this.oData until then)
You need to save changes right away.
Hello, we had the same problem after switching to COM mode As pointed out you have to add SESSION.Save().
As Thierry points out there's a possibility of multiple clicks causing multiple requests to process 'simultaneously'.
As suggested using Session.Save()
explicitly and then making sure you check the session vars before every Save()
operation can help, but even that's no guarantee if two requests are running literally side by side.
The only way you can really prevent that is by using a 'locking' variable (semaphore) that you check before the transaction and that is set before you enter the transaction.
- Check for the 'lock variable' in session table
- If it exists - it's busy, don't allow
- If it doesn't exist - create one and save
- Now run your processing
- When done remove the Session var
The value you save works best if it's a timestamp of some sort so if for some reason the remove operation doesn't happen it doesn't prevent you from saving. So you check for existence and for a certain timeout period (probably a few seconds only).
This makes the Session 'save' operation really small and atomic (basically a single value save operation) so there's very little (but still not 100%!) chance that you run into a truly simultaneous transaction.
Frankly I've never run into a scenario where apps are too busy to cause that kind of contention - even with accidental multi-clicks. For most apps it's sufficient to explicitly check before saving that the data wasn't already saved (with a timestamp preferably). If you see that the order was already saved within the last few seconds you can assume it's a dupe. This is a good data practice anyway for any data that may have contention.
Also, I tend to write my apps in such a way that multiple saves even if it does happen won't break things. If an order gets saved twice it should be repeatable so that even a if another save occurs it wouldn't actually break anything. In most applications there are a only a few places where this even applies (ie. where multiple users edit the same data).
+++ Rick ---