De-bouncing Submit Order button

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!

:slight_smile:

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

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

From: Arne B.

From: “Aidan R.” [email protected]

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

Aidan R. 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)

I am not sure I understand your polling idea :frowning:

-g.

From: George M.

I am not sure I understand your polling idea :frowning:

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

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