[freeside-commits] branch FREESIDE_4_BRANCH updated. 43f9be2fd8ef088788ade01d2e4bcc0667be36e5

Mark Wells mark at 420.am
Tue Jul 19 12:56:56 PDT 2016


The branch, FREESIDE_4_BRANCH has been updated
       via  43f9be2fd8ef088788ade01d2e4bcc0667be36e5 (commit)
       via  0491d209351c209a64885a4d1fdf1398c1b56397 (commit)
       via  df0a429189bedbd95b1e94d20c1243f51b398b77 (commit)
       via  026395c2e7438bd8c8b3721dd437cb41bc46d903 (commit)
       via  17f3394d929e83ed3b58916dcc40eac5c87bb5b4 (commit)
       via  666c35e4745dc6b5518f53267aadb3233f7cddb8 (commit)
      from  78a19c83d3cc5b3f22156d9ac19e5ae2d9170175 (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 43f9be2fd8ef088788ade01d2e4bcc0667be36e5
Merge: 0491d20 78a19c8
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Jul 19 12:56:49 2016 -0700

    Merge branch 'FREESIDE_4_BRANCH' of git.freeside.biz:/home/git/freeside into 4.x


commit 0491d209351c209a64885a4d1fdf1398c1b56397
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Jul 19 12:56:06 2016 -0700

    unreverse the check for tokenized payinfo, #71291

diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index facb9c8..e4a1d19 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -354,7 +354,7 @@ sub check {
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
 
     my $cardtype = cardtype($payinfo);
-    $cardtype = 'Tokenized' if $self->payinfo !~ /^99\d{14}$/; #token
+    $cardtype = 'Tokenized' if $self->payinfo =~ /^99\d{14}$/; #token
     
     return gettext('unknown_card_type') if $cardtype eq "Unknown";
     
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index a61125e..4f26e8c 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -197,7 +197,7 @@ sub payinfo_check {
 
     my $payinfo = $self->payinfo;
     my $cardtype = cardtype($payinfo);
-    $cardtype = 'Tokenized' if $payinfo !~ /^99\d{14}$/;
+    $cardtype = 'Tokenized' if $payinfo =~ /^99\d{14}$/;
     $self->set('paycardtype', $cardtype);
 
     if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {

commit df0a429189bedbd95b1e94d20c1243f51b398b77
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Jul 18 18:06:28 2016 -0700

    suppress msg_template warnings during 4.x upgrade, #21564

diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index 1dd48cc..b890717 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -93,6 +93,7 @@ sub extension_table { ''; } # subclasses don't HAVE to have extensions
 
 sub _rebless {
   my $self = shift;
+  return '' unless $self->msgclass;
   my $class = 'FS::msg_template::' . $self->msgclass;
   eval "use $class;";
   bless($self, $class) unless $@;

commit 026395c2e7438bd8c8b3721dd437cb41bc46d903
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Jul 15 16:11:53 2016 -0700

    use stored card type for payment search, #71291

diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
index 4ed297d..03aaedd 100755
--- a/httemplate/search/elements/cust_pay_or_refund.html
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -67,6 +67,15 @@ Examples:
                                     ],
                 'show_combined'  => 1,
 &>
+<%shared>
+# canonicalize the payby subtype string to an SQL-quoted list
+my %cardtype_of = (
+  'VisaMC'    => q['VISA card', 'MasterCard'],
+  'Amex'      => q['American Express card'],
+  'Discover'  => q['Discover card'],
+  'Maestro'   => q['Switch', 'Solo', 'Laser'],
+);  
+</%shared>
 <%init>
 
 my %opt = @_;
@@ -191,10 +200,8 @@ if ($opt{'show_card_type'}) {
   push @header, emt('Card Type');
   $align .= 'r';
   push @links, '';
-  push @fields, sub { 
-    (($_[0]->payby eq 'CARD') && ($_[0]->paymask !~ /N\/A/)) ? cardtype($_[0]->paymask) : ''
-  };
-  push @sort_fields, '';
+  push @fields, 'paycardtype';
+  push @sort_fields, 'paycardtype';
 }
 
 if ( $unapplied ) {
@@ -305,150 +312,32 @@ if ( $cgi->param('magic') ) {
     if ( $cgi->param('payby') ) {
 
       my @all_payby_search = ();
-      foreach my $payby ( $cgi->param('payby') ) {
-
-        $payby =~
-          /^(CARD|CHEK|BILL|CASH|PPAL|APPL|ANRD|PREP|WIRE|WEST|IDTP|EDI|MCRD|MCHK)(-(VisaMC|Amex|Discover|Maestro|Tokenized))?$/
-            or die "illegal payby $payby";
-
-        my $payby_search = "$table.payby = '$1'";
-
-        if ( $3 ) {
-
-          my $cardtype = $3;
-
-          my $similar_to = dbh->{Driver}->{Name} =~ /^mysql/i
-                             ? 'REGEXP' #doesn't behave exactly the same, but
-                                        #should work for our patterns
-                             : 'SIMILAR TO';
-
-          my $search;
-          if ( $cardtype eq 'VisaMC' ) {
-
-            #avoid posix regexes for portability
-            $search =
-              # Visa
-              " ( (     substring($table.payinfo from 1 for 1) = '4'     ".
-              #   is not Switch
-              "     AND substring($table.payinfo from 1 for 4) != '4936' ".
-              "     AND substring($table.payinfo from 1 for 6)           ".
-              "         NOT $similar_to '49030[2-9]'                        ".
-              "     AND substring($table.payinfo from 1 for 6)           ".
-              "         NOT $similar_to '49033[5-9]'                        ".
-              "     AND substring($table.payinfo from 1 for 6)           ".
-              "         NOT $similar_to '49110[1-2]'                        ".
-              "     AND substring($table.payinfo from 1 for 6)           ".
-              "         NOT $similar_to '49117[4-9]'                        ".
-              "     AND substring($table.payinfo from 1 for 6)           ".
-              "         NOT $similar_to '49118[1-2]'                        ".
-              "   )".
-              # MasterCard
-              "   OR substring($table.payinfo from 1 for 2) = '51' ".
-              "   OR substring($table.payinfo from 1 for 2) = '52' ".
-              "   OR substring($table.payinfo from 1 for 2) = '53' ".
-              "   OR substring($table.payinfo from 1 for 2) = '54' ".
-              "   OR substring($table.payinfo from 1 for 2) = '54' ".
-              "   OR substring($table.payinfo from 1 for 2) = '55' ".
-              "   OR substring($table.payinfo from 1 for 4) $similar_to '222[1-9]' ".
-              "   OR substring($table.payinfo from 1 for 3) $similar_to '22[3-9]' ".
-              "   OR substring($table.payinfo from 1 for 2) $similar_to '2[3-6]' ".
-              "   OR substring($table.payinfo from 1 for 3) $similar_to '27[0-1]' ".
-              "   OR substring($table.payinfo from 1 for 4) = '2720' ".
-              "   OR substring($table.payinfo from 1 for 3) = '2[2-7]x' ".
-              " ) ";
-
-          } elsif ( $cardtype eq 'Amex' ) {
-
-            $search =
-              " (    substring($table.payinfo from 1 for 2 ) = '34' ".
-              "   OR substring($table.payinfo from 1 for 2 ) = '37' ".
-              " ) ";
-
-          } elsif ( $cardtype eq 'Discover' ) {
-
-            my $country = $conf->config('countrydefault') || 'US';
-
-            $search =
-              " (    substring($table.payinfo from 1 for 4 ) = '6011'  ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '60x'   ".
-              "   OR substring($table.payinfo from 1 for 2 ) = '65'    ".
-
-              # diner's 300-305 / 3095
-              "   OR substring($table.payinfo from 1 for 3 ) = '300'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '301'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '302'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '303'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '304'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '305'   ".
-              "   OR substring($table.payinfo from 1 for 4 ) = '3095'  ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '30x'   ".
-
-              # diner's 36, 38, 39
-              "   OR substring($table.payinfo from 1 for 2 ) = '36'    ".
-              "   OR substring($table.payinfo from 1 for 2 ) = '38'    ".
-              "   OR substring($table.payinfo from 1 for 2 ) = '39'    ".
-
-              "   OR substring($table.payinfo from 1 for 3 ) = '644'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '645'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '646'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '647'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '648'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '649'   ".
-              "   OR substring($table.payinfo from 1 for 3 ) = '64x'   ".
-
-              # JCB cards in the 3528-3589 range identified as Discover inside US & territories (NOT Canada)
-              ( $country =~ /^(US|PR|VI|MP|PW|GU)$/
-               ?" OR substring($table.payinfo from 1 for 4 ) = '3528'  ".
-                " OR substring($table.payinfo from 1 for 4 ) = '3529'  ".
-                " OR substring($table.payinfo from 1 for 3 ) = '353'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '354'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '355'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '356'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '357'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '358'   ".
-                " OR substring($table.payinfo from 1 for 3 ) = '35x'   "
-               :""
-              ).
-
-              #China Union Pay processed as Discover in US, Mexico and Caribbean
-              ( $country =~ /^(US|MX|AI|AG|AW|BS|BB|BM|BQ|VG|KY|CW|DM|DO|GD|GP|JM|MQ|MS|BL|KN|LC|VC|MF|SX|TT|TC)$/
-               ?" OR substring($table.payinfo from 1 for 3 ) $similar_to '62[24-68x]'   "
-               :""
-              ).
-
-              " ) ";
-
-          } elsif ( $cardtype eq 'Maestro' ) {
-
-            $search =
-              " (    substring($table.payinfo from 1 for 2 ) = '63'     ".
-              "   OR substring($table.payinfo from 1 for 2 ) = '67'     ".
-              "   OR substring($table.payinfo from 1 for 6 ) = '564182' ".
-              "   OR substring($table.payinfo from 1 for 4 ) = '4936'   ".
-              "   OR substring($table.payinfo from 1 for 6 )            ".
-              "      $similar_to '49030[2-9]'                             ".
-              "   OR substring($table.payinfo from 1 for 6 )            ".
-              "      $similar_to '49033[5-9]'                             ".
-              "   OR substring($table.payinfo from 1 for 6 )            ".
-              "      $similar_to '49110[1-2]'                             ".
-              "   OR substring($table.payinfo from 1 for 6 )            ".
-              "      $similar_to '49117[4-9]'                             ".
-              "   OR substring($table.payinfo from 1 for 6 )            ".
-              "      $similar_to '49118[1-2]'                             ".
-              " ) ";
-
-          } elsif ( $cardtype eq 'Tokenized' ) {
-
-            $search = " substring($table.payinfo from 1 for 2 ) = '99' ";
+      foreach my $payby_string ( $cgi->param('payby') ) {
+
+        my $payby_search;
+
+        my ($payby, $subtype) = split('-', $payby_string);
+        # make sure it exists and is a transaction type
+        if ( FS::payby->payment_payby2longname($payby) ) {
+          $payby_search = "$table.payby = " . dbh->quote($payby);
+        } else {
+          die "illegal payby $payby_string";
+        }
+
+        if ( $subtype ) {
+
+          if ( $subtype eq 'Tokenized' ) {
+
+            $payby_search .= " AND substring($table.payinfo from 1 for 2 ) = '99' ";
+            # XXX should store the cardtype as 'Tokenized' in this case?
 
           } else {
-            die "unknown card type $cardtype";
-          }
 
-          my $masksearch = $search;
-          $masksearch =~ s/$table\.payinfo/$table.paymask/gi;
+            my $in_cardtype = $cardtype_of{$subtype}
+              or die "unknown card type $subtype";
+            $payby_search .= " AND $table.paycardtype IN($in_cardtype)";
 
-          $payby_search = "( $payby_search AND ( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) ) )";
+          }
 
         }
 
@@ -610,6 +499,8 @@ if ( $cgi->param('magic') ) {
     'addl_from' => $addl_from,
   };
 
+warn Dumper \$sql_query;
+
 } else {
 
   #hmm... is this still used?

commit 17f3394d929e83ed3b58916dcc40eac5c87bb5b4
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Jul 15 15:50:27 2016 -0700

    rename cardtype to paycardtype

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 60593ad..eab0c19 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1709,7 +1709,7 @@ sub tables_hashref {
         'weight',          'int', 'NULL',        '', '', '', 
         'payby',          'char',     '',         4, '', '', 
         'payinfo',     'varchar', 'NULL',       512, '', '', 
-        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paycardtype', 'varchar', 'NULL',   $char_d, '', '',
         'paycvv',      'varchar', 'NULL',       512, '', '', 
         'paymask',     'varchar', 'NULL',   $char_d, '', '', 
         #'paydate',   @date_type, '', '', 
@@ -2459,7 +2459,7 @@ sub tables_hashref {
         'usernum',         'int', 'NULL',      '', '', '',
         'payby',          'char',     '',       4, '', '',
         'payinfo',     'varchar', 'NULL',     512, '', '',
-        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paycardtype', 'varchar', 'NULL',   $char_d, '', '',
         'paymask',     'varchar', 'NULL', $char_d, '', '', 
         'paydate',     'varchar', 'NULL',      10, '', '', 
         'paybatch',    'varchar', 'NULL', $char_d, '', '',#for auditing purposes
@@ -2517,7 +2517,7 @@ sub tables_hashref {
         'usernum',         'int', 'NULL',      '', '', '',
         'payby',          'char',     '',       4, '', '',
         'payinfo',     'varchar', 'NULL',     512, '', '',
-        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paycardtype', 'varchar', 'NULL',   $char_d, '', '',
         'paymask',     'varchar', 'NULL', $char_d, '', '', 
         #'paydate' ?
         'paybatch',    'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
@@ -3078,7 +3078,7 @@ sub tables_hashref {
                                                      # be index into payby
                                                      # table eventually
         'payinfo',      'varchar',   'NULL', 512, '', '', #see cust_main above
-        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paycardtype',  'varchar', 'NULL',   $char_d, '', '',
         'paymask', 'varchar', 'NULL', $char_d, '', '', 
         'paybatch',     'varchar',   'NULL', $char_d, '', '', 
         'closed',    'char', 'NULL', 1, '', '', 
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index f93c545..6e2a62c 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -428,7 +428,7 @@ sub upgrade_data {
     'cust_refund' => [],
     'banned_pay' => [],
 
-    #cardtype
+    #paycardtype
     'cust_payby' => [],
 
     #default namespace
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index 8e87745..e0a7143 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -97,7 +97,7 @@ Payment Type (See L<FS::payinfo_Mixin> for valid values)
 
 Payment Information (See L<FS::payinfo_Mixin> for data format)
 
-=item cardtype
+=item paycardtype
 
 Credit card type, if appropriate; autodetected.
 
@@ -1211,7 +1211,7 @@ sub _upgrade_data {  #class method
   }
 
   ###
-  # set cardtype
+  # set paycardtype
   ###
   $class->upgrade_set_cardtype;
 
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index 993bab5..facb9c8 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -115,7 +115,7 @@ paytype
 
 payip
 
-=item cardtype
+=item paycardtype
 
 The credit card type (deduced from the card number).
 
@@ -354,10 +354,11 @@ sub check {
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
 
     my $cardtype = cardtype($payinfo);
-    $self->set('cardtype', $cardtype);
-    return gettext('unknown_card_type')
-      if $self->payinfo !~ /^99\d{14}$/ #token
-      && $cardtype eq "Unknown";
+    $cardtype = 'Tokenized' if $self->payinfo !~ /^99\d{14}$/; #token
+    
+    return gettext('unknown_card_type') if $cardtype eq "Unknown";
+    
+    $self->set('paycardtype', $cardtype);
 
     unless ( $ignore_banned_card ) {
       my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
@@ -453,9 +454,9 @@ sub check {
     # either ignoring invalid cards, or we can't decrypt the payinfo, but
     # try to detect the card type anyway. this never returns failure, so
     # the contract of $ignore_invalid_cards is maintained.
-    $self->set('cardtype', cardtype($self->paymask));
+    $self->set('paycardtype', cardtype($self->paymask));
   } else {
-    $self->set('cardtype', '');
+    $self->set('paycardtype', '');
   }
 
 #  } elsif ( $self->payby eq 'PREPAY' ) {
@@ -539,11 +540,14 @@ sub check_payinfo_cardtype {
   my $payinfo = $self->payinfo;
   $payinfo =~ s/\D//g;
 
-  return '' if $payinfo =~ /^99\d{14}$/; #token
+  if ( $payinfo =~ /^99\d{14}$/ ) {
+    $self->set('paycardtype', 'Tokenized');
+    return '';
+  }
 
   my %bop_card_types = map { $_=>1 } values %{ card_types() };
   my $cardtype = cardtype($payinfo);
-  $self->set('cardtype', $cardtype);
+  $self->set('paycardtype', $cardtype);
 
   return "$cardtype not accepted" unless $bop_card_types{$cardtype};
 
@@ -619,7 +623,7 @@ sub label {
   my $self = shift;
 
   my $name = $self->payby =~ /^(CARD|DCRD)$/
-              && $self->cardtype || FS::payby->shortname($self->payby);
+              && $self->paycardtype || FS::payby->shortname($self->payby);
 
   ( $self->payby =~ /^(CARD|CHEK)$/  ? $weight{$self->weight}. ' automatic '
                                      : 'Manual '
diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm
index ee144c1..4d2baa5 100644
--- a/FS/FS/cust_refund.pm
+++ b/FS/FS/cust_refund.pm
@@ -82,7 +82,7 @@ Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
 
 Payment Information (See L<FS::payinfo_Mixin> for data format)
 
-=item cardtype
+=item paycardtype
 
 Detected credit card type, if appropriate; autodetected.
 
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index b32f13b..a61125e 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -194,9 +194,12 @@ sub payinfo_check {
     or return "Illegal payby: ". $self->payby;
 
   if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
+
     my $payinfo = $self->payinfo;
     my $cardtype = cardtype($payinfo);
-    $self->set('cardtype', $cardtype);
+    $cardtype = 'Tokenized' if $payinfo !~ /^99\d{14}$/;
+    $self->set('paycardtype', $cardtype);
+
     if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {
       # allow it
     } else {
@@ -207,8 +210,7 @@ sub payinfo_check {
           or return "Illegal (mistyped?) credit card number (payinfo)";
         $self->payinfo($1);
         validate($self->payinfo) or return "Illegal credit card number";
-        return "Unknown card type" if $self->payinfo !~ /^99\d{14}$/ #token
-                                   && $cardtype eq "Unknown";
+        return "Unknown card type" if $cardtype eq "Unknown";
       } else {
         $self->payinfo('N/A'); #???
       }
@@ -216,9 +218,9 @@ sub payinfo_check {
   } else {
     if ( $self->payby eq 'CARD' and $self->paymask ) {
       # if we can't decrypt the card, at least detect the cardtype
-      $self->set('cardtype', cardtype($self->paymask));
+      $self->set('paycardtype', cardtype($self->paymask));
     } else {
-      $self->set('cardtype', '');
+      $self->set('paycardtype', '');
     }
     if ( $self->is_encrypted($self->payinfo) ) {
       #something better?  all it would cause is a decryption error anyway?
@@ -415,8 +417,8 @@ sub paydate_epoch_sql {
 
 =item upgrade_set_cardtype
 
-Find all records with a credit card payment type and no cardtype, and
-replace them in order to set their cardtype.
+Find all records with a credit card payment type and no paycardtype, and
+replace them in order to set their paycardtype.
 
 =cut
 
@@ -427,7 +429,7 @@ sub upgrade_set_cardtype {
   local $ignore_masked_payinfo = 1;
   my $search = FS::Cursor->new({
     table     => $class->table,
-    extra_sql => q[ WHERE payby IN('CARD','DCRD') AND cardtype IS NULL ],
+    extra_sql => q[ WHERE payby IN('CARD','DCRD') AND paycardtype IS NULL ],
   });
   while (my $record = $search->fetch) {
     my $error = $record->replace;

commit 666c35e4745dc6b5518f53267aadb3233f7cddb8
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Jul 14 23:32:19 2016 -0700

    store credit card type in cust_payby and transaction records, #71291, schema support

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 748df8b..60593ad 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2459,6 +2459,7 @@ sub tables_hashref {
         'usernum',         'int', 'NULL',      '', '', '',
         'payby',          'char',     '',       4, '', '',
         'payinfo',     'varchar', 'NULL',     512, '', '',
+        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
         'paymask',     'varchar', 'NULL', $char_d, '', '', 
         'paydate',     'varchar', 'NULL',      10, '', '', 
         'paybatch',    'varchar', 'NULL', $char_d, '', '',#for auditing purposes
@@ -2516,7 +2517,8 @@ sub tables_hashref {
         'usernum',         'int', 'NULL',      '', '', '',
         'payby',          'char',     '',       4, '', '',
         'payinfo',     'varchar', 'NULL',     512, '', '',
-	'paymask',     'varchar', 'NULL', $char_d, '', '', 
+        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paymask',     'varchar', 'NULL', $char_d, '', '', 
         #'paydate' ?
         'paybatch',    'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
         'closed',        'char',  'NULL',       1, '', '', 
@@ -3076,7 +3078,8 @@ sub tables_hashref {
                                                      # be index into payby
                                                      # table eventually
         'payinfo',      'varchar',   'NULL', 512, '', '', #see cust_main above
-	'paymask', 'varchar', 'NULL', $char_d, '', '', 
+        'cardtype',    'varchar', 'NULL',   $char_d, '', '',
+        'paymask', 'varchar', 'NULL', $char_d, '', '', 
         'paybatch',     'varchar',   'NULL', $char_d, '', '', 
         'closed',    'char', 'NULL', 1, '', '', 
         'source_paynum', 'int', 'NULL', '', '', '', # link to cust_payby, to prevent unapply of gateway-generated refunds
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index 3ff943f..f93c545 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -428,6 +428,9 @@ sub upgrade_data {
     'cust_refund' => [],
     'banned_pay' => [],
 
+    #cardtype
+    'cust_payby' => [],
+
     #default namespace
     'payment_gateway' => [],
 
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index 331a156..8e87745 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -97,6 +97,10 @@ Payment Type (See L<FS::payinfo_Mixin> for valid values)
 
 Payment Information (See L<FS::payinfo_Mixin> for data format)
 
+=item cardtype
+
+Credit card type, if appropriate; autodetected.
+
 =item paymask
 
 Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
@@ -1205,6 +1209,12 @@ sub _upgrade_data {  #class method
       process_upgrade_paybatch();
     }
   }
+
+  ###
+  # set cardtype
+  ###
+  $class->upgrade_set_cardtype;
+
 }
 
 sub process_upgrade_paybatch {
diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm
index 8d37a58..29540d1 100644
--- a/FS/FS/cust_pay_void.pm
+++ b/FS/FS/cust_pay_void.pm
@@ -74,6 +74,10 @@ Payment Type (See L<FS::payinfo_Mixin> for valid values)
 
 card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
 
+=item cardtype
+
+Credit card type, if appropriate.
+
 =item paybatch
 
 text field for tracking card processing
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index 62fa9be..993bab5 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -115,6 +115,9 @@ paytype
 
 payip
 
+=item cardtype
+
+The credit card type (deduced from the card number).
 
 =back
 
@@ -331,6 +334,13 @@ sub check {
   # Need some kind of global flag to accept invalid cards, for testing
   # on scrubbed data.
   #XXX if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
+
+  # In this block: detect card type; reject credit card / account numbers that
+  # are impossible or banned; reject other payment features (date, CVV length)
+  # that are inappropriate for the card type.
+  # However, if the payinfo is encrypted then just detect card type and assume
+  # the other checks were already done.
+
   if ( !$ignore_invalid_card && 
     $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
 
@@ -343,9 +353,11 @@ sub check {
     validate($payinfo)
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
 
+    my $cardtype = cardtype($payinfo);
+    $self->set('cardtype', $cardtype);
     return gettext('unknown_card_type')
       if $self->payinfo !~ /^99\d{14}$/ #token
-      && cardtype($self->payinfo) eq "Unknown";
+      && $cardtype eq "Unknown";
 
     unless ( $ignore_banned_card ) {
       my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
@@ -367,7 +379,7 @@ sub check {
     }
 
     if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) {
-      if ( cardtype($self->payinfo) eq 'American Express card' ) {
+      if ( $cardtype eq 'American Express card' ) {
         $self->paycvv =~ /^(\d{4})$/
           or return "CVV2 (CID) for American Express cards is four digits.";
         $self->paycvv($1);
@@ -380,7 +392,6 @@ sub check {
       $self->paycvv('');
     }
 
-    my $cardtype = cardtype($payinfo);
     if ( $cardtype =~ /^(Switch|Solo)$/i ) {
 
       return "Start date or issue number is required for $cardtype cards"
@@ -438,6 +449,15 @@ sub check {
       }
     }
 
+  } elsif ( $self->payby =~ /^CARD|DCRD$/ and $self->paymask ) {
+    # either ignoring invalid cards, or we can't decrypt the payinfo, but
+    # try to detect the card type anyway. this never returns failure, so
+    # the contract of $ignore_invalid_cards is maintained.
+    $self->set('cardtype', cardtype($self->paymask));
+  } else {
+    $self->set('cardtype', '');
+  }
+
 #  } elsif ( $self->payby eq 'PREPAY' ) {
 #
 #    my $payinfo = $self->payinfo;
@@ -449,8 +469,6 @@ sub check {
 #      unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
 #    $self->paycvv('');
 
-  }
-
   if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
 
     $self->paydate('');
@@ -458,6 +476,7 @@ sub check {
   } elsif ( $self->payby =~ /^(CARD|DCRD)$/ ) {
 
     # shouldn't payinfo_check do this?
+    # (except we don't ever call payinfo_check from here)
     return "Expiration date required"
       if $self->paydate eq '' || $self->paydate eq '-';
 
@@ -524,6 +543,7 @@ sub check_payinfo_cardtype {
 
   my %bop_card_types = map { $_=>1 } values %{ card_types() };
   my $cardtype = cardtype($payinfo);
+  $self->set('cardtype', $cardtype);
 
   return "$cardtype not accepted" unless $bop_card_types{$cardtype};
 
@@ -599,7 +619,7 @@ sub label {
   my $self = shift;
 
   my $name = $self->payby =~ /^(CARD|DCRD)$/
-              && cardtype($self->paymask) || FS::payby->shortname($self->payby);
+              && $self->cardtype || FS::payby->shortname($self->payby);
 
   ( $self->payby =~ /^(CARD|CHEK)$/  ? $weight{$self->weight}. ' automatic '
                                      : 'Manual '
@@ -872,6 +892,18 @@ sub search_sql {
 
 =back
 
+=cut
+
+sub _upgrade_data {
+
+  my $class = shift;
+  local $ignore_banned_card = 1;
+  local $ignore_expired_card = 1;
+  local $ignore_invalid_card = 1;
+  $class->upgrade_set_cardtype;
+
+}
+
 =head1 BUGS
 
 =head1 SEE ALSO
diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm
index ced9540..ee144c1 100644
--- a/FS/FS/cust_refund.pm
+++ b/FS/FS/cust_refund.pm
@@ -82,6 +82,10 @@ Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
 
 Payment Information (See L<FS::payinfo_Mixin> for data format)
 
+=item cardtype
+
+Detected credit card type, if appropriate; autodetected.
+
 =item paymask
 
 Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
@@ -472,6 +476,9 @@ sub _upgrade_data {  # class method
   my ($class, %opts) = @_;
   $class->_upgrade_reasonnum(%opts);
   $class->_upgrade_otaker(%opts);
+
+  local $ignore_empty_reasonnum = 1;
+  $class->upgrade_set_cardtype;
 }
 
 =back
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index 4176818..b32f13b 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -5,6 +5,7 @@ use Business::CreditCard;
 use FS::payby;
 use FS::Record qw(qsearch);
 use FS::UID qw(driver_name);
+use FS::Cursor;
 use Time::Local qw(timelocal);
 
 use vars qw($ignore_masked_payinfo);
@@ -194,6 +195,8 @@ sub payinfo_check {
 
   if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
     my $payinfo = $self->payinfo;
+    my $cardtype = cardtype($payinfo);
+    $self->set('cardtype', $cardtype);
     if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {
       # allow it
     } else {
@@ -205,12 +208,18 @@ sub payinfo_check {
         $self->payinfo($1);
         validate($self->payinfo) or return "Illegal credit card number";
         return "Unknown card type" if $self->payinfo !~ /^99\d{14}$/ #token
-                                   && cardtype($self->payinfo) eq "Unknown";
+                                   && $cardtype eq "Unknown";
       } else {
         $self->payinfo('N/A'); #???
       }
     }
   } else {
+    if ( $self->payby eq 'CARD' and $self->paymask ) {
+      # if we can't decrypt the card, at least detect the cardtype
+      $self->set('cardtype', cardtype($self->paymask));
+    } else {
+      $self->set('cardtype', '');
+    }
     if ( $self->is_encrypted($self->payinfo) ) {
       #something better?  all it would cause is a decryption error anyway?
       my $error = $self->ut_anything('payinfo');
@@ -404,6 +413,28 @@ sub paydate_epoch_sql {
   END"
 }
 
+=item upgrade_set_cardtype
+
+Find all records with a credit card payment type and no cardtype, and
+replace them in order to set their cardtype.
+
+=cut
+
+sub upgrade_set_cardtype {
+  my $class = shift;
+  # assign cardtypes to CARD/DCRDs that need them; check_payinfo_cardtype
+  # will do this. ignore any problems with the cards.
+  local $ignore_masked_payinfo = 1;
+  my $search = FS::Cursor->new({
+    table     => $class->table,
+    extra_sql => q[ WHERE payby IN('CARD','DCRD') AND cardtype IS NULL ],
+  });
+  while (my $record = $search->fetch) {
+    my $error = $record->replace;
+    die $error if $error;
+  }
+}
+
 =back
 
 =head1 BUGS

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

Summary of changes:
 FS/FS/Schema.pm                                    |    9 +-
 FS/FS/Upgrade.pm                                   |    3 +
 FS/FS/cust_pay.pm                                  |   10 ++
 FS/FS/cust_pay_void.pm                             |    4 +
 FS/FS/cust_payby.pm                                |   54 +++++-
 FS/FS/cust_refund.pm                               |    7 +
 FS/FS/msg_template.pm                              |    1 +
 FS/FS/payinfo_Mixin.pm                             |   37 +++-
 httemplate/search/elements/cust_pay_or_refund.html |  179 ++++----------------
 9 files changed, 146 insertions(+), 158 deletions(-)




More information about the freeside-commits mailing list