LP#1622696 Webstaff credit card payment support
authorBill Erickson <berickxx@gmail.com>
Wed, 20 Sep 2017 22:16:57 +0000 (18:16 -0400)
committerMike Rylander <mrylander@gmail.com>
Tue, 26 Sep 2017 19:03:35 +0000 (15:03 -0400)
Support collecting credit card info for both EG-processed and
externally-processed credit card payments, consistent with the XUL
client.

Refactor the chain of events leading up to payment submission,
including verifying warning-level payment amounts, annotating payments,
and collecting credit card data so the user is able to perform any or
all of these steps and cleanly cancel at any point along the way.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

Open-ILS/src/templates/staff/circ/patron/t_cc_payment_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/css/style.css.tt2
Open-ILS/web/js/ui/default/staff/circ/patron/bills.js

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_cc_payment_dialog.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_cc_payment_dialog.tt2
new file mode 100644 (file)
index 0000000..44ee85a
--- /dev/null
@@ -0,0 +1,127 @@
+<div class="modal-header">
+  <button type="button" class="close" 
+    ng-click="cancel()" aria-hidden="true">&times;</button>
+  <h4 class="modal-title">
+    [% l('Credit Card Information') %]
+  </h4>
+</div>
+<div class="modal-body tight-vert-form">
+  <div class="panel panel-default">
+    <div class="panel-heading">[% l('Credit Card Info') %]</div>
+    <div class="panel-body">
+      <div class="row form-group">
+        <div class="col-md-4"><label>[% l('Process Where?') %]</label></div>
+        <div class="col-md-8">
+          <select class="form-control" ng-model="context.cc.where_process">
+            <option value='1'>
+              [% l('Process payment through Evergreen') %]</option>
+            <option value='0'>
+              [% l('Record externally processed payment') %]</option>
+          </select>
+        </div>
+      </div>
+      <!-- wrapper div for internal CC processing fields -->
+      <div ng-if="context.cc.where_process == '1'">
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Credit Card Number') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" ng-model="context.cc.number"/>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Expire Month') %]</label></div>
+          <div class="col-md-8">
+            <input type='number' class="form-control" ng-model="context.cc.expire_month"/>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Expire Year') %]</label></div>
+          <div class="col-md-8">
+            <input type='number' class="form-control" ng-model="context.cc.expire_year"/>
+          </div>
+        </div>
+      </div><!--cc-internal-wrapper-->
+      <div ng-if="context.cc.where_process == '0'">
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Credit Card Type') %]</label></div>
+          <div class="col-md-8">
+            <select class="form-control" ng-model="context.cc.type">
+              <option value='VISA'>[% l('VISA') %]</option>
+              <option value='MasterCard'>[% l('Mastercard') %]</option>
+              <option value='American Express'>[% l('American Express') %]</option>
+              <option value='Discover'>[% l('Discover') %]</option>
+              <option value='Other'>[% l('Other') %]</option>
+            </select>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Approval Code') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" ng-model="context.cc.approval_code"/>
+          </div>
+        </div>
+      </div><!--cc-external-wrapper-->
+    </div><!--panel-body-->
+  </div><!--panel-->
+  <div class="panel panel-default">
+    <div class="panel-heading">[% l('Optional Fields') %]</div>
+    <div class="panel-body">
+      <div class="row form-group">
+        <div class="col-md-4"><label>[% l('Billing Name (first)') %]</label></div>
+        <div class="col-md-8">
+          <input type='text' class="form-control" 
+            ng-model="context.cc.billing_first"/>
+        </div>
+      </div>
+      <div class="row form-group">
+        <div class="col-md-4"><label>[% l('Billing Name (last)') %]</label></div>
+        <div class="col-md-8">
+          <input type='text' class="form-control" 
+            ng-model="context.cc.billing_last"/>
+        </div>
+      </div>
+      <div ng-if="context.cc.where_process == '1'">
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('Address') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" 
+              ng-model="context.cc.billing_address"/>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('City, town or village') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" 
+              ng-model="context.cc.billing_city"/>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('State or province') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" 
+              ng-model="context.cc.billing_state"/>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-md-4"><label>[% l('ZIP or postal code') %]</label></div>
+          <div class="col-md-8">
+            <input type='text' class="form-control" 
+              ng-model="context.cc.billing_zip"/>
+          </div>
+        </div>
+      </div><!--cc-internal-wrapper-->
+      <div class="row form-group">
+        <div class="col-md-4"><label>[% l('Note') %]</label></div>
+        <div class="col-md-8">
+          <textarea rows="2" type='text' class="form-control" 
+            ng-model="context.cc.note"></textarea>
+        </div>
+      </div>
+    </div><!--panel-body-->
+  </div><!--panel-->
+</div><!--modal-body-->
+<div class="modal-footer">
+  <button class="btn btn-primary" ng-click="ok()">[% l('Submit') %]</button>
+  <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+</div>
+
index 2369cd4..e5a661a 100644 (file)
@@ -205,6 +205,13 @@ table.list tr.selected td { /* deprecated? */
   background-color: rgb(248, 248, 248);
 }
 
+/* Reduces the vertical space added by form-group's.  Especially useful
+ * for tall modal windows.
+ */
+.tight-vert-form .form-group {
+  margin-bottom: 5px;
+} 
+
 
 /* ----------------------------------------------------------------------
  * Grid
index c648def..4fa6398 100644 (file)
@@ -24,7 +24,7 @@ function($q , egCore , egWorkLog , patronSvc) {
         .then(function(summary) {return service.summary = summary})
     }
 
-    service.applyPayment = function(type, payments, note, check) {
+    service.applyPayment = function(type, payments, note, check, cc_args) {
         return egCore.net.request(
             'open-ils.circ',
             'open-ils.circ.money.payment',
@@ -34,7 +34,8 @@ function($q , egCore , egWorkLog , patronSvc) {
                 payment_type : type,
                 check_number : check,
                 payments : payments,
-                patron_credit : 0
+                patron_credit : 0,
+                cc_args : cc_args
             },
             patronSvc.current.last_xact_id()
         ).then(function(resp) {
@@ -143,10 +144,10 @@ function($q , egCore , egWorkLog , patronSvc) {
 .controller('PatronBillsCtrl',
        ['$scope','$q','$routeParams','egCore','egConfirmDialog','$location',
         'egGridDataProvider','billSvc','patronSvc','egPromptDialog', 'egAlertDialog',
-        'egBilling',
+        'egBilling','$uibModal',
 function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
          egGridDataProvider , billSvc , patronSvc , egPromptDialog, egAlertDialog,
-         egBilling) {
+         egBilling , $uibModal) {
 
     $scope.initTab('bills', $routeParams.id);
     billSvc.userId = $routeParams.id;
@@ -316,10 +317,10 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
 
     // generates payments, collects user note if needed, and sends payment
     // to server.
-    function sendPayment(note) {
+    function sendPayment(note, cc_args) {
         var make_payments = generatePayments();
-        billSvc.applyPayment(
-            $scope.payment_type, make_payments, note, $scope.check_number)
+        billSvc.applyPayment($scope.payment_type, 
+            make_payments, note, $scope.check_number, cc_args)
         .then(function(payment_ids) {
 
             if (!$scope.disable_auto_print && $scope.receipt_on_pay.isChecked) {
@@ -498,32 +499,78 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
             return;
         }
 
-        if (($scope.payment_amount > $scope.warn_amount) && ($scope.amount_verified == false)) {
-            egConfirmDialog.open(
-                egCore.strings.PAYMENT_WARN_AMOUNT_TITLE, egCore.strings.PAYMENT_WARN_AMOUNT,
-                {   payment_amount : ''+$scope.payment_amount,
-                    ok : function() {
-                        $scope.amount_verfied = true;
-                        $scope.applyPayment();
-                    },
-                    cancel : function() {
-                        $scope.payment_amount = 0;
+        verify_payment_amount().then(
+            function() { // amount confirmed
+                add_payment_note().then(function(pay_note) {
+                    add_cc_args().then(function(cc_args) {
+                        sendPayment(pay_note, cc_args);
+                    })
+                });
+            },
+            function() { // amount rejected
+                console.warn('payment amount rejected');
+                $scope.payment_amount = 0;
+            }
+        );
+    }
+
+    function verify_payment_amount() {
+        if ($scope.payment_amount < $scope.warn_amount)
+            return $q.when();
+
+        return egConfirmDialog.open(
+            egCore.strings.PAYMENT_WARN_AMOUNT_TITLE, 
+            egCore.strings.PAYMENT_WARN_AMOUNT,
+            {payment_amount : ''+$scope.payment_amount}
+        ).result;
+    }
+
+    function add_payment_note() {
+        if (!$scope.annotate_payment) return $q.when();
+        return egPromptDialog.open(
+            egCore.strings.ANNOTATE_PAYMENT_MSG, '').result;
+    }
+
+    function add_cc_args() {
+        if ($scope.payment_type != 'credit_card_payment') 
+            return $q.when();
+
+        return $uibModal.open({
+            templateUrl : './circ/patron/t_cc_payment_dialog',
+            controller : [
+                        '$scope','$uibModalInstance',
+                function($scope , $uibModalInstance) {
+
+                    $scope.context = {
+                        cc : {
+                            where_process : '1', // internal=1 ; external=0
+                            type : 'VISA', // external only
+                            billing_first : patronSvc.current.first_given_name(),
+                            billing_last : patronSvc.current.family_name()
+                        }
                     }
-                }
-            );
-            return;
-        }
 
-        $scope.amount_verfied = false;
+                    var addr = patronSvc.current.billing_address() ||
+                        patronSvc.current.mailing_address();
+                    if (addr) {
+                        var cc = $scope.context.cc;
+                        cc.billing_address = addr.street1() + 
+                            (addr.street2() ? ' ' + addr.street2() : '');
+                        cc.billing_city = addr.city();
+                        cc.billing_state = addr.state();
+                        cc.billing_zip = addr.post_code();
+                    }
 
-        if ($scope.annotate_payment) {
-            egPromptDialog.open(
-                egCore.strings.ANNOTATE_PAYMENT_MSG, '',
-                {ok : function(value) {sendPayment(value)}}
-            );
-        } else {
-            sendPayment();
-        }
+                    $scope.ok = function() {
+                        $uibModalInstance.close($scope.context.cc);
+                    }
+
+                    $scope.cancel = function() {
+                        $uibModalInstance.dismiss();
+                    }
+                }
+            ]
+        }).result;
     }
 
     $scope.voidAllBillings = function(items) {