[freeside-commits] branch master updated. bde747e981abe6c517ce25118a53b13f53d63da7

Jonathan Prykop jonathan at 420.am
Fri Nov 4 23:21:25 PDT 2016


The branch, master has been updated
       via  bde747e981abe6c517ce25118a53b13f53d63da7 (commit)
      from  39b05a617a55fe0bb072cde02a5c1b5b16301f62 (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 bde747e981abe6c517ce25118a53b13f53d63da7
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Sat Nov 5 01:20:35 2016 -0500

    71513: Card tokenization [bug fixes to previous checkpoint]

diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index 7718f7a..e7a8030 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -376,44 +376,16 @@ sub _bop_content {
 }
 
 sub _tokenize_card {
-  my ($self,$transaction,$options,$log,%opt) = @_;
-  # options is for entire process, so we can update payinfo
-  # opt is just for this call, only key is replace
-
-  my $cust_payby = $options->{'cust_payby'};
-  if ( $cust_payby
-       and $transaction->can('card_token') 
+  my ($self,$transaction,$options) = @_;
+  if ( $transaction->can('card_token') 
        and $transaction->card_token 
-       and !$cust_payby->tokenized #not already tokenized
+       and !$self->tokenized($options->{'payinfo'})
   ) {
-
-    $options->{'payinfo'} = $transaction->card_token;
-    $cust_payby->payinfo($transaction->card_token);
-
-    my $error;
-    $error = $cust_payby->replace if $opt{'replace'};
-    if ( $error ) {
-      $log->error('Error storing token for cust '.$self->custnum.', cust_payby '.$cust_payby->custpaybynum.': '.$error);
-      return $error;
-    } else {
-      $log->debug('Tokenized card for cust '.$self->custnum.', cust_payby '.$cust_payby->custpaybynum);
-      return '';
-    }
-
+    $options->{'payinfo'} = $transaction->card_token; #for creating cust_pay
+    $options->{'cust_payby'}->payinfo($transaction->card_token) if $options->{'cust_payby'};
+    return $transaction->card_token;
   }
-
-}
-
-# only store payinfo in cust_pay/cust_pay_pending
-# if it's a tokenized card or if processor requires card for void
-sub _cust_pay_opts {
-  my ($self,$payby,$payinfo,$transaction) = @_;
-  ( (($payby eq 'CARD') && $self->tokenized($payinfo))
-    || (($payby eq 'CARD') && $transaction->info('CC_void_requires_card'))
-    || (($payby eq 'CHEK') && $transaction->info('ECHECK_void_requires_account'))
-  )
-    ? ('payinfo' => $payinfo)
-    : ();
+  return '';
 }
 
 my %bop_method2payby = (
@@ -681,15 +653,12 @@ sub realtime_bop {
 
   #okay, good to go, if we're a duplicate, cust_pay_pending will kick us out
 
-  my $transaction = new $namespace( $payment_gateway->gateway_module,
-                                    $self->_bop_options(\%options),
-                                  );
-
   my $cust_pay_pending = new FS::cust_pay_pending {
     'custnum'           => $self->custnum,
     'paid'              => $options{amount},
     '_date'             => '',
     'payby'             => $bop_method2payby{$options{method}},
+    'payinfo'           => $options{payinfo},
     'paymask'           => $options{paymask},
     'paydate'           => $paydate,
     'recurring_billing' => $content{recurring_billing},
@@ -698,7 +667,6 @@ sub realtime_bop {
     'gatewaynum'        => $payment_gateway->gatewaynum || '',
     'session_id'        => $options{session_id} || '',
     'jobnum'            => $options{depend_jobnum} || '',
-    $self->_cust_pay_opts($options{payinfo},$transaction),
   };
   $cust_pay_pending->payunique( $options{payunique} )
     if defined($options{payunique}) && length($options{payunique});
@@ -715,6 +683,10 @@ sub realtime_bop {
   my( $action1, $action2 ) =
     split( /\s*\,\s*/, $payment_gateway->gateway_action );
 
+  my $transaction = new $namespace( $payment_gateway->gateway_module,
+                                    $self->_bop_options(\%options),
+                                  );
+
   $transaction->content(
     'type'           => $options{method},
     $self->_bop_auth(\%options),          
@@ -819,6 +791,8 @@ sub realtime_bop {
   ) {
     my $error = $self->remove_cvv_from_cust_payby($options{payinfo});
     if ( $error ) {
+      $log->critical('Error removing cvv for cust '.$self->custnum.': '.$error);
+      #not returning error, should at least attempt to handle results of an otherwise valid transaction
       warn "WARNING: error removing cvv: $error\n";
     }
   }
@@ -827,8 +801,15 @@ sub realtime_bop {
   # Tokenize
   ###
 
-  my $error = $self->_tokenize_card($transaction,\%options,$log,'replace' => 1);
-  return $error if $error;
+  if (my $card_token = $self->_tokenize_card($transaction,\%options)) {
+    # cpp will be replaced in _realtime_bop_result
+    $cust_pay_pending->payinfo($card_token);
+    if ($options{'cust_payby'} and my $error = $options{'cust_payby'}->replace) {
+      $log->critical('Error storing token for cust '.$self->custnum.', cust_payby '.$options{'cust_payby'}->custpaybynum.': '.$error);
+      #not returning error, should at least attempt to handle results of an otherwise valid transaction
+      #this leaves real card number in cust_payby, but can't do much else if cust_payby won't replace
+    }
+  }
 
   ###
   # result handling
@@ -925,7 +906,7 @@ sub _realtime_bop_result {
     or return "no payment gateway in arguments to _realtime_bop_result";
 
   $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
-  my $cpp_captured_err = $cust_pay_pending->replace;
+  my $cpp_captured_err = $cust_pay_pending->replace; #also saves tokenization
   return $cpp_captured_err if $cpp_captured_err;
 
   if ( $transaction->is_success() ) {
@@ -939,6 +920,7 @@ sub _realtime_bop_result {
        'paid'     => $cust_pay_pending->paid,
        '_date'    => '',
        'payby'    => $cust_pay_pending->payby,
+       'payinfo'  => $options{'payinfo'},
        'paymask'  => $options{'paymask'} || $cust_pay_pending->paymask,
        'paydate'  => $cust_pay_pending->paydate,
        'pkgnum'   => $cust_pay_pending->pkgnum,
@@ -948,7 +930,6 @@ sub _realtime_bop_result {
        'auth'           => $transaction->authorization,
        'order_number'   => $order_number || '',
        'no_auto_apply'  => $options{'no_auto_apply'} ? 'Y' : '',
-       $self->_cust_pay_opts($options{payinfo},$transaction),
     } );
     #doesn't hurt to know, even though the dup check is in cust_pay_pending now
     $cust_pay->payunique( $options{payunique} )
@@ -1854,9 +1835,7 @@ sub realtime_verify_bop {
   ###
 
   my $error;
-  my $transaction = new $namespace( $payment_gateway->gateway_module,
-                                    $self->_bop_options(\%options),
-                                  ); #need this back so we can do _tokenize_card
+  my $transaction; #need this back so we can do _tokenize_card
 
   # don't mutex the customer here, because they might be uncommitted. and
   # this is only verification. it doesn't matter if they have other
@@ -1867,13 +1846,13 @@ sub realtime_verify_bop {
     'paid'              => '1.00',
     '_date'             => '',
     'payby'             => $bop_method2payby{'CC'},
+    'payinfo'           => $options{payinfo},
     'paymask'           => $options{paymask},
     'paydate'           => $paydate,
     'pkgnum'            => $options{'pkgnum'},
     'status'            => 'new',
     'gatewaynum'        => $payment_gateway->gatewaynum || '',
     'session_id'        => $options{session_id} || '',
-    $self->_cust_pay_opts($options{payinfo},$transaction),
   };
   $cust_pay_pending->payunique( $options{payunique} )
     if defined($options{payunique}) && length($options{payunique});
@@ -1904,6 +1883,10 @@ sub realtime_verify_bop {
       if $DEBUG > 1;
     warn Dumper($cust_pay_pending) if $DEBUG > 2;
 
+    $transaction = new $namespace( $payment_gateway->gateway_module,
+                                   $self->_bop_options(\%options),
+                                    );
+
     $transaction->content(
       'type'           => 'CC',
       $self->_bop_auth(\%options),          
@@ -2122,12 +2105,22 @@ sub realtime_verify_bop {
   }
 
   ###
+  # remove paycvv here?  need to find out if a reversed auth
+  #   counts as an initial transaction for paycvv retention requirements
+  ###
+
+  ###
   # Tokenize
   ###
 
-  #important that we not pass replace option here,
+  #important that we not replace cust_payby here,
   #because cust_payby->replace uses realtime_verify_bop!
-  $self->_tokenize_card($transaction,\%options,$log);
+  if (my $card_token = $self->_tokenize_card($transaction,\%options)) {
+    $cust_pay_pending->payinfo($card_token);
+    my $cpp_token_err = $cust_pay_pending->replace;
+    #this leaves real card number in cust_payby, but can't do much else if cust_payby won't replace
+    return $cpp_token_err if $cpp_token_err;
+  }
 
   ###
   # result handling
@@ -2264,9 +2257,16 @@ sub realtime_tokenize {
 
   if ( $transaction->card_token() ) { # no is_success flag
 
-    #important that we not pass replace option here, 
+    # realtime_tokenize should not clear paycvv at this time.  it might be
+    # needed for the first transaction, and a tokenize isn't actually a
+    # transaction that hits the gateway.  at some point in the future, card
+    # fortress should take on the "store paycvv until first transaction"
+    # functionality and we should fix this in freeside, but i that's a bigger
+    # project for another time.
+
+    #important that we not replace cust_payby here, 
     #because cust_payby->replace uses realtime_tokenize!
-    $self->_tokenize_card($transaction,\%options,$log);
+    $self->_tokenize_card($transaction,\%options);
 
   } else {
 
@@ -2278,10 +2278,19 @@ sub realtime_tokenize {
 
 }
 
+
+=item tokenized PAYINFO
+
+Convenience wrapper for L<FS::payinfo_Mixin/tokenized>
+
+PAYINFO is required
+
+=cut
+
 sub tokenized {
   my $this = shift;
   my $payinfo = shift;
-  $payinfo =~ /^99\d{14}$/;
+  FS::cust_pay->tokenized($payinfo);
 }
 
 =back
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index a0a2cbc..6982834 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -67,7 +67,7 @@ sub payinfo {
   my($self,$payinfo) = @_;
 
   if ( defined($payinfo) ) {
-    $self->paymask($self->mask_payinfo) unless $self->tokenized; #make sure old mask is set
+    $self->paymask($self->mask_payinfo) unless $self->paymask || $self->tokenized; #make sure old mask is set
     $self->setfield('payinfo', $payinfo);
     $self->paymask($self->mask_payinfo) unless $self->tokenized($payinfo); #remask unless tokenizing
   } else {
@@ -454,12 +454,17 @@ sub process_set_cardtype {
   }
 }
 
+=item tokenized [ PAYINFO ]
+
+Returns true if object payinfo is tokenized
+
+Optionally, an arbitrary payby and payinfo can be passed.
+
+=cut
+
 sub tokenized {
   my $self = shift;
   my $payinfo = scalar(@_) ? shift : $self->payinfo;
-  ## or just $self->cust_main->tokenized($payinfo) ??
-  ##   everything that currently uses this mixin is linked to cust_main,
-  ##   but just in case, false laziness w/ FS::cust_main::Billing_Realtime
   $payinfo =~ /^99\d{14}$/;
 }
 
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index 84687f0..1532605 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -193,6 +193,11 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) {
 
     errorpage("error saving info, payment not processed: $error")
       if $error;	
+
+  } elsif ( $payby eq 'CARD' ) { # not saving
+
+    $paymask = FS::payinfo_Mixin->mask_payinfo('CARD',$payinfo); # for untokenized but tokenizable payinfo
+
   }
 
 }

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

Summary of changes:
 FS/FS/cust_main/Billing_Realtime.pm |  115 +++++++++++++++++++----------------
 FS/FS/payinfo_Mixin.pm              |   13 ++--
 httemplate/misc/process/payment.cgi |    5 ++
 3 files changed, 76 insertions(+), 57 deletions(-)




More information about the freeside-commits mailing list