only tokenize cards
[freeside.git] / FS / FS / cust_main / Billing_Realtime.pm
index 3a3947b..f089059 100644 (file)
@@ -416,6 +416,13 @@ sub realtime_bop {
   # set fields from passed cust_payby
   _bop_cust_payby_options(\%options);
 
+  # check for banned credit card/ACH
+  my $ban = FS::banned_pay->ban_search(
+    'payby'   => $bop_method2payby{$options{method}},
+    'payinfo' => $options{payinfo},
+  );
+  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
+
   # possibly run a separate transaction to tokenize card number,
   #   so that we never store tokenized card info in cust_pay_pending
   if (($options{method} eq 'CC') && !$self->tokenized($options{'payinfo'})) {
@@ -502,16 +509,6 @@ sub realtime_bop {
   die $@ if $@;
 
   ###
-  # check for banned credit card/ACH
-  ###
-
-  my $ban = FS::banned_pay->ban_search(
-    'payby'   => $bop_method2payby{$options{method}},
-    'payinfo' => $options{payinfo},
-  );
-  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
-
-  ###
   # check for term discount validity
   ###
 
@@ -1454,9 +1451,10 @@ sub realtime_refund_bop {
       ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
     }
 
+    my $payment_gateway;
     if ( $gatewaynum ) { #gateway for the payment to be refunded
 
-      my $payment_gateway =
+      $payment_gateway =
         qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
       die "payment gateway $gatewaynum not found"
         unless $payment_gateway;
@@ -1470,7 +1468,7 @@ sub realtime_refund_bop {
     } else { #try the default gateway
 
       my $conf_processor;
-      my $payment_gateway =
+      $payment_gateway =
         $self->agent->payment_gateway('method' => $options{method});
 
       ( $conf_processor, $login, $password, $namespace ) =
@@ -1487,8 +1485,27 @@ sub realtime_refund_bop {
         unless ($processor eq $conf_processor)
             || (($conf_processor eq 'CardFortress') && ($processor eq $bop_options{'gateway'}));
 
+      $processor = $conf_processor;
+
     }
 
+    # if gateway has switched to CardFortress but token_check hasn't run yet,
+    # tokenize just this record now, so that token gets passed/set appropriately
+    if ($cust_pay->payby eq 'CARD' && !$cust_pay->tokenized) {
+      my %tokenopts = (
+        'payment_gateway' => $payment_gateway,
+        'method'          => 'CC',
+        'payinfo'         => $cust_pay->payinfo,
+        'paydate'         => $cust_pay->paydate,
+      );
+      my $error = $self->realtime_tokenize(\%tokenopts); # no-op unless gateway can tokenize
+      if ($self->tokenized($tokenopts{'payinfo'})) { # implies no error
+        warn "  tokenizing cust_pay\n" if $DEBUG > 1;
+        $cust_pay->payinfo($tokenopts{'payinfo'});
+        $error = $cust_pay->replace;
+      }
+      return $error if $error;
+    }
 
   } else { # didn't specify a paynum, so look for agent gateway overrides
            # like a normal transaction 
@@ -1773,6 +1790,13 @@ sub realtime_verify_bop {
   return "No cust_payby" unless $options{'cust_payby'};
   _bop_cust_payby_options(\%options);
 
+  # check for banned credit card/ACH
+  my $ban = FS::banned_pay->ban_search(
+    'payby'   => $bop_method2payby{'CC'},
+    'payinfo' => $options{payinfo},
+  );
+  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
+
   # possibly run a separate transaction to tokenize card number,
   #   so that we never store tokenized card info in cust_pay_pending
   if (($options{method} eq 'CC') && !$self->tokenized($options{'payinfo'})) {
@@ -1793,16 +1817,6 @@ sub realtime_verify_bop {
   die $@ if $@;
 
   ###
-  # check for banned credit card/ACH
-  ###
-
-  my $ban = FS::banned_pay->ban_search(
-    'payby'   => $bop_method2payby{'CC'},
-    'payinfo' => $options{payinfo},
-  );
-  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
-
-  ###
   # massage data
   ###
 
@@ -2210,6 +2224,13 @@ sub realtime_tokenize {
   return '' unless $options{method} eq 'CC';
   return '' if $self->tokenized($options{payinfo}); #already tokenized
 
+  # check for banned credit card/ACH
+  my $ban = FS::banned_pay->ban_search(
+    'payby'   => $bop_method2payby{'CC'},
+    'payinfo' => $options{payinfo},
+  );
+  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
+
   ###
   # select a gateway
   ###
@@ -2238,16 +2259,6 @@ sub realtime_tokenize {
                 && grep /^Tokenize$/, @{$supported_actions{'CC'}};
 
   ###
-  # check for banned credit card/ACH
-  ###
-
-  my $ban = FS::banned_pay->ban_search(
-    'payby'   => $bop_method2payby{'CC'},
-    'payinfo' => $options{payinfo},
-  );
-  return "Banned credit card" if $ban && $ban->bantype ne 'warn';
-
-  ###
   # massage data
   ###
 
@@ -2388,8 +2399,9 @@ sub token_check {
 
   my $cache = {}; #cache for module info
 
-  # look for a gateway that can't tokenize
+  # look for a gateway that can and can't tokenize
   my $require_tokenized = 1;
+  my $someone_tokenizing = 0;
   foreach my $gateway (
     FS::payment_gateway->all_gateways(
       'method'  => 'CC',
@@ -2401,18 +2413,26 @@ sub token_check {
       # no default gateway, no promise to tokenize
       # can just load other gateways as-needeed below
       $require_tokenized = 0;
-      last;
+      last if $someone_tokenizing;
+      next;
     }
     my $info = _token_check_gateway_info($cache,$gateway);
     die $info unless ref($info); # means it's an error message
-    unless ($info->{'can_tokenize'}) {
+    if ($info->{'can_tokenize'}) {
+      $someone_tokenizing = 1;
+    } else {
       # a configured gateway can't tokenize, that's all we need to know right now
       # can just load other gateways as-needeed below
       $require_tokenized = 0;
-      last;
+      last if $someone_tokenizing;
     }
   }
 
+  unless ($someone_tokenizing) { #no need to check, if no one can tokenize
+    warn "no gateways tokenize\n" if $debug;
+    return;
+  }
+
   warn "REQUIRE TOKENIZED" if $require_tokenized && $debug;
 
   # upgrade does not call this with autocommit turned on,
@@ -2443,7 +2463,7 @@ CUSTLOOP:
       }
 
       if ($require_tokenized && $opt{'daily'}) {
-        $log->critical("Untokenized card number detected in cust_payby ".$cust_payby->custpaybynum);
+        $log->info("Untokenized card number detected in cust_payby ".$cust_payby->custpaybynum. '; tokenizing');
         $dbh->commit or die $dbh->errstr; # commit log message
       }
 
@@ -2544,7 +2564,7 @@ CUSTLOOP:
       }
 
       if ($require_tokenized && $opt{'daily'}) {
-        $log->critical("Untokenized card number detected in $table ".$record->get($record->primary_key));
+        $log->info("Untokenized card number detected in $table ".$record->get($record->primary_key). ';tokenizing');
         $dbh->commit or die $dbh->errstr; # commit log message
       }
 
@@ -2553,7 +2573,11 @@ CUSTLOOP:
         # might happen for cust_pay_pending from failed verify records,
         #   in which case we attempt tokenization without cust_main
         # everything else should absolutely have a cust_main
-        unless ($table eq 'cust_pay_pending' && $record->{'custnum_pending'}) {
+        if ($table eq 'cust_pay_pending' and !$record->custnum ) {
+          # override the usual safety check and allow the record to be
+          # updated even without a custnum.
+          $record->set('custnum_pending', 1);
+        } else {
           my $error = "Could not load cust_main for $table ".$record->get($record->primary_key);
           if ($opt{'queue'}) {
             $log->critical($error);
@@ -2635,7 +2659,7 @@ CUSTLOOP:
       warn "ATTEMPTING GATEWAY-ONLY TOKENIZE" if $debug && !$cust_main;
 
       # if we got this far, time to mutex
-      $record = $record->select_for_update;
+      $record->select_for_update;
 
       # no clear record of name/address/etc used for transaction,
       # but will load name/phone/id from customer if run as an object method,
@@ -2682,7 +2706,15 @@ sub _token_check_next_recnum {
   my $recnum = shift @$recnums;
   return $recnum if $recnum;
   my $tclass = 'FS::'.$table;
-  my $sth = $dbh->prepare('SELECT '.$tclass->primary_key.' FROM '.$table.' ORDER BY '.$tclass->primary_key.' LIMIT '.$step.' OFFSET '.$$offset) or die $dbh->errstr;
+  my $sth = $dbh->prepare(
+    'SELECT '.$tclass->primary_key.
+    ' FROM '.$table.
+    " WHERE payby IN ( 'CARD', 'DCRD' ) ".
+    "   AND ( length(payinfo) > 80 OR payinfo NOT LIKE '99%' )".
+    ' ORDER BY '.$tclass->primary_key.
+    ' LIMIT '.$step.
+    ' OFFSET '.$$offset
+  ) or die $dbh->errstr;
   $sth->execute() or die $sth->errstr;
   my @recnums;
   while (my $rec = $sth->fetchrow_hashref) {