[freeside-commits] branch master updated. 5d089cbe4980f7c9c25b83e164099b22bc59eead

Ivan ivan at 420.am
Fri Jan 29 18:26:44 PST 2016


The branch, master has been updated
       via  5d089cbe4980f7c9c25b83e164099b22bc59eead (commit)
      from  9db332d80967969856c5b5fe8b6ac91304734b04 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 5d089cbe4980f7c9c25b83e164099b22bc59eead
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Jan 29 18:26:43 2016 -0800

    Use any card on file when making a payment, RT#23741

diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 9e0db29..8e09848 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -4435,8 +4435,10 @@ sub payment_history {
 Saves a new cust_payby for this customer, replacing an existing entry only
 in select circumstances.  Does not validate input.
 
-If auto is specified, marks this as the customer's primary method (weight 1) 
-and changes existing primary methods for that payby to secondary methods (weight 2.)
+If auto is specified, marks this as the customer's primary method, or the 
+specified weight.  Existing payment methods have their weight incremented as
+appropriate.
+
 If bill_location is specified with auto, also sets location in cust_main.
 
 Will not insert complete duplicates of existing records, or records in which the
@@ -4448,39 +4450,77 @@ blanks when replacing.
 
 Accepts the following named parameters:
 
-payment_payby - either CARD or CHEK
+=over 4
+
+=item payment_payby
+
+either CARD or CHEK
+
+=item auto
+
+save as an automatic payment type (CARD/CHEK if true, DCRD/DCHK if false)
+
+=item weight
+
+optional, set higher than 1 for secondary, etc.
+
+=item payinfo
+
+required
+
+=item paymask
+
+optional, but should be specified for anything that might be tokenized, will be preserved when replacing
+
+=item payname
+
+required
+
+=item payip
+
+optional, will be preserved when replacing
+
+=item paydate
+
+CARD only, required
+
+=item bill_location
 
-auto - save as an automatic payment type (CARD/CHEK if true, DCRD/DCHK if false)
+CARD only, required, FS::cust_location object
 
-payinfo - required
+=item paystart_month
+
+CARD only, optional, will be preserved when replacing
 
-paymask - optional, but should be specified for anything that might be tokenized, will be preserved when replacing
+=item paystart_year
 
-payname - required
+CARD only, optional, will be preserved when replacing
 
-payip - optional, will be preserved when replacing
+=item payissue
 
-paydate - CARD only, required
+CARD only, optional, will be preserved when replacing
 
-bill_location - CARD only, required, FS::cust_location object
+=item paycvv
 
-paystart_month - CARD only, optional, will be preserved when replacing
+CARD only, only used if conf cvv-save is set appropriately
 
-paystart_year - CARD only, optional, will be preserved when replacing
+=item paytype
 
-payissue - CARD only, optional, will be preserved when replacing
+CHEK only
 
-paycvv - CARD only, only used if conf cvv-save is set appropriately
+=item paystate
 
-paytype - CHEK only
+CHEK only
 
-paystate - CHEK only
+=back
 
 =cut
 
 #The code for this option is in place, but it's not currently used
 #
-# replace - existing cust_payby object to be replaced (must match custnum)
+# =item replace
+#
+# existing cust_payby object to be replaced (must match custnum)
 
 # stateid/stateid_state/ss are not currently supported in cust_payby,
 # might not even work properly in 4.x, but will need to work here if ever added
@@ -4511,8 +4551,7 @@ sub save_cust_payby {
     @check_existing = qw( CHEK DCHK );
   }
 
-  # every automatic payment type added here will be marked primary
-  $new->set( 'weight' => $opt{'auto'} ? 1 : '' );
+  $new->set( 'weight' => $opt{'auto'} ? $opt{'weight'} : '' );
 
   # basic fields
   $new->payinfo($opt{'payinfo'}); # sets default paymask, but not if it's already tokenized
@@ -4606,7 +4645,7 @@ PAYBYLOOP:
       # if we got this far, we're definitely replacing
       $old = $cust_payby;
       last PAYBYLOOP;
-    }
+    } #PAYBYLOOP
   }
 
   if ($old) {
@@ -4649,7 +4688,8 @@ PAYBYLOOP:
       last unless $cust_payby->payby !~ /^D/;
       last if $cust_payby->weight > 1;
       next if $new->custpaybynum eq $cust_payby->custpaybynum;
-      $cust_payby->set( 'weight' => 2 );
+      next if $cust_payby->weight < ($opt{'weight'} || 1);
+      $cust_payby->weight( $cust_payby->weight + 1 );
       my $error = $cust_payby->replace;
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index 9111fdf..a686242 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -560,6 +560,39 @@ sub paydate_mon_year {
 
 }
 
+=item label
+
+Returns a one line text label for this payment type.
+
+=cut
+
+my %weight = (
+  1 => 'Primary',
+  2 => 'Secondary',
+  3 => 'Tertiary',
+  4 => 'Fourth',
+  5 => 'Fifth',
+  6 => 'Sixth',
+  7 => 'Seventh',
+);
+
+sub label {
+  my $self = shift;
+
+  my $name = $self->payby =~ /^(CARD|DCRD)$/
+              && cardtype($self->paymask) || FS::payby->shortname($self->payby);
+
+  ( $self->payby =~ /^(CARD|CHEK)$/  ? $weight{$self->weight}. ' automatic '
+                                     : 'Manual '
+  ).
+  "$name: ". $self->paymask.
+  ( $self->payby =~ /^(CARD|DCRD)$/
+      ? ' Exp '. join('/', $self->paydate_mon_year)
+      : ''
+  );
+
+}
+
 =item realtime_bop
 
 =cut
diff --git a/httemplate/elements/select-cust_payby.html b/httemplate/elements/select-cust_payby.html
new file mode 100644
index 0000000..a726cb3
--- /dev/null
+++ b/httemplate/elements/select-cust_payby.html
@@ -0,0 +1,23 @@
+<% include( '/elements/select-table.html',
+                 'table'         => 'cust_payby',
+                 'name_col'      => 'label',
+                 'value'         => $custpaybynum,
+                 'disable_empty' => 1,
+                 'post_options'  => [ '0' => 'Enter new payment information' ],
+                 'hashref'       => { 'custnum'  => $opt{'custnum'},
+                                      'disabled' => '',
+                                    },
+                 %opt,
+             )
+%>
+<%init>
+
+my %opt = @_;
+my $custpaybynum = $opt{'curr_value'} || $opt{'value'};
+
+if ( $opt{'cust_payby'} ) {
+  $opt{'records'} = delete $opt{'cust_payby'};
+  $opt{'presorted'} = 1 if ! exists($opt{'presorted'});
+}
+
+</%init>
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
index 0b04fee..4b6ddb4 100644
--- a/httemplate/elements/select-table.html
+++ b/httemplate/elements/select-table.html
@@ -28,6 +28,7 @@ Example:
     'agent_null_right' => '', #right to see un-agented entries
     #or
     'records'        => \@records, #instead of search params
+    'presorted'      => 0, #set true to disable sorting the records on name_col
 
     #instead of the primary key... only for special cases
     'value_col'      => 'columnname',
@@ -176,8 +177,9 @@ if ( $opt{'agent_virt'} ) {
 
 my @records = ();
 if ( $opt{'records'} ) {
-  @records = sort { $a->get($name_col) cmp $b->get($name_col) }
-             @{ $opt{'records'} };
+  @records = @{ $opt{'records'} };
+  @records = sort { $a->get($name_col) cmp $b->get($name_col) } @records
+    unless $opt{'presorted'};
 } else {
   @records = qsearch( {
     'table'     => $opt{'table'},
diff --git a/httemplate/elements/tr-select-cust_payby.html b/httemplate/elements/tr-select-cust_payby.html
new file mode 100644
index 0000000..e2b2e09
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_payby.html
@@ -0,0 +1,31 @@
+% if ( scalar(@{ $opt{'cust_payby'} }) == 0 ) { 
+
+  <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'custpaybynum' %>" VALUE="">
+
+% } else { 
+
+  <TR>
+    <TH ALIGN="right"><% $opt{'label'} || 'Payment via' %></TD>
+    <TD>
+      <% include( '/elements/select-cust_payby.html',
+                    'curr_value' => $custpaybynum,
+                    %opt
+                )
+      %>
+    </TD>
+  </TR>
+
+% } 
+
+<%init>
+
+my %opt = @_;
+my $custpaybynum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'cust_payby'} ||= [ qsearch( 'cust_payby', { custnum  => $opt{custnum},
+                                                  disabled => '',
+                                                }
+                                )
+                       ];
+
+</%init>
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
index 7afdfd1..02648a8 100644
--- a/httemplate/misc/payment.cgi
+++ b/httemplate/misc/payment.cgi
@@ -33,23 +33,57 @@
     &>
 % }
 
+<SCRIPT TYPE="text/javascript">
+  function cust_payby_changed (what) {
+    var custpaybynum = what.options[what.selectedIndex].value
+    if ( custpaybynum == '' || custpaybynum == '0' ) {
+       //what.form.payinfo.disabled = false;
+       $('#cust_payby').slideDown();
+    } else {
+       //what.form.payinfo.value = '';
+       //what.form.payinfo.disabled = true;
+       $('#cust_payby').slideUp();
+    }
+  }
+</SCRIPT>
+
+% #can't quite handle CARD/CHEK on the same page yet, but very close
+% #does it make sense from a UI/usability perspective?
+%
+% my @cust_payby = ();
+% if ( $payby eq 'CARD' ) {
+%   @cust_payby = $cust_main->cust_payby('CARD','DCRD');
+% } elsif ( $payby eq 'CHEK' ) {
+%   @cust_payby = $cust_main->cust_payby('CHEK','DCHK');
+% } else {
+%   die "unknown payby $payby";
+% }
+%
+% my $custpaybynum = length(scalar($cgi->param('custpaybynum')))
+%                      ? scalar($cgi->param('custpaybynum'))
+%                      : scalar(@cust_payby) && $cust_payby[0]->custpaybynum;
+
+<& /elements/tr-select-cust_payby.html,
+     'cust_payby' => \@cust_payby,
+     'curr_value' => $custpaybynum,
+     'onchange'   => 'cust_payby_changed(this)',
+&>
+
+</TABLE>
+<BR>
+<DIV ID="cust_payby"
+  <% $custpaybynum ? 'STYLE="display:none"'
+                   : ''
+  %>
+>
+<TABLE class="fsinnerbox">
+
 % my $auto = 0;
 % if ( $payby eq 'CARD' ) {
 %
 %   my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
 %   my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
 %   my $location = $cust_main->bill_location;
-%
-%   #auto-fill with the highest weighted match
-%   my ($cust_payby) = $cust_main->cust_payby('CARD','DCRD');
-%   if ($cust_payby) {
-%     $payinfo = $cust_payby->paymask;
-%     $paycvv  = $cust_payby->paycvv;
-%     ( $month, $year ) = $cust_payby->paydate_monthyear;
-%     $payname = $cust_payby->payname if $cust_payby->payname;
-%     $location = $cust_payby->cust_location || $location;
-%     $auto = 1 if $cust_payby->payby eq 'CARD';
-%   }
 
     <TR>
       <TH ALIGN="right"><% mt('Card number') |h %></TH>
@@ -104,22 +138,6 @@
 %   my( $account, $aba, $branch, $payname, $ss, $paytype, $paystate,
 %       $stateid, $stateid_state )
 %     = ( '', '', '', '', '', '', '', '', '' );
-%   my ($cust_payby) = $cust_main->cust_payby('CHEK','DCHK');
-%   if ($cust_payby) {
-%     $cust_payby->paymask =~ /^([\dx]+)\@([\d\.x]*)$/i
-%       or die "unparsable paymask ". $cust_payby->paymask;
-%     ($account, $aba) = ($1, $2);
-%     ($branch,$aba) = split('\.',$aba)
-%       if $conf->config('echeck-country') eq 'CA';
-%     $payname = $cust_payby->payname;
-%     $paytype = $cust_payby->getfield('paytype');
-%     $paystate = $cust_payby->getfield('paystate');
-%     $auto = 1 if $cust_payby->payby eq 'CHEK';
-%     # these values aren't in cust_payby, but maybe should be...
-%     $ss = $cust_main->ss;
-%     $stateid = $cust_main->getfield('stateid');
-%     $stateid_state = $cust_main->getfield('stateid_state');
-%   }
 %
 %  #false laziness w/{edit,view}/cust_main/billing.html
 %  my $routing_label = $conf->config('echeck-country') eq 'US'
@@ -210,7 +228,7 @@
 
 
 <TR>
-  <TD COLSPAN=2>
+  <TD COLSPAN=8>
     <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
     <% mt('Remember this information') |h %>
   </TD>
@@ -237,19 +255,43 @@
 % }
 
 <TR>
-  <TD COLSPAN=2>
+  <TD COLSPAN=8>
     <INPUT TYPE="checkbox"<% $auto ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
     <% mt("Charge future payments to this [_1] automatically",$type{$payby}) |h %> 
+% if ( @cust_payby ) {
+    <% mt('as') |h %>
+    <SELECT NAME="weight">
+%     for ( 1 .. 1+scalar(grep { $_->payby =~ /^(CARD|CHEK)$/ } @cust_payby) ) {
+        <OPTION VALUE="<%$_%>"><% mt( $weight{$_} ) |h %>
+%     }
+    </SELECT>
+% } else {
+    <INPUT TYPE="hidden" NAME="weight" VALUE="1">
+% }
   </TD>
 </TR>
 
 </TABLE>
+</DIV>
 
 <BR>
 <INPUT TYPE="submit" NAME="process" VALUE="<% mt('Process payment') |h %>">
 </FORM>
 
 <& /elements/footer.html &>
+<%once>
+
+my %weight = (
+  1 => 'Primary',
+  2 => 'Secondary',
+  3 => 'Tertiary',
+  4 => 'Fourth',
+  5 => 'Fifth',
+  6 => 'Sixth',
+  7 => 'Seventh',
+);
+
+</%once>
 <%init>
 
 die "access denied"
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index a6046a0..79b43b7 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -23,14 +23,21 @@ die "access denied" unless $curuser->access_right('Process payment');
 
 my $conf = new FS::Conf;
 
+##
+# info for all payments, stored or unstored
+##
+
 #some false laziness w/MyAccount::process_payment
 
 $cgi->param('custnum') =~ /^(\d+)$/
   or die "illegal custnum ". $cgi->param('custnum');
 my $custnum = $1;
 
-my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
-die "unknown custnum $custnum" unless $cust_main;
+my $cust_main = qsearchs({
+  'table'     => 'cust_main',
+  'hashref'   => { 'custnum' => $custnum },
+  'extra_sql' => ' AND '. $curuser->agentnums_sql,
+}) or die "unknown custnum $custnum";
 
 $cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/
   or errorpage("illegal amount ". $cgi->param('amount'));
@@ -42,14 +49,6 @@ if ( $cgi->param('fee') =~ /^\s*(\d*(\.\d\d)?)\s*$/ ) {
   $amount = sprintf('%.2f', $amount + $fee);
 }
 
-$cgi->param('year') =~ /^(\d+)$/
-  or errorpage("illegal year ". $cgi->param('year'));
-my $year = $1;
-
-$cgi->param('month') =~ /^(\d+)$/
-  or errorpage("illegal month ". $cgi->param('month'));
-my $month = $1;
-
 $cgi->param('payby') =~ /^(CARD|CHEK)$/
   or errorpage("illegal payby ". $cgi->param('payby'));
 my $payby = $1;
@@ -61,10 +60,6 @@ my %type = ( 'CARD' => 'credit card',
              'CHEK' => 'electronic check (ACH)',
            );
 
-$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/
-  or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname'));
-my $payname = $1;
-
 $cgi->param('payunique') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
   or errorpage(gettext('illegal_text'). " payunique: ". $cgi->param('payunique'));
 my $payunique = $1;
@@ -73,33 +68,48 @@ $cgi->param('balance') =~ /^\s*(\-?\s*\d*(\.\d\d)?)\s*$/
   or errorpage("illegal balance");
 my $balance = $1;
 
-my $payinfo;
-my $paymask; # override only used by loaded cust payinfo, only implemented for realtime processing
-my $paycvv = '';
-my $loaded_cust_payby;
-if ( $payby eq 'CHEK' ) {
-
-  if ($cgi->param('payinfo1') =~ /xx/i || $cgi->param('payinfo2') =~ /xx/i ) {
-
-    my $search_paymask = $cgi->param('payinfo1') . '@' . $cgi->param('payinfo2');
-    $search_paymask .= '.' . $cgi->param('payinfo3')
-      if $conf->config('echeck-country') eq 'CA';
-
-    #paymask might not be saved in database, need to run paymask method for any potential match
-    foreach my $search_cust_payby ($cust_main->cust_payby('CHEK','DCHK')) {
-      if ($search_paymask eq $search_cust_payby->paymask) {
-        # if there are multiple matches, assume for now that it's the first one returned,
-        # since that's what auto-fills; it's unlikely a masked number would be entered by hand,
-        # but it's very likely users will just click-through what's been auto-filled
-        $loaded_cust_payby = $search_cust_payby;
-        last;
-      }
-    }
-    errorpage("Masked payinfo not found") unless $loaded_cust_payby;
-    $payinfo = $loaded_cust_payby->payinfo;
-    $paymask = $loaded_cust_payby->paymask;
+$cgi->param('discount_term') =~ /^(\d*)$/
+  or errorpage("illegal discount_term");
+my $discount_term = $1;
+
+my( $payinfo, $paycvv, $month, $year, $payname );
+my $paymask = '';
+if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) {
+
+  ##
+  # use stored cust_payby info
+  ##
+
+  my $cust_payby = qsearchs('cust_payby', { custnum      => $custnum,
+                                            custpaybynum => $custpaybynum, } )
+    or die "unknown custpaybynum $custpaybynum";
+
+  $payinfo = $cust_payby->payinfo;
+  $paymask = $cust_payby->paymask;
+  $paycvv = '';
+  ( $month, $year ) = $cust_payby->paydate_mon_year;
+  $payname = $cust_payby->payname;
+
+} else {
+
+  ##
+  # use new info
+  ##
+
+  $cgi->param('year') =~ /^(\d+)$/
+    or errorpage("illegal year ". $cgi->param('year'));
+  $year = $1;
+
+  $cgi->param('month') =~ /^(\d+)$/
+    or errorpage("illegal month ". $cgi->param('month'));
+  $month = $1;
+
+  $cgi->param('payname') =~ /^([\w \,\.\-\']+)$/
+    or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname'));
+  $payname = $1;
+
+  if ( $payby eq 'CHEK' ) {
 
-  } else {
     $cgi->param('payinfo1') =~ /^(\d+)$/
       or errorpage("Illegal account number ". $cgi->param('payinfo1'));
     my $payinfo1 = $1;
@@ -112,47 +122,30 @@ if ( $payby eq 'CHEK' ) {
       $payinfo2 = "$1.$payinfo2";
     }
     $payinfo = $payinfo1. '@'. $payinfo2;
-  }
 
-} elsif ( $payby eq 'CARD' ) {
+  } elsif ( $payby eq 'CARD' ) {
 
-  $payinfo = $cgi->param('payinfo');
-  if ($payinfo =~ /xx/i) {
+    $payinfo = $cgi->param('payinfo');
 
-    #paymask might not be saved in database, need to run paymask method for any potential match
-    foreach my $search_cust_payby ($cust_main->cust_payby('CARD','DCRD')) {
-      if ($payinfo eq $search_cust_payby->paymask) {
-        $loaded_cust_payby = $search_cust_payby;
-        last;
-      }
-    }
-
-    errorpage("Masked payinfo not found") unless $loaded_cust_payby;
-    $payinfo = $loaded_cust_payby->payinfo;
-    $paymask = $loaded_cust_payby->paymask;
-
-  }
-
-  $payinfo =~ s/\D//g;
-  $payinfo =~ /^(\d{13,16}|\d{8,9})$/
-    or errorpage(gettext('invalid_card'));
-  $payinfo = $1;
-  validate($payinfo)
-    or errorpage(gettext('invalid_card'));
+    $payinfo =~ s/\D//g;
+    $payinfo =~ /^(\d{13,16}|\d{8,9})$/
+      or errorpage(gettext('invalid_card'));
+    $payinfo = $1;
+    validate($payinfo)
+      or errorpage(gettext('invalid_card'));
 
-  unless ( $payinfo =~ /^99\d{14}$/ ) { #token
+    unless ( $payinfo =~ /^99\d{14}$/ ) { #token
 
-    my $cardtype = cardtype($payinfo);
+      my $cardtype = cardtype($payinfo);
 
-    errorpage(gettext('unknown_card_type'))
-      if $cardtype eq "Unknown";
+      errorpage(gettext('unknown_card_type'))
+        if $cardtype eq "Unknown";
 
-    my %bop_card_types = map { $_=>1 } values %{ card_types() };
-    errorpage("$cardtype not accepted") unless $bop_card_types{$cardtype};
+      my %bop_card_types = map { $_=>1 } values %{ card_types() };
+      errorpage("$cardtype not accepted") unless $bop_card_types{$cardtype};
 
-  }
+    }
 
-  if ( defined $cust_main->dbdef_table->column('paycvv') ) { #is this test necessary anymore?
     if ( length($cgi->param('paycvv') ) ) {
       if ( cardtype($payinfo) eq 'American Express card' ) {
         $cgi->param('paycvv') =~ /^(\d{4})$/
@@ -163,48 +156,49 @@ if ( $payby eq 'CHEK' ) {
           or errorpage("CVV2 (CVC2/CID) is three digits.");
         $paycvv = $1;
       }
-    }elsif( $conf->exists('backoffice-require_cvv') ){
+    } elsif ( $conf->exists('backoffice-require_cvv') ){
       errorpage("CVV2 is required");
     }
-  }
 
-} else {
-  die "unknown payby $payby";
-}
-
-$cgi->param('discount_term') =~ /^(\d*)$/
-  or errorpage("illegal discount_term");
-my $discount_term = $1;
-
-# save first, for proper tokenization later
-if ( $cgi->param('save') ) {
-
-  my %saveopt;
-  if ( $payby eq 'CARD' ) {
-    my $bill_location = FS::cust_location->new;
-    $bill_location->set( $_ => $cgi->param($_) )
-      foreach @{$payby2fields{$payby}};
-    $saveopt{'bill_location'} = $bill_location;
-    $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this
-    $saveopt{'paydate'} = "$year-$month-01";
   } else {
-    # ss/stateid/stateid_state won't be saved, but should be harmless to pass
-    %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}};
+    die "unknown payby $payby";
   }
 
-  my $error = $cust_main->save_cust_payby(
-    'payment_payby' => $payby,
-    'auto'          => scalar($cgi->param('auto')),
-    'payinfo'       => $payinfo,
-    'paymask'       => $paymask,
-    'payname'       => $payname,
-    %saveopt
-  );
+  # save first, for proper tokenization later
+  if ( $cgi->param('save') ) {
+
+    my %saveopt;
+    if ( $payby eq 'CARD' ) {
+      my $bill_location = FS::cust_location->new;
+      $bill_location->set( $_ => $cgi->param($_) )
+        foreach @{$payby2fields{$payby}};
+      $saveopt{'bill_location'} = $bill_location;
+      $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this
+      $saveopt{'paydate'} = "$year-$month-01";
+    } else {
+      # ss/stateid/stateid_state won't be saved, but should be harmless to pass
+      %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}};
+    }
+
+    my $error = $cust_main->save_cust_payby(
+      'payment_payby' => $payby,
+      'auto'          => scalar($cgi->param('auto')),
+      'weight'        => scalar($cgi->param('weight')),
+      'payinfo'       => $payinfo,
+      'payname'       => $payname,
+      %saveopt
+    );
+
+    errorpage("error saving info, payment not processed: $error")
+      if $error;	
+  }
 
-  errorpage("error saving info, payment not processed: $error")
-    if $error;	
 }
 
+##
+# now run the payment
+##
+
 my $error = '';
 my $paynum = '';
 if ( $cgi->param('batch') ) {
@@ -218,7 +212,7 @@ if ( $cgi->param('batch') ) {
                                      'payinfo'  => $payinfo,
                                      'paydate'  => "$year-$month-01",
                                      'payname'  => $payname,
-                                     map { $_ => $cgi->param($_) } 
+                                     map { $_ => scalar($cgi->param($_)) } 
                                        @{$payby2fields{$payby}}
                                    );
   errorpage($error) if $error;
@@ -237,7 +231,7 @@ if ( $cgi->param('batch') ) {
     'paycvv'     => $paycvv,
     'paynum_ref' => \$paynum,
     'discount_term' => $discount_term,
-    map { $_ => $cgi->param($_) } @{$payby2fields{$payby}}
+    map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}}
   );
   errorpage($error) if $error;
 
@@ -261,6 +255,8 @@ if ( $cgi->param('batch') ) {
 
 }
 
-#success!
+##
+# success!  step 3: profit!
+##
 
 </%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/cust_main.pm                            |   82 +++++++---
 FS/FS/cust_payby.pm                           |   33 ++++
 httemplate/elements/select-cust_payby.html    |   23 +++
 httemplate/elements/select-table.html         |    6 +-
 httemplate/elements/tr-select-cust_payby.html |   31 ++++
 httemplate/misc/payment.cgi                   |  100 ++++++++----
 httemplate/misc/process/payment.cgi           |  212 ++++++++++++-------------
 7 files changed, 327 insertions(+), 160 deletions(-)
 create mode 100644 httemplate/elements/select-cust_payby.html
 create mode 100644 httemplate/elements/tr-select-cust_payby.html




More information about the freeside-commits mailing list