Howdy!
I'm writing an online store, and I don't want mine to be one of
the ones that has to put bold-face warning text by the Submit
button, saying: DANGER: PRESS THIS BUTTON ONLY ONCE!
:)
My current plan is to have any and all POSTs from the Submit
button to redirect to a processing_order page, which uses a
meta refresh every three seconds or so to poll for the final
order completion page.
Multiple Submit POSTs does order processing? redirects to:
1st yes
processing_order page
2nd no
processing_order page
3rd no
processing_order page
Coupled with that, I need a way to indicate that the current
order is in the processing state, and this has to be totally
atomic.
I'm not sure how fancy I need to get.
For example:
unless session[:order_in_process]
session[:order_in_process] = true
# bill credit card
end
That's essentially what I want to do, but I don't like the
potential race condition there. I'll be using Mongrel to
host my Nitro app. My understanding is Mongrel uses ruby
green threads, and I'm not planning to run multiple Mongrel
proxys, just one. I'm also using the MemorySessionStore.
So as far as I know, all my requests will be handled in the
context of a single ruby process.
If so, I presume I could eliminate the above race condition
with a global mutex...
do_order_processing = false
$order_sync_mutex.synchronize {
unless session[:order_in_process]
session[:order_in_process] = true
do_order_processing = true
end
}
if do_order_processing
# bill credit card
end
Anyway, I just wanted to ask if my approach seems reasonable,
and/or if I've missed some horrible loophole.
Thanks,
Bill
on 2007-10-10 09:44
on 2007-10-10 10:46
> Howdy! > > I'm writing an online store, and I don't want mine to be one of > the ones that has to put bold-face warning text by the Submit > button, saying: DANGER: PRESS THIS BUTTON ONLY ONCE! Why not deactivate the button on click and do the order processing asynchronously? Aidan
on 2007-10-10 12:18
Aidan Rogers schreef: >> Howdy! >> >> I'm writing an online store, and I don't want mine to be one of >> the ones that has to put bold-face warning text by the Submit >> button, saying: DANGER: PRESS THIS BUTTON ONLY ONCE! >> > > Why not deactivate the button on click and do the order processing > asynchronously? > Good idea but breaks when Javascript is disabled/not available. (ab)
on 2007-10-10 21:29
From: Arne Brasseur > From: "Aidan Rogers" <aidan@yoyo.org> > >> > >> I'm writing an online store, and I don't want mine to be one of > >> the ones that has to put bold-face warning text by the Submit > >> button, saying: DANGER: PRESS THIS BUTTON ONLY ONCE! > > > > Why not deactivate the button on click and do the order processing > > asynchronously? > > Good idea but breaks when Javascript is disabled/not available. Indeed, I like the idea, thanks. My store currently uses no Javascript at all, though. Also I favor debouncing on the server side in the hopes of allowing the client to recover from situations like: - client clicks submit, but their wireless connection drops after the POST, and they get a server timed out - client clicks back button and clicks submit again; the server sees the POST a second time, but that's fine I think my proposed approach will handle these situations safely, but it all depends on all Nitro requests being handled by a single Ruby process. That means I have to make sure the http server isn't set up to proxy requests to multiple Mongrel processes, and such. (But we have a low-traffic site, so I don't anticipate needing to scale to multiple Mongrel processes anytime soon. If I did, I guess I could use a filesystem lock instead of a Ruby Mutex.) Regards, Bill
on 2007-10-11 00:32
From: George Moschovitis > > I am not sure I understand your polling idea :( The polling idea was a method of presenting an, "We are processing your order, this may take a moment..." page, while the server is completing the purchase in an asynchronous thread. (Also it was conceived as a way of eliminating special cases, in that every (possible) multiple click of the Submit button goes through the same logic.) The processing_order page polls "itself" with a <meta http-equiv="refresh" content="3" /> and as long as the server is still busy processing the order, the page remains at the "...this may take a moment." page. But when it polls and the order is complete, then its Controller redirects to a final order complete, or purchase failed page. The logic for the refresh page looks like this: # This is a meta-refresh holding page... the user can click the Submit button on # final_review as much as they like, and they'll end up here def processing_order do_process_order = false $order_sync_mutex.synchronize { if session[:order_process_state].nil? session[:order_process_state] = :in_process do_process_order = true end } if do_process_order kickoff_async_process_order_and_bill_cc sleep 2.0 # if it hasn't completed by this time, we'll show the "This may take a moment..." page end order_state = $order_sync_mutex.synchronize { session[:order_process_state] } case order_state when :complete then redirect :order_complete when :fail then redirect :purchase_error else # continue and display the processing_order meta refresh html end end So, everytime processing_order is invoked, it checks the session[:order_process_state] value. If we haven't begun processing the order yet, it knows to kick off the async processing. Regardless, it always checks the current session[:order_process_state] at the end, to determine whether to remain on the "This may take a moment" polling page, or proceed to the purchase succeeded/failed pages. Does that make sense and/or seem reasonable? Regards, Bill