fix refunds on 2.1 (fallout from webpay bs on RT#4103), RT#8700
[freeside.git] / FS / FS / cust_main.pm
index 684031c..99b97af 100644 (file)
@@ -86,7 +86,7 @@ $skip_fuzzyfiles = 0;
 @fuzzyfields = ( 'first', 'last', 'company', 'address1' );
 
 @encrypted_fields = ('payinfo', 'paycvv');
 @fuzzyfields = ( 'first', 'last', 'company', 'address1' );
 
 @encrypted_fields = ('payinfo', 'paycvv');
-sub nohistory_fields { ('paycvv'); }
+sub nohistory_fields { ('payinfo', 'paycvv'); }
 
 @paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
 
 
 @paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
 
@@ -1453,8 +1453,15 @@ sub replace {
 
   }
 
 
   }
 
-  if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ &&
-       grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) {
+  if ( $self->payby =~ /^(CARD|CHEK|LECB)$/
+       && ( ( $self->get('payinfo') ne $old->get('payinfo')
+              && $self->get('payinfo') !~ /^99\d{14}$/ 
+            )
+            || grep { $self->get($_) ne $old->get($_) } qw(paydate payname)
+          )
+     )
+  {
+
     # card/check/lec info has changed, want to retry realtime_ invoice events
     my $error = $self->retry_realtime;
     if ( $error ) {
     # card/check/lec info has changed, want to retry realtime_ invoice events
     my $error = $self->retry_realtime;
     if ( $error ) {
@@ -1727,7 +1734,8 @@ sub check {
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
 
     return gettext('unknown_card_type')
       or return gettext('invalid_card'); # . ": ". $self->payinfo;
 
     return gettext('unknown_card_type')
-      if cardtype($self->payinfo) eq "Unknown";
+      if $self->payinfo !~ /^99\d{14}$/ #token
+      && cardtype($self->payinfo) eq "Unknown";
 
     my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
     if ( $ban ) {
 
     my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
     if ( $ban ) {
@@ -4239,6 +4247,7 @@ sub _bop_options {
   $options->{payment_gateway}->gatewaynum
     ? $options->{payment_gateway}->options
     : @{ $options->{payment_gateway}->get('options') };
   $options->{payment_gateway}->gatewaynum
     ? $options->{payment_gateway}->options
     : @{ $options->{payment_gateway}->get('options') };
+
 }
 
 sub _bop_defaults {
 }
 
 sub _bop_defaults {
@@ -4265,14 +4274,6 @@ sub _bop_content {
   my ($self, $options) = @_;
   my %content = ();
 
   my ($self, $options) = @_;
   my %content = ();
 
-  $content{address} = exists($options->{'address1'})
-                        ? $options->{'address1'}
-                        : $self->address1;
-  my $address2 = exists($options->{'address2'})
-                   ? $options->{'address2'}
-                   : $self->address2;
-  $content{address} .= ", ". $address2 if length($address2);
-
   my $payip = exists($options->{'payip'}) ? $options->{'payip'} : $self->payip;
   $content{customer_ip} = $payip if length($payip);
 
   my $payip = exists($options->{'payip'}) ? $options->{'payip'} : $self->payip;
   $content{customer_ip} = $payip if length($payip);
 
@@ -4283,14 +4284,30 @@ sub _bop_content {
     (    $conf->exists('business-onlinepayment-email_customer')
       || $conf->exists('business-onlinepayment-email-override') );
       
     (    $conf->exists('business-onlinepayment-email_customer')
       || $conf->exists('business-onlinepayment-email-override') );
       
-  $content{payfirst} = $self->getfield('first');
-  $content{paylast} = $self->getfield('last');
+  my ($payname, $payfirst, $paylast);
+  if ( $options->{payname} && $options->{method} ne 'ECHECK' ) {
+    ($payname = $options->{payname}) =~
+      /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+      or return "Illegal payname $payname";
+    ($payfirst, $paylast) = ($1, $2);
+  } else {
+    $payfirst = $self->getfield('first');
+    $paylast = $self->getfield('last');
+    $payname = "$payfirst $paylast";
+  }
 
 
-  $content{account_name} = "$content{payfirst} $content{paylast}"
-    if $options->{method} eq 'ECHECK';
+  $content{last_name} = $paylast;
+  $content{first_name} = $payfirst;
 
 
-  $content{name} = $options->{payname};
-  $content{name} = $content{account_name} if exists($content{account_name});
+  $content{name} = $payname;
+
+  $content{address} = exists($options->{'address1'})
+                        ? $options->{'address1'}
+                        : $self->address1;
+  my $address2 = exists($options->{'address2'})
+                   ? $options->{'address2'}
+                   : $self->address2;
+  $content{address} .= ", ". $address2 if length($address2);
 
   $content{city} = exists($options->{city})
                      ? $options->{city}
 
   $content{city} = exists($options->{city})
                      ? $options->{city}
@@ -4304,10 +4321,11 @@ sub _bop_content {
   $content{country} = exists($options->{country})
                         ? $options->{country}
                         : $self->country;
   $content{country} = exists($options->{country})
                         ? $options->{country}
                         : $self->country;
+
   $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/
   $content{phone} = $self->daytime || $self->night;
 
   $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/
   $content{phone} = $self->daytime || $self->night;
 
-  (%content);
+  \%content;
 }
 
 my %bop_method2payby = (
 }
 
 my %bop_method2payby = (
@@ -4383,13 +4401,8 @@ sub realtime_bop {
   # massage data
   ###
 
   # massage data
   ###
 
-  my (%bop_content) = $self->_bop_content(\%options);
-
-  if ( $options{method} ne 'ECHECK' ) {
-    $options{payname} =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
-      or return "Illegal payname $options{payname}";
-    ($bop_content{payfirst}, $bop_content{paylast}) = ($1, $2);
-  }
+  my $bop_content = $self->_bop_content(\%options);
+  return $bop_content unless ref($bop_content);
 
   my @invoicing_list = $self->invoicing_list_emailonly;
   if ( $conf->exists('emailinvoiceautoalways')
 
   my @invoicing_list = $self->invoicing_list_emailonly;
   if ( $conf->exists('emailinvoiceautoalways')
@@ -4455,6 +4468,9 @@ sub realtime_bop {
     $content{account_type} = exists($options{'paytype'})
                                ? uc($options{'paytype'}) || 'CHECKING'
                                : uc($self->getfield('paytype')) || 'CHECKING';
     $content{account_type} = exists($options{'paytype'})
                                ? uc($options{'paytype'}) || 'CHECKING'
                                : uc($self->getfield('paytype')) || 'CHECKING';
+    $content{account_name} = $self->getfield('first'). ' '.
+                             $self->getfield('last');
+
     $content{customer_org} = $self->company ? 'B' : 'I';
     $content{state_id}       = exists($options{'stateid'})
                                  ? $options{'stateid'}
     $content{customer_org} = $self->company ? 'B' : 'I';
     $content{state_id}       = exists($options{'stateid'})
                                  ? $options{'stateid'}
@@ -4539,7 +4555,7 @@ sub realtime_bop {
     'amount'         => $options{amount},
     #'invoice_number' => $options{'invnum'},
     'customer_id'    => $self->custnum,
     'amount'         => $options{amount},
     #'invoice_number' => $options{'invnum'},
     'customer_id'    => $self->custnum,
-    %bop_content,
+    %$bop_content,
     'reference'      => $cust_pay_pending->paypendingnum, #for now
     'email'          => $email,
     %content, #after
     'reference'      => $cust_pay_pending->paypendingnum, #for now
     'email'          => $email,
     %content, #after
@@ -4554,6 +4570,8 @@ sub realtime_bop {
   my $BOP_TESTING_SUCCESS = 1;
 
   unless ( $BOP_TESTING ) {
   my $BOP_TESTING_SUCCESS = 1;
 
   unless ( $BOP_TESTING ) {
+    $transaction->test_transaction(1)
+      if $conf->exists('business-onlinepayment-test_transaction');
     $transaction->submit();
   } else {
     if ( $BOP_TESTING_SUCCESS ) {
     $transaction->submit();
   } else {
     if ( $BOP_TESTING_SUCCESS ) {
@@ -4606,6 +4624,8 @@ sub realtime_bop {
 
     $capture->content( %capture );
 
 
     $capture->content( %capture );
 
+    $capture->test_transaction(1)
+      if $conf->exists('business-onlinepayment-test_transaction');
     $capture->submit();
 
     unless ( $capture->is_success ) {
     $capture->submit();
 
     unless ( $capture->is_success ) {
@@ -4635,6 +4655,25 @@ sub realtime_bop {
   }
 
   ###
   }
 
   ###
+  # Tokenize
+  ###
+
+
+  if ( $transaction->can('card_token') && $transaction->card_token ) {
+
+    $self->card_token($transaction->card_token);
+
+    if ( $options{'payinfo'} eq $self->payinfo ) {
+      $self->payinfo($transaction->card_token);
+      my $error = $self->replace;
+      if ( $error ) {
+        warn "WARNING: error storing token: $error, but proceeding anyway\n";
+      }
+    }
+
+  }
+
+  ###
   # result handling
   ###
 
   # result handling
   ###
 
@@ -5161,7 +5200,7 @@ sub realtime_refund_bop {
   my $self = shift;
 
   my %options = ();
   my $self = shift;
 
   my %options = ();
-  if (ref($_[0]) ne 'HASH') {
+  if (ref($_[0]) eq 'HASH') {
     %options = %{$_[0]};
   } else {
     my $method = shift;
     %options = %{$_[0]};
   } else {
     my $method = shift;
@@ -5293,6 +5332,8 @@ sub realtime_refund_bop {
       }
     }
     $void->content( 'action' => 'void', %content );
       }
     }
     $void->content( 'action' => 'void', %content );
+    $void->test_transaction(1)
+      if $conf->exists('business-onlinepayment-test_transaction');
     $void->submit();
     if ( $void->is_success ) {
       my $error = $cust_pay->void($options{'reason'});
     $void->submit();
     if ( $void->is_success ) {
       my $error = $cust_pay->void($options{'reason'});
@@ -5395,6 +5436,8 @@ sub realtime_refund_bop {
   );
   warn join('', map { "  $_ => $sub_content{$_}\n" } keys %sub_content )
     if $DEBUG > 1;
   );
   warn join('', map { "  $_ => $sub_content{$_}\n" } keys %sub_content )
     if $DEBUG > 1;
+  $refund->test_transaction(1)
+    if $conf->exists('business-onlinepayment-test_transaction');
   $refund->submit();
 
   return "$processor error: ". $refund->error_message
   $refund->submit();
 
   return "$processor error: ". $refund->error_message
@@ -7392,6 +7435,12 @@ WHERE clause hashref (elements "AND"ed together) (typically used with the total
 (unused.  obsolete?)
 JOIN clause (typically used with the total option)
 
 (unused.  obsolete?)
 JOIN clause (typically used with the total option)
 
+=item cutoff
+
+An absolute cutoff time.  Payments, credits, and refunds I<applied> after this 
+time will be ignored.  Note that START_TIME and END_TIME only limit the date 
+range for invoices and I<unapplied> payments, credits, and refunds.
+
 =back
 
 =cut
 =back
 
 =cut
@@ -7399,10 +7448,12 @@ JOIN clause (typically used with the total option)
 sub balance_date_sql {
   my( $class, $start, $end, %opt ) = @_;
 
 sub balance_date_sql {
   my( $class, $start, $end, %opt ) = @_;
 
-  my $owed         = FS::cust_bill->owed_sql;
-  my $unapp_refund = FS::cust_refund->unapplied_sql;
-  my $unapp_credit = FS::cust_credit->unapplied_sql;
-  my $unapp_pay    = FS::cust_pay->unapplied_sql;
+  my $cutoff = $opt{'cutoff'};
+
+  my $owed         = FS::cust_bill->owed_sql($cutoff);
+  my $unapp_refund = FS::cust_refund->unapplied_sql($cutoff);
+  my $unapp_credit = FS::cust_credit->unapplied_sql($cutoff);
+  my $unapp_pay    = FS::cust_pay->unapplied_sql($cutoff);
 
   my $j = $opt{'join'} || '';
 
 
   my $j = $opt{'join'} || '';
 
@@ -7435,9 +7486,11 @@ Available options are:
 =cut
 
 sub unapplied_payments_date_sql {
 =cut
 
 sub unapplied_payments_date_sql {
-  my( $class, $start, $end, ) = @_;
+  my( $class, $start, $end, %opt ) = @_;
 
 
-  my $unapp_pay    = FS::cust_pay->unapplied_sql;
+  my $cutoff = $opt{'cutoff'};
+
+  my $unapp_pay    = FS::cust_pay->unapplied_sql($cutoff);
 
   my $pay_where = $class->_money_table_where( 'cust_pay', $start, $end,
                                                           'unapplied_date'=>1 );
 
   my $pay_where = $class->_money_table_where( 'cust_pay', $start, $end,
                                                           'unapplied_date'=>1 );
@@ -8790,14 +8843,23 @@ sub _agent_plandata {
 
 }
 
 
 }
 
+=item queued_bill 'custnum' => CUSTNUM [ , OPTION => VALUE ... ]
+
+Subroutine (not a method), designed to be called from the queue.
+
+Takes a list of options and values.
+
+Pulls up the customer record via the custnum option and calls bill_and_collect.
+
+=cut
+
 sub queued_bill {
 sub queued_bill {
-  ## actual sub, not a method, designed to be called from the queue.
-  ## sets up the customer, and calls the bill_and_collect
   my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
   my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
+
   my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
   my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
-      $cust_main->bill_and_collect(
-        %args,
-      );
+  warn 'bill_and_collect custnum#'. $cust_main->custnum. "\n";#log custnum w/pid
+
+  $cust_main->bill_and_collect( %args );
 }
 
 sub _upgrade_data { #class method
 }
 
 sub _upgrade_data { #class method