TPac; credit card payment "Processing..." page
authorBill Erickson <berick@esilibrary.com>
Mon, 21 Nov 2011 20:47:37 +0000 (15:47 -0500)
committerMike Rylander <mrylander@gmail.com>
Wed, 21 Mar 2012 13:56:56 +0000 (09:56 -0400)
To prevent impatient patrons from re-submitting credit card payment
request forms, insert a Processing... page between the initial form
submission and the actual payment processing.

Steps 3-5 are new or modified.

1. User selects transactions to pay
2. User fills in payment form and Submits
3. Payment form data is cached in memcache with a temporary token
4. Processing page is displayed with message about how processing the
   payment can take time, do not refresh, etc.
5. Page is refreshed via http meta refresh to submit the form using
   the temp token.  The page is not visibly changed, though, since it
   takes time for the form submission to begin returning data.
6. User is directed to the payment results page.

Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/templates/opac/myopac/main_pay.tt2
Open-ILS/src/templates/opac/myopac/main_pay_init.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/main_payment_form.tt2
Open-ILS/web/css/skin/default/opac/style.css

index 76b3f63..14e0760 100644 (file)
@@ -144,6 +144,7 @@ sub load {
     return $self->load_myopac_circs if $path =~ m|opac/myopac/circs|;
     return $self->load_myopac_payment_form if $path =~ m|opac/myopac/main_payment_form|;
     return $self->load_myopac_payments if $path =~ m|opac/myopac/main_payments|;
+    return $self->load_myopac_pay_init if $path =~ m|opac/myopac/main_pay_init|;
     return $self->load_myopac_pay if $path =~ m|opac/myopac/main_pay|;
     return $self->load_myopac_main if $path =~ m|opac/myopac/main|;
     return $self->load_myopac_receipt_email if $path =~ m|opac/myopac/receipt_email|;
index fddb0ed..da6f3b4 100644 (file)
@@ -7,6 +7,8 @@ use OpenILS::Utils::Fieldmapper;
 use OpenILS::Application::AppUtils;
 use OpenILS::Event;
 use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Cache;
+use Digest::MD5 qw(md5_hex);
 use Data::Dumper;
 $Data::Dumper::Indent = 0;
 use DateTime;
@@ -1167,24 +1169,15 @@ sub load_myopac_payments {
     return Apache2::Const::OK;
 }
 
-sub load_myopac_pay {
+# 1. caches the form parameters
+# 2. loads the credit card payment "Processing..." page
+sub load_myopac_pay_init {
     my $self = shift;
-    my $r;
+    my $cache = OpenSRF::Utils::Cache->new('global');
 
     my @payment_xacts = ($self->cgi->param('xact'), $self->cgi->param('xact_misc'));
-    $logger->info("tpac paying fines for xacts @payment_xacts");
-
-    $r = $self->prepare_fines(undef, undef, \@payment_xacts) and return $r;
 
-    # balance_owed is computed specifically from the fines we're trying
-    # to pay in this case.
-    if ($self->ctx->{fines}->{balance_owed} <= 0) {
-        $self->apache->log->info(
-            sprintf("Can't pay non-positive balance. xacts selected: (%s)",
-                join(", ", map(int, $self->cgi->param("xact"), $self->cgi->param('xact_misc'))))
-        );
-        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
-    }
+    return $self->generic_redirect unless @payment_xacts;
 
     my $cc_args = {"where_process" => 1};
 
@@ -1194,11 +1187,71 @@ sub load_myopac_pay {
         billing_zip
     /);
 
+    my $cache_args = {
+        cc_args => $cc_args, 
+        user => $self->ctx->{user}->id,
+        xacts => \@payment_xacts
+    };
+
+    # generate a temporary cache token and cache the form data
+    my $token = md5_hex($$ . time() . rand());
+    $cache->put_cache($token, $cache_args, 30);
+
+    $logger->info("tpac caching payment info with token $token and xacts [@payment_xacts]");
+
+    # after we render the processing page, we quickly redirect to submit
+    # the actual payment.  The refresh url contains the payment token.
+    # It also contains the list of xact IDs, which allows us to clear the 
+    # cache at the earliest possible time while leaving a trace of which 
+    # transactions we were processing, so the UI can bring the user back
+    # to the payment form w/ the same xacts if the payment fails.
+
+    my $refresh = "1; url=main_pay/$token?xact=" . pop(@payment_xacts);
+    $refresh .= ";xact=$_" for @payment_xacts;
+    $self->ctx->{refresh} = $refresh;
+
+    return Apache2::Const::OK;
+}
+
+# retrieve the cached CC payment info and send off for processing
+sub load_myopac_pay {
+    my $self = shift;
+    my $token = $self->ctx->{page_args}->[0];
+    return Apache2::Const::HTTP_BAD_REQUEST unless $token;
+
+    my $cache = OpenSRF::Utils::Cache->new('global');
+    my $cache_args = $cache->get_cache($token);
+    $cache->delete_cache($token);
+
+    # this page is loaded immediately after the token is created.
+    # if the cached data is not there, it's because of an invalid
+    # token (or cache failure) and not because of a timeout.
+    return Apache2::Const::HTTP_BAD_REQUEST unless $cache_args;
+
+    my @payment_xacts = @{$cache_args->{xacts}};
+    my $cc_args = $cache_args->{cc_args};
+
+    # as an added security check, verify the user submitting 
+    # the form is the same as the user whose data was cached
+    return Apache2::Const::HTTP_BAD_REQUEST unless
+        $cache_args->{user} == $self->ctx->{user}->id;
+
+    $logger->info("tpac paying fines with token $token and xacts [@payment_xacts]");
+
+    my $r;
+    $r = $self->prepare_fines(undef, undef, \@payment_xacts) and return $r;
+
+    # balance_owed is computed specifically from the fines we're paying
+    if ($self->ctx->{fines}->{balance_owed} <= 0) {
+        $logger->info("tpac can't pay non-positive balance. xacts selected: [@payment_xacts]");
+        return Apache2::Const::HTTP_BAD_REQUEST;
+    }
+
     my $args = {
         "cc_args" => $cc_args,
         "userid" => $self->ctx->{user}->id,
         "payment_type" => "credit_card_payment",
-        "payments" => $self->prepare_fines_for_payment   # should be safe after self->prepare_fines
+        "payments" => $self->prepare_fines_for_payment  # should be safe after self->prepare_fines
     };
 
     my $resp = $U->simplereq("open-ils.circ", "open-ils.circ.money.payment",
index 7c34682..e02d956 100644 (file)
@@ -4,6 +4,7 @@
     myopac_page = "main";
     myopac_main_page = "pay" %]
 <div id="myopac_summary_div">
+
     [% IF ctx.payment_response.textcode %]
         <div class="payment-error">
             <span title="[% ctx.payment_response.textcode %]">
@@ -22,6 +23,7 @@
                 END;
                 retry_url =  mkurl(ctx.opac_root _ '/myopac/main_payment_form', url_args, 1);
             %]
+            <br/>
             <a href="[% retry_url %]">[% l('Go back') %]</a>
             [% l('to try again or to cancel this payment attempt.') %]
         </p>
diff --git a/Open-ILS/src/templates/opac/myopac/main_pay_init.tt2 b/Open-ILS/src/templates/opac/myopac/main_pay_init.tt2
new file mode 100644 (file)
index 0000000..8355710
--- /dev/null
@@ -0,0 +1,15 @@
+[%  PROCESS "opac/parts/header.tt2";
+    PROCESS "opac/parts/misc_util.tt2";
+    WRAPPER "opac/parts/myopac/base.tt2";
+    myopac_page = "main";
+    myopac_main_page = "pay" %]
+<div id="myopac_summary_div">
+
+    <div class="payment-processing">
+        [% l('Processing...') %] <br/><br/>
+        [% l('Processing your payment may take some time.') %]<br/>
+        [% l("Please do not Refresh or use your browser's Back button or your credit card may be charged more than once.") %]<br/>
+    </div>
+
+</div>
+[% END %]
index cb4d4a1..3f152b1 100644 (file)
@@ -16,7 +16,7 @@
 <div id="pay_fines_now">
     [% IF last_chance %]
     <p><big>[% l("Are you sure you are ready to charge [_1] to your credit card?", money(ctx.fines.balance_owed)) %]</big></p>
-    <form action="[% ctx.opac_root %]/myopac/main_pay" method="POST">
+    <form action="[% ctx.opac_root %]/myopac/main_pay_init" method="POST">
         [% FOR k IN CGI.Vars;
             NEXT UNLESS k;
             FOR val IN CGI.param(k) %]
index 10dacd9..0279eea 100644 (file)
@@ -960,6 +960,13 @@ div.result_place_hold {
     padding: 10px; border: 1px solid #888;
 }
 
+.payment-processing {
+    font-weight: bold; color: green;
+    font-size: 120%;
+    padding: 10px; border: 1px solid #888;
+    text-align: center;
+}
+
 #adv_search_parent {
        margin-bottom:0px;
 }