Patch from Lebbeous Fogle-Weekley to fine-tune credit card payment support in the...
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 27 Oct 2009 14:44:20 +0000 (14:44 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 27 Oct 2009 14:44:20 +0000 (14:44 +0000)
This adds support for differentiating between in-band (using credit card processor) and
out-of-band (using external CC mechamism) in the staff client and payment entries

git-svn-id: svn://svn.open-ils.org/ILS/trunk@14628 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/extras/ils_events.xml
Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm
Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/080.schema.money.sql
Open-ILS/src/sql/Pg/upgrade/0057.mccp_processor_column.sql [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
Open-ILS/xul/staff_client/server/patron/bill_cc_info.xul

index ad61875..e3c8d3f 100644 (file)
@@ -1294,6 +1294,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field name="cash_drawer" reporter:datatype="link"/>
                        <field name="cc_number" reporter:datatype="text"/>
                        <field name="cc_type" reporter:datatype="text"/>
+                       <field name="cc_processor" reporter:datatype="text"/>
                        <field name="expire_month" reporter:datatype="int" />
                        <field name="expire_year" reporter:datatype="int" />
                        <field name="id" reporter:datatype="id" />
index 56c40da..4f189fb 100644 (file)
        <event code='4020' textcode='CREDIT_PROCESSOR_DECLINED_TRANSACTION'>
                <desc xml:lang="en-US">The credit card processor has declined the transaction.</desc>
        </event>
+       <event code='4040' textcode='CREDIT_PROCESSOR_SUCCESS_WO_RECORD'>
+        <desc xml:lang="en-US">A *TERRIBLE* problem has occurred: a credit
+            card transaction was processed successfuly, but the patron's
+            payment could not be recorded within Evergreen.  Please seek
+            assistance.</desc>
+       </event>
 
 
        <event code='5000' textcode='PERM_FAILURE'>
index 2aa3240..778055d 100644 (file)
@@ -13,7 +13,6 @@
 # GNU General Public License for more details.
 # ---------------------------------------------------------------
 
-
 package OpenILS::Application::Circ::Money;
 use base qw/OpenILS::Application/;
 use strict; use warnings;
@@ -30,12 +29,12 @@ use OpenILS::Utils::CStoreEditor qw/:funcs/;
 use OpenILS::Utils::Penalty;
 
 __PACKAGE__->register_method(
-       method => "make_payments",
-       api_name => "open-ils.circ.money.payment",
+    method => "make_payments",
+    api_name => "open-ils.circ.money.payment",
     signature => {
         desc => q/Create payments for a given user and set of transactions,
-               login must have CREATE_PAYMENT priveleges.
-               If any payments fail, all are reverted back./,
+            login must have CREATE_PAYMENT privileges.
+            If any payments fail, all are reverted back./,
         params => [
             {desc => 'Authtoken', type => 'string'},
             {desc => q/Arguments Hash, supporting the following params:
@@ -44,13 +43,15 @@ __PACKAGE__->register_method(
                     userid
                     patron_credit
                     note
-                    cc_args : {
-                        type
-                        number
-                        expire_month
-                        expire_year
-                        approval_code
-                    }
+                    cc_args: {
+                        where_process   1 to use processor, !1 for out-of-band
+                        approval_code   (for out-of-band payment)
+                        type            (for out-of-band payment)
+                        number          (for call to payment processor)
+                        expire_month    (for call to payment processor)
+                        expire_year     (for call to payment processor)
+                        note            (if payments->{note} is blank, use this)
+                    },
                     check_number
                     payments: [ 
                         [trans_id, amt], 
@@ -61,36 +62,38 @@ __PACKAGE__->register_method(
         ]
     }
 );
-
 sub make_payments {
-       my($self, $client, $auth, $payments) = @_;
+    my($self, $client, $auth, $payments) = @_;
 
-       my $e = new_editor(authtoken => $auth, xact => 1);
+    my $e = new_editor(authtoken => $auth, xact => 1);
     return $e->die_event unless $e->checkauth;
 
-       my $type = $payments->{payment_type};
-       my $user_id = $payments->{userid};
-       my $credit = $payments->{patron_credit} || 0;
-       my $drawer = $e->requestor->wsid;
-       my $note = $payments->{note};
-       my $check_number = $payments->{check_number};
+    my $type = $payments->{payment_type};
+    my $user_id = $payments->{userid};
+    my $credit = $payments->{patron_credit} || 0;
+    my $drawer = $e->requestor->wsid;
+    my $note = $payments->{note};
     my $cc_args = $payments->{cc_args};
-       my $total_paid = 0;
+    my $check_number = $payments->{check_number};
+    my $total_paid = 0;
+    my $this_ou = $e->requestor->ws_ou;
     my %orgs;
 
+    # unless/until determined by payment processor API
+    my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
+
     my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
 
     # A user is allowed to make credit card payments on his/her own behalf
     # All other scenarious require permission
     unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
-           return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
+        return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
     }
 
     # first collect the transactions and make sure the transaction
     # user matches the requested user
     my %xacts;
     for my $pay (@{$payments->{payments}}) {
-
         my $xact_id = $pay->[0];
         my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
             or return $e->die_event;
@@ -105,379 +108,442 @@ sub make_payments {
 
     my @payment_objs;
 
-       for my $pay (@{$payments->{payments}}) {
-
+    for my $pay (@{$payments->{payments}}) {
         my $transid = $pay->[0];
-               my $amount = $pay->[1];
-               $amount =~ s/\$//og; # just to be safe
+        my $amount = $pay->[1];
+        $amount =~ s/\$//og; # just to be safe
         my $trans = $xacts{$transid};
 
-               $total_paid += $amount;
+        $total_paid += $amount;
 
         $orgs{$U->xact_org($transid, $e)} = 1;
 
-        # making payment with existing patron credit
-               $credit -= $amount if $type eq 'credit_payment';
-
-               # A negative payment is a refund.  
-               if( $amount < 0 ) {
+        # A negative payment is a refund.  
+        if( $amount < 0 ) {
 
             # Negative credit card payments are not allowed
             if($type eq 'credit_card_payment') {
                 $e->rollback;
-                               return OpenILS::Event->new(
+                return OpenILS::Event->new(
                     'BAD_PARAMS', 
                     note => q/Negative credit card payments not allowed/
                 );
             }
 
-                       # If the refund causes the transaction balance to exceed 0 dollars, 
-                       # we are in effect loaning the patron money.  This is not allowed.
-                       if( ($trans->balance_owed - $amount) > 0 ) {
+            # If the refund causes the transaction balance to exceed 0 dollars, 
+            # we are in effect loaning the patron money.  This is not allowed.
+            if( ($trans->balance_owed - $amount) > 0 ) {
                 $e->rollback;
-                               return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
-                       }
+                return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
+            }
 
-                       # Otherwise, make sure the refund does not exceed desk payments
-                       # This is also not allowed
-                       my $desk_total = 0;
-                       my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
-                       $desk_total += $_->amount for @$desk_payments;
+            # Otherwise, make sure the refund does not exceed desk payments
+            # This is also not allowed
+            my $desk_total = 0;
+            my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
+            $desk_total += $_->amount for @$desk_payments;
 
-                       if( (-$amount) > $desk_total ) {
+            if( (-$amount) > $desk_total ) {
                 $e->rollback;
-                               return OpenILS::Event->new(
-                                       'REFUND_EXCEEDS_DESK_PAYMENTS', 
-                                       payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
-                       }
-               }
-
-               my $payobj = "Fieldmapper::money::$type";
-               $payobj = $payobj->new;
-
-               $payobj->amount($amount);
-               $payobj->amount_collected($amount);
-               $payobj->xact($transid);
-               $payobj->note($note);
-
-               if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
-               if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
-               if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
-               if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
-
-        # Store the last 4 digits?
-               #if ($payobj->has_field('cc_number')) { $payobj->cc_number($cc_args->{number}); }
-               #if ($payobj->has_field('approval_code')) { $payobj->approval_code($cc_args->{approval_code}); }
-               if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
-               if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
-               
-               # update the transaction if it's done 
-               if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
-
-                       # Any overpay on this transaction goes directly into patron credit 
-                       $cred = -$cred;
-                       $credit += $cred;
-            my $circ = $e->retrieve_action_circulation($transid);
+                return OpenILS::Event->new(
+                    'REFUND_EXCEEDS_DESK_PAYMENTS', 
+                    payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
+            }
+        }
 
-                       if(!$circ || $circ->stop_fines) {
-                           # If this is a circulation, we can't close the transaction unless stop_fines is set
-                $trans = $e->retrieve_money_billable_transaction($transid);
-                               $trans->xact_finish("now");
-                $e->update_money_billable_transaction($trans) or return $e->die_event;
-                       }
-               }
+        my $payobj = "Fieldmapper::money::$type";
+        $payobj = $payobj->new;
 
-        my $method = "create_money_$type";
-        $e->$method($payobj) or return $e->die_event;
-        push(@payment_objs, $payobj);
+        $payobj->amount($amount);
+        $payobj->amount_collected($amount);
+        $payobj->xact($transid);
+        $payobj->note($note);
+        if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
+            $payobj->note($cc_args->{note});
+        }
 
-       } # all payment objects have been created and inserted. 
+        if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
+        if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
+        if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
+        if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
 
-       my $evt = _update_patron_credit($e, $patron, $credit);
-       return $evt if $evt;
+        # Store the last 4 digits of the CC number
+        if ($payobj->has_field('cc_number')) {
+            $payobj->cc_number(substr($cc_args->{number}, -4));
+        }
+        if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
+        if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
+        
+        # Note: It is important not to set approval_code
+        # on the fieldmapper object yet.
 
-    for my $org_id (keys %orgs) {
-        # calculate penalties for each of the affected orgs
-        $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
-        return $evt if $evt;
-    }
+        push(@payment_objs, $payobj);
+
+    } # all payment objects have been created and inserted. 
 
+    #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED  ###
+    $e->rollback;
+
+    # After we try to externally process a credit card (if desired), we'll
+    # open a new transaction.  We cannot leave one open while credit card
+    # processing might be happening, as it can easily time out the database
+    # transaction.
     if($type eq 'credit_card_payment') {
-        my $this_ou = $e->requestor->ws_ou;
-        my $response = $apputils->simplereq(
-            'open-ils.credit',
-            'open-ils.credit.process',
-            {
-                "desc" => $payments->{note},
-                "amount" => $total_paid,
-                "patron_id" => $user_id,
-                "cc" => $payments->{cc_number},
-                "expiration" => sprintf(
-                    "%02d-%04d",
-                    $payments->{expire_month},
-                    $payments->{expire_year}
-                ),
-                "ou" => $this_ou
+        $approval_code = $cc_args->{approval_code};
+        # If an approval code was not given, we'll need
+        # to call to the payment processor ourselves.
+        if ($cc_args->{where_process} == 1) {
+            return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
+                if not $cc_args->{number};
+            my $response = $apputils->simplereq(
+                'open-ils.credit',
+                'open-ils.credit.process',
+                {
+                    "desc" => $cc_args->{note},
+                    "amount" => $total_paid,
+                    "patron_id" => $user_id,
+                    "cc" => $cc_args->{number},
+                    "expiration" => sprintf(
+                        "%02d-%04d",
+                        $cc_args->{expire_month},
+                        $cc_args->{expire_year}
+                    ),
+                    "ou" => $this_ou
+                }
+            );
+
+            if (exists $response->{ilsevent}) {
+                return $response;
             }
-        );
+            if ($response->{statusCode} != 200) {
+                $logger->info("Credit card payment for user $user_id " .
+                    "failed with message: " . $response->{statusText});
+                return OpenILS::Event->new(
+                    'CREDIT_PROCESSOR_DECLINED_TRANSACTION',
+                    note => $response->{statusText}
+                );
+            }
+            $approval_code = $response->{approvalCode};
+            $cc_type = $response->{cardType};
+            $cc_processor = $response->{processor};
+            $logger->info("Credit card payment processing for " .
+                "user $user_id succeeded");
+        }
+        else {
+            return OpenILS::Event->new(
+                'BAD_PARAMS', note => 'Need approval code'
+            ) if not $cc_args->{approval_code};
+        }
+    }
 
-        if (exists $response->{ilsevent}) {
-            $e->rollback;
-            return $response;
+    ### RE-OPEN TRANSACTION HERE ###
+    $e->xact_begin;
+
+    # create payment records
+    my $create_money_method = "create_money_" . $type;
+    for my $payment (@payment_objs) {
+        # update the transaction if it's done
+        my $amount = $payment->amount;
+        my $transid = $payment->xact;
+        my $trans = $xacts{$transid};
+        if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
+            # Any overpay on this transaction goes directly into patron
+            # credit making payment with existing patron credit.
+            $credit -= $amount if $type eq 'credit_payment';
+
+            $cred = -$cred;
+            $credit += $cred;
+            my $circ = $e->retrieve_action_circulation($transid);
+
+            if(!$circ || $circ->stop_fines) {
+                # If this is a circulation, we can't close the transaction
+                # unless stop_fines is set.
+                $trans = $e->retrieve_money_billable_transaction($transid);
+                $trans->xact_finish("now");
+                if (!$e->update_money_billable_transaction($trans)) {
+                    $logger->warn("update_money_billable_transaction() " .
+                        "failed");
+                    $e->rollback;
+                    return OpenILS::Event->new(
+                        'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
+                        note => 'update_money_billable_transaction() failed'
+                    );
+                }
+            }
         }
-        if ($response->{statusCode} != 200) {
+
+        $payment->approval_code($approval_code) if $approval_code;
+        $payment->cc_type($cc_type) if $cc_type;
+        $payment->cc_processor($cc_processor) if $cc_processor;
+        if (!$e->$create_money_method($payment)) {
+            $logger->warn("$create_money_method failed: " .
+                Dumper($payment)); # won't contain CC number.
             $e->rollback;
-            $logger->info("Credit card payment for user $user_id failed with message: " . $response->{statusText});
             return OpenILS::Event->new(
-                'CREDIT_PROCESSOR_DECLINED_TRANSACTION',
-                note => $response->{statusText}
+                'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
+                note => "$create_money_method failed"
             );
         }
+    }
 
-        for my $payment (@payment_objs) {
-            $payment->approval_code($response->{approvalCode});
-            $e->update_money_credit_card_payment($payment) or return $e->die_event;
+    my $evt = _update_patron_credit($e, $patron, $credit);
+    if ($evt) {
+        $logger->warn("_update_patron_credit() failed");
+        $e->rollback;
+        return OpenILS::Event->new(
+            'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
+            note => "_update_patron_credit() failed"
+        );
+    }
+
+    for my $org_id (keys %orgs) {
+        # calculate penalties for each of the affected orgs
+        $evt = OpenILS::Utils::Penalty->calculate_penalties(
+            $e, $user_id, $org_id
+        );
+        if ($evt) {
+            $logger->warn(
+                "OpenILS::Utils::Penalty::calculate_penalties() failed"
+            );
+            $e->rollback;
+            return OpenILS::Event->new(
+                'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
+                note => "OpenILS::Utils::Penalty::calculate_penalties() failed"
+            );
         }
-        $logger->info("Credit card payment for user $user_id succeeded");
     }
 
     $e->commit;
     return 1;
 }
 
-
 sub _update_patron_credit {
-       my($e, $patron, $credit) = @_;
+    my($e, $patron, $credit) = @_;
     return undef if $credit == 0;
-       $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
+    $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
     return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
     $e->update_actor_user($patron) or return $e->die_event;
-       return undef;
+    return undef;
 }
 
 
 __PACKAGE__->register_method(
-       method  => "retrieve_payments",
-       api_name        => "open-ils.circ.money.payment.retrieve.all_",
-       notes           => "Returns a list of payments attached to a given transaction"
-       );
-       
+    method    => "retrieve_payments",
+    api_name    => "open-ils.circ.money.payment.retrieve.all_",
+    notes        => "Returns a list of payments attached to a given transaction"
+    );
 sub retrieve_payments {
-       my( $self, $client, $login, $transid ) = @_;
+    my( $self, $client, $login, $transid ) = @_;
 
-       my( $staff, $evt ) =  
-               $apputils->checksesperm($login, 'VIEW_TRANSACTION');
-       return $evt if $evt;
+    my( $staff, $evt ) =  
+        $apputils->checksesperm($login, 'VIEW_TRANSACTION');
+    return $evt if $evt;
 
-       # XXX the logic here is wrong.. we need to check the owner of the transaction
-       # to make sure the requestor has access
+    # XXX the logic here is wrong.. we need to check the owner of the transaction
+    # to make sure the requestor has access
 
-       # XXX grab the view, for each object in the view, grab the real object
+    # XXX grab the view, for each object in the view, grab the real object
 
-       return $apputils->simplereq(
-               'open-ils.cstore',
-               'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
+    return $apputils->simplereq(
+        'open-ils.cstore',
+        'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
 }
 
 
-
 __PACKAGE__->register_method(
-       method  => "retrieve_payments2",
+    method    => "retrieve_payments2",
     authoritative => 1,
-       api_name        => "open-ils.circ.money.payment.retrieve.all",
-       notes           => "Returns a list of payments attached to a given transaction"
-       );
-       
+    api_name    => "open-ils.circ.money.payment.retrieve.all",
+    notes        => "Returns a list of payments attached to a given transaction"
+    );
+    
 sub retrieve_payments2 {
-       my( $self, $client, $login, $transid ) = @_;
-
-       my $e = new_editor(authtoken=>$login);
-       return $e->event unless $e->checkauth;
-       return $e->event unless $e->allowed('VIEW_TRANSACTION');
-
-       my @payments;
-       my $pmnts = $e->search_money_payment({ xact => $transid });
-       for( @$pmnts ) {
-               my $type = $_->payment_type;
-               my $meth = "retrieve_money_$type";
-               my $p = $e->$meth($_->id) or return $e->event;
-               $p->payment_type($type);
-               $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
-                       if $p->has_field('cash_drawer');
-               push( @payments, $p );
-       }
-
-       return \@payments;
-}
+    my( $self, $client, $login, $transid ) = @_;
+
+    my $e = new_editor(authtoken=>$login);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION');
+
+    my @payments;
+    my $pmnts = $e->search_money_payment({ xact => $transid });
+    for( @$pmnts ) {
+        my $type = $_->payment_type;
+        my $meth = "retrieve_money_$type";
+        my $p = $e->$meth($_->id) or return $e->event;
+        $p->payment_type($type);
+        $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
+            if $p->has_field('cash_drawer');
+        push( @payments, $p );
+    }
 
+    return \@payments;
+}
 
 
 __PACKAGE__->register_method(
-       method  => "create_grocery_bill",
-       api_name        => "open-ils.circ.money.grocery.create",
-       notes           => <<"  NOTE");
-       Creates a new grocery transaction using the transaction object provided
-       PARAMS: (login_session, money.grocery (mg) object)
-       NOTE
+    method    => "create_grocery_bill",
+    api_name    => "open-ils.circ.money.grocery.create",
+    notes        => <<"    NOTE");
+    Creates a new grocery transaction using the transaction object provided
+    PARAMS: (login_session, money.grocery (mg) object)
+    NOTE
 
 sub create_grocery_bill {
-       my( $self, $client, $login, $transaction ) = @_;
+    my( $self, $client, $login, $transaction ) = @_;
 
-       my( $staff, $evt ) = $apputils->checkses($login);
-       return $evt if $evt;
-       $evt = $apputils->check_perms($staff->id, 
-               $transaction->billing_location, 'CREATE_TRANSACTION' );
-       return $evt if $evt;
+    my( $staff, $evt ) = $apputils->checkses($login);
+    return $evt if $evt;
+    $evt = $apputils->check_perms($staff->id, 
+        $transaction->billing_location, 'CREATE_TRANSACTION' );
+    return $evt if $evt;
 
 
-       $logger->activity("Creating grocery bill " . Dumper($transaction) );
+    $logger->activity("Creating grocery bill " . Dumper($transaction) );
 
-       $transaction->clear_id;
-       my $session = $apputils->start_db_session;
-       my $transid = $session->request(
-               'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
+    $transaction->clear_id;
+    my $session = $apputils->start_db_session;
+    my $transid = $session->request(
+        'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
 
-       throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
+    throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
 
-       $logger->debug("Created new grocery transaction $transid");
-       
-       $apputils->commit_db_session($session);
+    $logger->debug("Created new grocery transaction $transid");
+    
+    $apputils->commit_db_session($session);
 
     my $e = new_editor(xact=>1);
     $evt = _check_open_xact($e, $transid);
     return $evt if $evt;
     $e->commit;
 
-       return $transid;
+    return $transid;
 }
 
 
 __PACKAGE__->register_method(
-       method => 'fetch_grocery',
-       api_name => 'open-ils.circ.money.grocery.retrieve'
+    method => 'fetch_grocery',
+    api_name => 'open-ils.circ.money.grocery.retrieve'
 );
-
 sub fetch_grocery {
-       my( $self, $conn, $auth, $id ) = @_;
-       my $e = new_editor(authtoken=>$auth);
-       return $e->event unless $e->checkauth;
-       return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
-       my $g = $e->retrieve_money_grocery($id)
-               or return $e->event;
-       return $g;
+    my( $self, $conn, $auth, $id ) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
+    my $g = $e->retrieve_money_grocery($id)
+        or return $e->event;
+    return $g;
 }
 
 
 __PACKAGE__->register_method(
-       method  => "billing_items",
+    method    => "billing_items",
     authoritative => 1,
-       api_name        => "open-ils.circ.money.billing.retrieve.all",
-       notes           =><<"   NOTE");
-       Returns a list of billing items for the given transaction.
-       PARAMS( login, transaction_id )
-       NOTE
+    api_name    => "open-ils.circ.money.billing.retrieve.all",
+    notes        =><<"    NOTE");
+    Returns a list of billing items for the given transaction.
+    PARAMS( login, transaction_id )
+    NOTE
 
 sub billing_items {
-       my( $self, $client, $login, $transid ) = @_;
-
-       my( $trans, $evt ) = $U->fetch_billable_xact($transid);
-       return $evt if $evt;
-
-       my $staff;
-       ($staff, $evt ) = $apputils->checkses($login);
-       return $evt if $evt;
-
-       if($staff->id ne $trans->usr) {
-               $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
-               return $evt if $evt;
-       }
-       
-       return $apputils->simplereq( 'open-ils.cstore',
-               'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
+    my( $self, $client, $login, $transid ) = @_;
+
+    my( $trans, $evt ) = $U->fetch_billable_xact($transid);
+    return $evt if $evt;
+
+    my $staff;
+    ($staff, $evt ) = $apputils->checkses($login);
+    return $evt if $evt;
+
+    if($staff->id ne $trans->usr) {
+        $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
+        return $evt if $evt;
+    }
+    
+    return $apputils->simplereq( 'open-ils.cstore',
+        'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
 }
 
 
 __PACKAGE__->register_method(
-       method  => "billing_items_create",
-       api_name        => "open-ils.circ.money.billing.create",
-       notes           =><<"   NOTE");
-       Creates a new billing line item
-       PARAMS( login, bill_object (mb) )
-       NOTE
+    method    => "billing_items_create",
+    api_name    => "open-ils.circ.money.billing.create",
+    notes        =><<"    NOTE");
+    Creates a new billing line item
+    PARAMS( login, bill_object (mb) )
+    NOTE
 
 sub billing_items_create {
-       my( $self, $client, $login, $billing ) = @_;
+    my( $self, $client, $login, $billing ) = @_;
 
-       my $e = new_editor(authtoken => $login, xact => 1);
-       return $e->die_event unless $e->checkauth;
-       return $e->die_event unless $e->allowed('CREATE_BILL');
+    my $e = new_editor(authtoken => $login, xact => 1);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('CREATE_BILL');
 
-       my $xact = $e->retrieve_money_billable_transaction($billing->xact)
-               or return $e->die_event;
+    my $xact = $e->retrieve_money_billable_transaction($billing->xact)
+        or return $e->die_event;
 
-       # if the transaction was closed, re-open it
-       if($xact->xact_finish) {
-               $xact->clear_xact_finish;
-               $e->update_money_billable_transaction($xact)
-                       or return $e->die_event;
-       }
+    # if the transaction was closed, re-open it
+    if($xact->xact_finish) {
+        $xact->clear_xact_finish;
+        $e->update_money_billable_transaction($xact)
+            or return $e->die_event;
+    }
 
-       my $amt = $billing->amount;
-       $amt =~ s/\$//og;
-       $billing->amount($amt);
+    my $amt = $billing->amount;
+    $amt =~ s/\$//og;
+    $billing->amount($amt);
 
-       $e->create_money_billing($billing) or return $e->die_event;
+    $e->create_money_billing($billing) or return $e->die_event;
     my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
     return $evt if $evt;
-       $e->commit;
+    $e->commit;
 
-       return $billing->id;
+    return $billing->id;
 }
 
+
 __PACKAGE__->register_method(
-       method          =>      'void_bill',
-       api_name                => 'open-ils.circ.money.billing.void',
-       signature       => q/
-               Voids a bill
-               @param authtoken Login session key
-               @param billid Id for the bill to void.  This parameter may be repeated to reference other bills.
-               @return 1 on success, Event on error
-       /
+    method        =>    'void_bill',
+    api_name        => 'open-ils.circ.money.billing.void',
+    signature    => q/
+        Voids a bill
+        @param authtoken Login session key
+        @param billid Id for the bill to void.  This parameter may be repeated to reference other bills.
+        @return 1 on success, Event on error
+    /
 );
-
-
 sub void_bill {
-       my( $s, $c, $authtoken, @billids ) = @_;
+    my( $s, $c, $authtoken, @billids ) = @_;
 
-       my $e = new_editor( authtoken => $authtoken, xact => 1 );
-       return $e->die_event unless $e->checkauth;
-       return $e->die_event unless $e->allowed('VOID_BILLING');
+    my $e = new_editor( authtoken => $authtoken, xact => 1 );
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('VOID_BILLING');
 
     my %users;
     for my $billid (@billids) {
 
-           my $bill = $e->retrieve_money_billing($billid)
-                   or return $e->die_event;
+        my $bill = $e->retrieve_money_billing($billid)
+            or return $e->die_event;
 
         my $xact = $e->retrieve_money_billable_transaction($bill->xact)
             or return $e->die_event;
 
         if($U->is_true($bill->voided)) {
             $e->rollback;
-               return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
+            return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
         }
 
         my $org = $U->xact_org($bill->xact, $e);
         $users{$xact->usr} = {} unless $users{$xact->usr};
         $users{$xact->usr}->{$org} = 1;
 
-           $bill->voided('t');
-           $bill->voider($e->requestor->id);
-           $bill->void_time('now');
+        $bill->voided('t');
+        $bill->voider($e->requestor->id);
+        $bill->void_time('now');
     
-           $e->update_money_billing($bill) or return $e->die_event;
-           my $evt = _check_open_xact($e, $bill->xact, $xact);
-           return $evt if $evt;
+        $e->update_money_billing($bill) or return $e->die_event;
+        my $evt = _check_open_xact($e, $bill->xact, $xact);
+        return $evt if $evt;
     }
 
     # calculate penalties for all user/org combinations
@@ -486,190 +552,176 @@ sub void_bill {
             OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
         }
     }
-
-       $e->commit;
-       return 1;
+    $e->commit;
+    return 1;
 }
 
+
 __PACKAGE__->register_method(
-       method          =>      'edit_bill_note',
-       api_name                => 'open-ils.circ.money.billing.note.edit',
-       signature       => q/
-               Edits the note for a bill
-               @param authtoken Login session key
+    method        =>    'edit_bill_note',
+    api_name        => 'open-ils.circ.money.billing.note.edit',
+    signature    => q/
+        Edits the note for a bill
+        @param authtoken Login session key
         @param note The replacement note for the bills we're editing
-               @param billid Id for the bill to edit the note of.  This parameter may be repeated to reference other bills.
-               @return 1 on success, Event on error
-       /
+        @param billid Id for the bill to edit the note of.  This parameter may be repeated to reference other bills.
+        @return 1 on success, Event on error
+    /
 );
-
-
 sub edit_bill_note {
-       my( $s, $c, $authtoken, $note, @billids ) = @_;
+    my( $s, $c, $authtoken, $note, @billids ) = @_;
 
-       my $e = new_editor( authtoken => $authtoken, xact => 1 );
-       return $e->die_event unless $e->checkauth;
-       return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
+    my $e = new_editor( authtoken => $authtoken, xact => 1 );
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
 
     for my $billid (@billids) {
 
-           my $bill = $e->retrieve_money_billing($billid)
-                   or return $e->die_event;
+        my $bill = $e->retrieve_money_billing($billid)
+            or return $e->die_event;
 
-           $bill->note($note);
+        $bill->note($note);
         # FIXME: Does this get audited?  Need some way so that the original creator of the bill does not get credit/blame for the new note.
     
-           $e->update_money_billing($bill) or return $e->die_event;
+        $e->update_money_billing($bill) or return $e->die_event;
     }
-
-       $e->commit;
-       return 1;
+    $e->commit;
+    return 1;
 }
 
+
 __PACKAGE__->register_method(
-       method          =>      'edit_payment_note',
-       api_name                => 'open-ils.circ.money.payment.note.edit',
-       signature       => q/
-               Edits the note for a payment
-               @param authtoken Login session key
+    method        =>    'edit_payment_note',
+    api_name        => 'open-ils.circ.money.payment.note.edit',
+    signature    => q/
+        Edits the note for a payment
+        @param authtoken Login session key
         @param note The replacement note for the payments we're editing
-               @param paymentid Id for the payment to edit the note of.  This parameter may be repeated to reference other payments.
-               @return 1 on success, Event on error
-       /
+        @param paymentid Id for the payment to edit the note of.  This parameter may be repeated to reference other payments.
+        @return 1 on success, Event on error
+    /
 );
-
-
 sub edit_payment_note {
-       my( $s, $c, $authtoken, $note, @paymentids ) = @_;
+    my( $s, $c, $authtoken, $note, @paymentids ) = @_;
 
-       my $e = new_editor( authtoken => $authtoken, xact => 1 );
-       return $e->die_event unless $e->checkauth;
-       return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
+    my $e = new_editor( authtoken => $authtoken, xact => 1 );
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
 
     for my $paymentid (@paymentids) {
 
-           my $payment = $e->retrieve_money_payment($paymentid)
-                   or return $e->die_event;
+        my $payment = $e->retrieve_money_payment($paymentid)
+            or return $e->die_event;
 
-           $payment->note($note);
+        $payment->note($note);
         # FIXME: Does this get audited?  Need some way so that the original taker of the payment does not get credit/blame for the new note.
     
-           $e->update_money_payment($payment) or return $e->die_event;
+        $e->update_money_payment($payment) or return $e->die_event;
     }
 
-       $e->commit;
-       return 1;
+    $e->commit;
+    return 1;
 }
 
 sub _check_open_xact {
-       my( $editor, $xactid, $xact ) = @_;
+    my( $editor, $xactid, $xact ) = @_;
 
-       # Grab the transaction
-       $xact ||= $editor->retrieve_money_billable_transaction($xactid);
+    # Grab the transaction
+    $xact ||= $editor->retrieve_money_billable_transaction($xactid);
     return $editor->event unless $xact;
     $xactid ||= $xact->id;
 
-       # grab the summary and see how much is owed on this transaction
-       my ($summary) = $U->fetch_mbts($xactid, $editor);
-
-       # grab the circulation if it is a circ;
-       my $circ = $editor->retrieve_action_circulation($xactid);
-
-       # If nothing is owed on the transaction but it is still open
-       # and this transaction is not an open circulation, close it
-       if( 
-               ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
-               ( !$circ or $circ->stop_fines )) {
-
-               $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
-               $xact->xact_finish('now');
-               $editor->update_money_billable_transaction($xact)
-                       or return $editor->event;
-               return undef;
-       }
-
-       # If money is owed or a refund is due on the xact and xact_finish
-       # is set, clear it (to reopen the xact) and update
-       if( $summary->balance_owed != 0 and $xact->xact_finish ) {
-               $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
-               $xact->clear_xact_finish;
-               $editor->update_money_billable_transaction($xact)
-                       or return $editor->event;
-               return undef;
-       }
-
-       return undef;
-}
+    # grab the summary and see how much is owed on this transaction
+    my ($summary) = $U->fetch_mbts($xactid, $editor);
+
+    # grab the circulation if it is a circ;
+    my $circ = $editor->retrieve_action_circulation($xactid);
 
+    # If nothing is owed on the transaction but it is still open
+    # and this transaction is not an open circulation, close it
+    if( 
+        ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
+        ( !$circ or $circ->stop_fines )) {
+
+        $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
+        $xact->xact_finish('now');
+        $editor->update_money_billable_transaction($xact)
+            or return $editor->event;
+        return undef;
+    }
+
+    # If money is owed or a refund is due on the xact and xact_finish
+    # is set, clear it (to reopen the xact) and update
+    if( $summary->balance_owed != 0 and $xact->xact_finish ) {
+        $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
+        $xact->clear_xact_finish;
+        $editor->update_money_billable_transaction($xact)
+            or return $editor->event;
+        return undef;
+    }
+    return undef;
+}
 
 
 __PACKAGE__->register_method (
-       method => 'fetch_mbts',
+    method => 'fetch_mbts',
     authoritative => 1,
-       api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
+    api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
 );
 sub fetch_mbts {
-       my( $self, $conn, $auth, $id) = @_;
+    my( $self, $conn, $auth, $id) = @_;
 
-       my $e = new_editor(xact => 1, authtoken=>$auth);
-       return $e->event unless $e->checkauth;
-       my ($mbts) = $U->fetch_mbts($id, $e);
+    my $e = new_editor(xact => 1, authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my ($mbts) = $U->fetch_mbts($id, $e);
 
-       my $user = $e->retrieve_actor_user($mbts->usr)
-               or return $e->die_event;
+    my $user = $e->retrieve_actor_user($mbts->usr)
+        or return $e->die_event;
 
-       return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
-       $e->rollback;
-       return $mbts
+    return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
+    $e->rollback;
+    return $mbts
 }
 
 
-
 __PACKAGE__->register_method(
-       method => 'desk_payments',
-       api_name => 'open-ils.circ.money.org_unit.desk_payments'
+    method => 'desk_payments',
+    api_name => 'open-ils.circ.money.org_unit.desk_payments'
 );
-
 sub desk_payments {
-       my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
-       my $e = new_editor(authtoken=>$auth);
-       return $e->event unless $e->checkauth;
-       return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
-       my $data = $U->storagereq(
-               'open-ils.storage.money.org_unit.desk_payments.atomic',
-               $org, $start_date, $end_date );
-
-       $_->workstation( $_->workstation->name ) for(@$data);
-       return $data;
+    my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
+    my $data = $U->storagereq(
+        'open-ils.storage.money.org_unit.desk_payments.atomic',
+        $org, $start_date, $end_date );
+
+    $_->workstation( $_->workstation->name ) for(@$data);
+    return $data;
 }
 
 
 __PACKAGE__->register_method(
-       method => 'user_payments',
-       api_name => 'open-ils.circ.money.org_unit.user_payments'
+    method => 'user_payments',
+    api_name => 'open-ils.circ.money.org_unit.user_payments'
 );
 
 sub user_payments {
-       my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
-       my $e = new_editor(authtoken=>$auth);
-       return $e->event unless $e->checkauth;
-       return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
-       my $data = $U->storagereq(
-               'open-ils.storage.money.org_unit.user_payments.atomic',
-               $org, $start_date, $end_date );
-       for(@$data) {
-               $_->usr->card(
-                       $e->retrieve_actor_card($_->usr->card)->barcode);
-               $_->usr->home_ou(
-                       $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
-       }
-       return $data;
+    my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
+    my $data = $U->storagereq(
+        'open-ils.storage.money.org_unit.user_payments.atomic',
+        $org, $start_date, $end_date );
+    for(@$data) {
+        $_->usr->card(
+            $e->retrieve_actor_card($_->usr->card)->barcode);
+        $_->usr->home_ou(
+            $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
+    }
+    return $data;
 }
 
-
-
-
 1;
-
-
-
index bd1a845..c43b8de 100644 (file)
@@ -265,6 +265,8 @@ sub dispatch {
 
         my $retval = {
             statusText => "Transaction approved: " . $transaction->authorization,
+            processor => $argshash->{processor},
+            cardType => $cardtype,
             statusCode => 200,
             approvalCode => $transaction->authorization,
             server_response => $transaction->server_response
index 2715e7a..86e1052 100644 (file)
@@ -51,8 +51,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0056'); -- phasefx
-
+INSERT INTO config.upgrade_log (version) VALUES ('0057'); -- senator
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
index 16955f9..007990b 100644 (file)
@@ -619,6 +619,7 @@ CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.check_payment FOR EACH
 CREATE TABLE money.credit_card_payment (
        cc_type         TEXT,
        cc_number       TEXT,
+    cc_processor TEXT,
        expire_month    INT,
        expire_year     INT,
        approval_code   TEXT
diff --git a/Open-ILS/src/sql/Pg/upgrade/0057.mccp_processor_column.sql b/Open-ILS/src/sql/Pg/upgrade/0057.mccp_processor_column.sql
new file mode 100644 (file)
index 0000000..c1659bf
--- /dev/null
@@ -0,0 +1,7 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0057'); -- senator
+
+ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
+
+COMMIT;
index 011f64a..6cbdeaf 100644 (file)
 <!ENTITY staff.pat.barcode_entry.submit_btn.accesskey "S">
 <!ENTITY staff.pat.barcode_entry.retrieving.label "Retrieving...">
 <!ENTITY staff.patron.bill_cc_info.title "Credit Card Information">
+<!ENTITY staff.patron.bill_cc_info.where_process.label "Process where?">
+<!ENTITY staff.patron.bill_cc_info.process_int.label "Process payment through Evergreen">
+<!ENTITY staff.patron.bill_cc_info.process_ext.label "Record externally processed payment">
 <!ENTITY staff.patron.bill_cc_info.visa.label "Visa">
 <!ENTITY staff.patron.bill_cc_info.mastercard.label "Mastercard">
 <!ENTITY staff.patron.bill_cc_info.american_express.label "American Express">
index 3f08b1c..e6b5fc3 100644 (file)
@@ -10,6 +10,8 @@ staff.patron.barcode_entry.patron_consent_deny=Deny
 staff.patron.barcode_entry.patron_consent_confirm=Check here to confirm this message
 staff.patron.barcode_entry.patron_display_error=spawning patron display
 staff.patron.barcode_entry.user_perm_display_error=spawning user perm editor
+staff.patron.bill_cc_info.need_cc_number=You must provide a credit card number
+staff.patron.bill_cc_info.need_approval_code=You must provide an approval code or an imprint slip number
 staff.patron.bill_details.my_init.error=bill_details.xul, my_init:
 staff.patron.bill_details.handle_edit_bill_note.note_dialog.title=Replacement Note
 staff.patron.bill_details.handle_edit_bill_note.note_dialog.prompt=Enter new note:
index 9379669..808242a 100644 (file)
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_bill" title="&staff.patron.bill_cc_info.title;"
-    orient="vertical" style="overflow: auto"
-    onload="try{info_init(); font_helper();}catch(E){alert(E);}"
-    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+       orient="vertical" style="overflow: auto"
+       onload="try{info_init(); font_helper();refresh_fields();}catch(E){alert(E);}"
+       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
     <!-- BEHAVIOR -->
         <script type="text/javascript">var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true; var g = {};</script>
         <scripts id="openils_util_scripts"/>
 
-    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
-
-    <script>
-    <![CDATA[
-        function $(id) { return document.getElementById(id); }
-
-        function info_init() {
-            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-            if (typeof JSAN == 'undefined') { throw( $("commonStrings").getString('common.jsan.missing') ); }
-            JSAN.errorLevel = "die"; // none, warn, or die
-            JSAN.addRepository('/xul/server/');
-            JSAN.use('util.error'); g.error = new util.error();
-            g.error.sdump('D_TRACE','my_init() for patron_display.xul');
-            g.OpenILS = {}; JSAN.use('OpenILS.data'); g.OpenILS.data = new OpenILS.data();
-            g.OpenILS.data.init({'via':'stash'});
-            g.payment_blob = { 'cc_args' : {}, 'cancelled' : true };
-            g.OpenILS.data.temp = js2JSON( g.payment_blob );
-            g.OpenILS.data.stash('temp');
-
-            document.getElementById('cc_number').focus();
+       <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+
+       <script>
+       <![CDATA[
+               function $(id) { return document.getElementById(id); }
+
+        XULElement.prototype.hide = function() {
+            this.style.display = "none";
+        }
+        XULElement.prototype.reveal = function() {
+            this.style.display = "";
+        }
+
+        var show = {'int': 1, 'ext': 2}; // tied to Application::Circ::Money
+        var fields_of_interest = {
+            "cc_type":          show['ext'],
+            "cc_number":        show['int'],
+            "expire_month":     show['int'],
+            "expire_year":      show['int'],
+            "approval_code":    show['ext'],
+            "note":             show['ext'] + show['int'],
+            "where_process":    show['ext'] + show['int']
+        };
+
+        function is_relevant_here(field) {
+            var flag = $('where_process').value;
+            var field_flag = fields_of_interest[field];
+            return ((field_flag & flag) == flag);
+        }
+
+        function refresh_fields() {
+            for (var field in fields_of_interest) {
+                if (is_relevant_here(field)) {
+                    $('row_' + field).reveal();
+                }
+                else {
+                    $('row_' + field).hide();
+                }
+            }
         }
 
-        function info_finish() {
-            /* FIXME -- need unique temp space name */
-            delete( g.payment_blob.cancelled );
-            g.OpenILS.data.temp = js2JSON( g.payment_blob );
-            g.OpenILS.data.stash('temp');
+               function info_init() {
+                       netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                       if (typeof JSAN == 'undefined') { throw( $("commonStrings").getString('common.jsan.missing') ); }
+                       JSAN.errorLevel = "die"; // none, warn, or die
+                       JSAN.addRepository('/xul/server/');
+                       JSAN.use('util.error'); g.error = new util.error();
+                       g.error.sdump('D_TRACE','my_init() for patron_display.xul');
+                       g.OpenILS = {}; JSAN.use('OpenILS.data'); g.OpenILS.data = new OpenILS.data();
+                       g.OpenILS.data.init({'via':'stash'});
+            /* 'true' as a string matches the expectations in bills.js */
+            g.payment_blob = { 'cc_args' : {}, 'cancelled' : 'true' };
+                       g.OpenILS.data.temp = js2JSON( g.payment_blob );
+                       g.OpenILS.data.stash('temp');
+
+                       document.getElementById('cc_number').focus();
+               }
+
+        function sanity_check() {
+            if ($('where_process').value == show['int']) { // internal process
+                if ($('cc_number').value.match(/^\s*$/)) {
+                    alert($('patronStrings').getString('staff.patron.bill_cc_info.need_cc_number'));
+                    return false;
+                }
+            }
+            else {  // external
+                if ($('approval_code').value.match(/^\s*$/)) {
+                    alert($('patronStrings').getString('staff.patron.bill_cc_info.need_approval_code'));
+                    return false;
+                }
+            }
+            return true;
         }
 
-    ]]>
-    </script>
-
-    <messagecatalog id="patronStrings" src="/xul/server/locale/<!--#echo var='locale'-->/patron.properties" />
-
-    <groupbox>
-        <caption label="&staff.patron.bill_cc_info.info.label;"/>
-        <grid>
-            <columns> <column flex="0" /> <column flex="0" /> </columns>
-            <rows>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.type.label;"/>
-                    <menulist id="cc_type" oncommand="g.payment_blob.cc_args.type = this.value;">
-                        <menupopup>
-                            <menuitem label="&staff.patron.bill_cc_info.visa.label;" value="Visa"/>
-                            <menuitem label="&staff.patron.bill_cc_info.mastercard.label;" value="Mastercard"/>
-                            <menuitem label="&staff.patron.bill_cc_info.american_express.label;" value="American Express"/>
-                            <menuitem label="&staff.patron.bill_cc_info.discover.label;" value="Discover"/>
-                            <menuitem label="&staff.patron.bill_cc_info.other.label;" value="Other"/>
-                        </menupopup>
-                    </menulist>
-                </row>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.cc_number.value;"/>
-                    <textbox id="cc_number" onchange="g.payment_blob.cc_args.number = event.target.value" context="clipboard"/>
-                </row>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.month_expire.value;"/>
-                    <textbox id="expire_month" onchange="g.payment_blob.cc_args.expire_month = event.target.value" context="clipboard"/>
-                </row>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.year_expire.value;"/>
-                    <textbox id="expire_year" onchange="g.payment_blob.cc_args.expire_year = event.target.value" context="clipboard"/>
-                </row>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.approval_code.value;"/>
-                    <textbox id="approval_code" onchange="g.payment_blob.cc_args.approval_code = event.target.value" context="clipboard"/>
-                </row>
-                <row>
-                    <label value="&staff.patron.bill_cc_info.note.value;"/>
-                    <textbox id="note" onchange="g.payment_blob.note = event.target.value" multiline="true" context="clipboard"/>
-                </row>
-            </rows>
-        </grid>
-        <hbox>
-            <spacer flex="1"/>
-            <button label="&staff.patron.bill_cc_info.cancel.label;" oncommand="window.close()" accesskey="&staff.patron.bill_cc_info.cancel.accesskey;"/>
-            <button label="&staff.patron.bill_cc_info.submit.label;" oncommand="info_finish(); window.close();" accesskey="&staff.patron.bill_cc_info.submit.accesskey;"/>
-        </hbox>
-    </groupbox>
+               function info_finish() {
+                       /* FIXME -- need unique temp space name */
+
+            /* The following for loop gathers our payment_blob values from
+            the widgets in this window.  This is better than the method of
+            gathering data that was here before (using oncommand attributes
+            to set values in this JS object whenever a field value changed)
+            because (if for no other reason), a select menu, if left at the
+            default value, would never reach the payment_blob, because its
+            oncommand attribute would never fire. */
+            for (var field in fields_of_interest) {
+                if (is_relevant_here(field)) {
+                    var matches = field.match(/^cc_(.+)$/);
+                    var target_key = matches ? matches[1] : field;
+                    g.payment_blob.cc_args[target_key] = $(field).value;
+                }
+            }
+                       delete( g.payment_blob.cancelled );
+                       g.OpenILS.data.temp = js2JSON( g.payment_blob );
+                       g.OpenILS.data.stash('temp');
+               }
+
+       ]]>
+       </script>
+
+       <messagecatalog id="patronStrings" src="/xul/server/locale/<!--#echo var='locale'-->/patron.properties" />
+
+       <groupbox>
+               <caption label="&staff.patron.bill_cc_info.info.label;"/>
+               <grid>
+                       <columns> <column flex="0" /> <column flex="0" /> </columns>
+                       <rows>
+                               <row id="row_where_process">
+                                       <label value="&staff.patron.bill_cc_info.where_process.label;"/>
+                                       <menulist id="where_process" oncommand="refresh_fields();">
+                                               <menupopup>
+                                                       <menuitem label="&staff.patron.bill_cc_info.process_int.label;" value="1"/>
+                                                       <menuitem label="&staff.patron.bill_cc_info.process_ext.label;" value="2"/>
+                                               </menupopup>
+                                       </menulist>
+                               </row>
+                               <row id="row_cc_type">
+                                       <label value="&staff.patron.bill_cc_info.type.label;"/>
+                                       <menulist id="cc_type">
+                                               <menupopup>
+                            <menuitem label="&staff.patron.bill_cc_info.visa.label;" value="VISA"/><!-- capitalization to match CC processors' output -->
+                            <menuitem label="&staff.patron.bill_cc_info.mastercard.label;" value="MasterCard"/><!-- capitalization to match CC processors' output -->
+                                                       <menuitem label="&staff.patron.bill_cc_info.american_express.label;" value="American Express"/>
+                                                       <menuitem label="&staff.patron.bill_cc_info.discover.label;" value="Discover"/>
+                                                       <menuitem label="&staff.patron.bill_cc_info.other.label;" value="Other"/>
+                                               </menupopup>
+                                       </menulist>
+                               </row>
+                               <row id="row_approval_code">
+                                       <label value="&staff.patron.bill_cc_info.approval_code.value;"/>
+                                       <textbox id="approval_code" context="clipboard"/>
+                               </row>
+                               <row id="row_cc_number">
+                                       <label value="&staff.patron.bill_cc_info.cc_number.value;"/>
+                                       <textbox id="cc_number" context="clipboard"/>
+                               </row>
+                               <row id="row_expire_month">
+                                       <label value="&staff.patron.bill_cc_info.month_expire.value;"/>
+                                       <textbox id="expire_month" context="clipboard"/>
+                               </row>
+                               <row id="row_expire_year">
+                                       <label value="&staff.patron.bill_cc_info.year_expire.value;"/>
+                                       <textbox id="expire_year" context="clipboard"/>
+                               </row>
+                               <row id="row_note">
+                                       <label value="&staff.patron.bill_cc_info.note.value;"/>
+                                       <textbox id="note" multiline="true" context="clipboard"/>
+                               </row>
+                       </rows>
+               </grid>
+               <hbox>
+                       <spacer flex="1"/>
+                       <button label="&staff.patron.bill_cc_info.cancel.label;" oncommand="window.close()" accesskey="&staff.patron.bill_cc_info.cancel.accesskey;"/>
+            <button label="&staff.patron.bill_cc_info.submit.label;" oncommand="if (sanity_check()) { info_finish(); window.close(); }" accesskey="&staff.patron.bill_cc_info.submit.accesskey;"/>
+               </hbox>
+       </groupbox>
 
 </window>
 
-