74355: freeside-upgrade error N/A (tokenized) [payinfo_check return bug fix, already...
[freeside.git] / FS / FS / payinfo_Mixin.pm
index 5c4acf7..b30ea35 100644 (file)
@@ -4,8 +4,11 @@ use strict;
 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);
+use vars qw( $ignore_masked_payinfo $allow_closed_replace );
 
 =head1 NAME
 
@@ -38,14 +41,15 @@ For Customers (cust_main):
 For Refunds (cust_refund):
 'CARD' (credit cards), 'CHEK' (electronic check/ACH),
 'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
-'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free)
+'WEST' (Western Union), 'MCRD' (Manual credit card), 'MCHK' (Manual electronic
+check), 'CBAK' Chargeback, or 'COMP' (free)
 
 
 For Payments (cust_pay):
 'CARD' (credit cards), 'CHEK' (electronic check/ACH),
 'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
-'CASH' (cash), 'WEST' (Western Union), 'MCRD' (Manual credit card),
-'PPAL' (PayPal)
+'CASH' (cash), 'WEST' (Western Union), 'MCRD' (Manual credit card), 'MCHK'
+(Manual electronic check), 'PPAL' (PayPal)
 'COMP' (free) is depricated as a payment type in cust_pay
 
 =cut 
@@ -133,7 +137,11 @@ sub mask_payinfo {
     return 'N/A (tokenized)'; #?
   } else { # if not, mask it...
 
-    if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') {
+    if ($payby eq 'CARD' || $payby eq 'DCRD') {
+                                                #|| $payby eq 'MCRD') {
+                                                #MCRD isn't a card in payinfo,
+                                                #its a record of an _offline_
+                                                #card
 
       # Credit Cards
 
@@ -163,6 +171,13 @@ sub mask_payinfo {
              substr($account,(length($account)-2)).
              ( length($aba) ? "@".$aba : '');
 
+    } elsif ($payby eq 'EDI') {
+      # EDI.
+      # These numbers have been seen anywhere from 8 to 30 digits, and 
+      # possibly more.  Lacking any better idea I'm going to mask all but
+      # the last 4 digits.
+      return 'x' x (length($payinfo) - 4) . substr($payinfo, -4);
+
     } else { # Tie up loose ends
       return $payinfo;
     }
@@ -174,23 +189,6 @@ sub mask_payinfo {
 
 Checks payby and payinfo.
 
-For Customers (cust_main):
-'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
-'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
-'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or
-'PREPAY' (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>)
-
-For Refunds (cust_refund):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
-'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' (Chargeback),  or 'COMP' (free)
-
-For Payments (cust_pay):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
-'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
-'COMP' (free) is depricated as a payment type in cust_pay
-
 =cut
 
 sub payinfo_check {
@@ -200,10 +198,17 @@ sub payinfo_check {
     or return "Illegal payby: ". $self->payby;
 
   if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
-    my $payinfo = $self->payinfo;
+
+    if ( $self->payinfo =~ /^99\d{14}$/ && ! $self->paycardtype ) {
+      return "paycardtype required (cannot be derived from a token)";
+    } else {
+      $self->set('paycardtype', cardtype($self->payinfo));
+    }
+
     if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {
       # allow it
     } else {
+      my $payinfo = $self->payinfo;
       $payinfo =~ s/\D//g;
       $self->payinfo($payinfo);
       if ( $self->payinfo ) {
@@ -211,13 +216,18 @@ 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($self->payinfo) eq "Unknown";
+        return "Unknown card type" if $self->paycardtype 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('paycardtype', cardtype($self->paymask));
+    } else {
+      $self->set('paycardtype', '');
+    }
     if ( $self->is_encrypted($self->payinfo) ) {
       #something better?  all it would cause is a decryption error anyway?
       my $error = $self->ut_anything('payinfo');
@@ -228,9 +238,10 @@ sub payinfo_check {
     }
   }
 
+  return '';
 }
 
-=item payby_payinfo_pretty
+=item payby_payinfo_pretty [ LOCALE ]
 
 Returns payment method and information (suitably masked, if applicable) as
 a human-readable string, such as:
@@ -245,22 +256,44 @@ or
 
 sub payby_payinfo_pretty {
   my $self = shift;
+  my $locale = shift;
+  my $lh = FS::L10N->get_handle($locale);
   if ( $self->payby eq 'CARD' ) {
-    'Card #'. $self->paymask;
+    if ($self->paymask =~ /tokenized/) {
+      $lh->maketext('Tokenized Card');
+    } else {
+      $lh->maketext('Card #') . $self->paymask;
+    }
   } elsif ( $self->payby eq 'CHEK' ) {
-    'E-check acct#'. $self->payinfo;
+
+    #false laziness w/view/cust_main/payment_history.html::translate_payinfo
+    my( $account, $aba ) = split('@', $self->paymask );
+
+    if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada
+      my($branch, $routing) = ($1, $2);
+      $lh->maketext("Routing [_1], Branch [_2], Acct [_3]",
+                     $routing, $branch, $account);
+    } else {
+      $lh->maketext("Routing [_1], Acct [_2]", $aba, $account);
+    }
+
   } elsif ( $self->payby eq 'BILL' ) {
-    'Check #'. $self->payinfo;
+    $lh->maketext('Check #') . $self->payinfo;
   } elsif ( $self->payby eq 'PREP' ) {
-    'Prepaid card #'. $self->payinfo;
+    $lh->maketext('Prepaid card #') . $self->payinfo;
   } elsif ( $self->payby eq 'CASH' ) {
-    'Cash '. $self->payinfo;
+    $lh->maketext('Cash') . ' ' . $self->payinfo;
   } elsif ( $self->payby eq 'WEST' ) {
-    'Western Union'; #. $self->payinfo;
+    # does Western Union localize their name?
+    $lh->maketext('Western Union');
   } elsif ( $self->payby eq 'MCRD' ) {
-    'Manual credit card'; #. $self->payinfo;
+    $lh->maketext('Manual credit card');
+  } elsif ( $self->payby eq 'MCHK' ) {
+    $lh->maketext('Manual electronic check');
+  } elsif ( $self->payby eq 'EDI' ) {
+    $lh->maketext('EDI') . ' ' . $self->paymask;
   } elsif ( $self->payby eq 'PPAL' ) {
-    'PayPal transaction#' . $self->order_number;
+    $lh->maketext('PayPal transaction#') . $self->order_number;
   } else {
     $self->payby. ' '. $self->payinfo;
   }
@@ -278,18 +311,39 @@ sub payinfo_used {
   my $payinfo = shift || $self->payinfo;
   my %hash = (
     'custnum' => $self->custnum,
-    'payby'   => 'CARD',
+    'payby'   => $self->payby,
   );
 
   return 1
   if qsearch('cust_pay', { %hash, 'payinfo' => $payinfo } )
-  || qsearch('cust_pay', 
-    { %hash, 'paymask' => $self->mask_payinfo('CARD', $payinfo) }  )
+  || qsearch('cust_pay', { %hash, 'paymask' => $self->mask_payinfo } )
   ;
 
   return 0;
 }
 
+=item upgrade_set_cardtype
+
+Find all records with a credit card payment type and no paycardtype, and
+replace them in order to set their paycardtype.
+
+=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 paycardtype IS NULL ],
+  });
+  while (my $record = $search->fetch) {
+    my $error = $record->replace;
+    die $error if $error;
+  }
+}
+
 =back
 
 =head1 BUGS