remove thread/PerlIO warning - standard in 5.8.x and working fine
[freeside.git] / FS / FS / cust_bill.pm
index c314050..55a66b7 100644 (file)
@@ -3,10 +3,12 @@ package FS::cust_bill;
 use strict;
 use vars qw( @ISA $conf $money_char );
 use vars qw( $lpr $invoice_from $smtpmachine );
-use vars qw( $processor );
+use vars qw( $cybercash );
 use vars qw( $xaction $E_NoErr );
 use vars qw( $bop_processor $bop_login $bop_password $bop_action @bop_options );
+use vars qw( $ach_processor $ach_login $ach_password $ach_action @ach_options );
 use vars qw( $invoice_lines @buf ); #yuck
+use vars qw( $realtime_bop_decline_quiet );
 use Date::Format;
 use Mail::Internet 1.44;
 use Mail::Header;
@@ -24,6 +26,8 @@ use FS::cust_bill_event;
 
 @ISA = qw( FS::Record );
 
+$realtime_bop_decline_quiet = 0;
+
 #ask FS::UID to run this stuff for us later
 $FS::UID::callback{'FS::cust_bill'} = sub { 
 
@@ -35,6 +39,11 @@ $FS::UID::callback{'FS::cust_bill'} = sub {
   $invoice_from = $conf->config('invoice_from');
   $smtpmachine = $conf->config('smtpmachine');
 
+  ( $bop_processor,$bop_login, $bop_password, $bop_action ) = ( '', '', '', '');
+  @bop_options = ();
+  ( $ach_processor,$ach_login, $ach_password, $ach_action ) = ( '', '', '', '');
+  @ach_options = ();
+
   if ( $conf->exists('cybercash3.2') ) {
     require CCMckLib3_2;
       #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
@@ -55,7 +64,7 @@ $FS::UID::callback{'FS::cust_bill'} = sub {
       my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
       die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
     }
-    $processor='cybercash3.2';
+    $cybercash='cybercash3.2';
   } elsif ( $conf->exists('business-onlinepayment') ) {
     ( $bop_processor,
       $bop_login,
@@ -64,8 +73,20 @@ $FS::UID::callback{'FS::cust_bill'} = sub {
       @bop_options
     ) = $conf->config('business-onlinepayment');
     $bop_action ||= 'normal authorization';
+    ( $ach_processor, $ach_login, $ach_password, $ach_action, @ach_options ) =
+      ( $bop_processor, $bop_login, $bop_password, $bop_action, @bop_options );
+    eval "use Business::OnlinePayment";  
+  }
+
+  if ( $conf->exists('business-onlinepayment-ach') ) {
+    ( $ach_processor,
+      $ach_login,
+      $ach_password,
+      $ach_action,
+      @ach_options
+    ) = $conf->config('business-onlinepayment-ach');
+    $ach_action ||= 'normal authorization';
     eval "use Business::OnlinePayment";  
-    $processor="Business::OnlinePayment::$bop_processor";
   }
 
 };
@@ -610,7 +631,15 @@ for supported processors.
 
 sub realtime_card {
   my $self = shift;
-  $self->realtime_bop('CC', @_);
+  $self->realtime_bop(
+    'CC',
+    $bop_processor,
+    $bop_login,
+    $bop_password,
+    $bop_action,
+    \@bop_options,
+    @_
+  );
 }
 
 =item realtime_ach
@@ -624,20 +653,48 @@ for supported processors.
 
 sub realtime_ach {
   my $self = shift;
-  $self->realtime_bop('ECHECK', @_);
+  $self->realtime_bop(
+    'ECHECK',
+    $ach_processor,
+    $ach_login,
+    $ach_password,
+    $ach_action,
+    \@ach_options,
+    @_
+  );
 }
 
-sub realtime_bop {
+=item realtime_lec
+
+Attempts to pay this invoice with phone bill (LEC) payment via a
+Business::OnlinePayment realtime gateway.  See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
+
+=cut
+
+sub realtime_lec {
   my $self = shift;
-  my $method = shift;
+  $self->realtime_bop(
+    'LEC',
+    $bop_processor,
+    $bop_login,
+    $bop_password,
+    $bop_action,
+    \@bop_options,
+    @_
+  );
+}
+
+sub realtime_bop {
+  my( $self, $method, $processor, $login, $password, $action, $options ) = @_;
+
+  #trim an extraneous blank line
+  pop @$options if scalar(@$options) % 2 && $options->[-1] =~ /^\s*$/;
+
   my $cust_main = $self->cust_main;
   my $amount = $self->owed;
 
-  unless ( $processor =~ /^Business::OnlinePayment::(.*)$/ ) {
-    return "Real-time card/ACH processing not enabled (processor $processor)";
-  }
-  my $bop_processor = $1; #hmm?
-
   my $address = $cust_main->address1;
   $address .= ", ". $cust_main->address2 if $cust_main->address2;
 
@@ -663,7 +720,7 @@ sub realtime_bop {
   }
   my $email = $invoicing_list[0];
 
-  my( $action1, $action2 ) = split(/\s*\,\s*/, $bop_action );
+  my( $action1, $action2 ) = split(/\s*\,\s*/, $action );
 
   my $description = 'Internet Services';
   if ( $conf->exists('business-onlinepayment-description') ) {
@@ -683,23 +740,39 @@ sub realtime_bop {
 
   my %content;
   if ( $method eq 'CC' ) { 
+
     $content{card_number} = $cust_main->payinfo;
     $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
     $content{expiration} = "$2/$1";
+
+    $content{cvv2} = $cust_main->paycvv
+      if defined $cust_main->dbdef_table->column('paycvv')
+         && length($cust_main->paycvv);
+
+    $content{recurring_billing} = 'YES'
+      if qsearch('cust_pay', { 'custnum' => $cust_main->custnum,
+                               'payby'   => 'CARD',
+                               'payinfo' => $cust_main->payinfo, } );
+
   } elsif ( $method eq 'ECHECK' ) {
-    my($account_number,$routing_code) = $cust_main->payinfo
+    my($account_number,$routing_code) = $cust_main->payinfo;
     ( $content{account_number}, $content{routing_code} ) =
       split('@', $cust_main->payinfo);
     $content{bank_name} = $cust_main->payname;
+    $content{account_type} = 'CHECKING';
+    $content{account_name} = $payname;
+    $content{customer_org} = $self->company ? 'B' : 'I';
+    $content{customer_ssn} = $self->ss;
+  } elsif ( $method eq 'LEC' ) {
+    $content{phone} = $cust_main->payinfo;
   }
   
   my $transaction =
-    new Business::OnlinePayment( $bop_processor, @bop_options );
+    new Business::OnlinePayment( $processor, @$options );
   $transaction->content(
-    %content,
     'type'           => $method,
-    'login'          => $bop_login,
-    'password'       => $bop_password,
+    'login'          => $login,
+    'password'       => $password,
     'action'         => $action1,
     'description'    => $description,
     'amount'         => $amount,
@@ -716,6 +789,7 @@ sub realtime_bop {
     'referer'        => 'http://cleanwhisker.420.am/',
     'email'          => $email,
     'phone'          => $cust_main->daytime || $cust_main->night,
+    %content, #after
   );
   $transaction->submit();
 
@@ -728,14 +802,14 @@ sub realtime_bop {
     #warn "********* $auth ***********\n";
     #warn "********* $ordernum ***********\n";
     my $capture =
-      new Business::OnlinePayment( $bop_processor, @bop_options );
+      new Business::OnlinePayment( $processor, @$options );
 
     my %capture = (
       %content,
       type           => $method,
       action         => $action2,
-      login          => $bop_login,
-      password       => $bop_password,
+      login          => $login,
+      password       => $password,
       order_number   => $ordernum,
       amount         => $amount,
       authorization  => $auth,
@@ -762,11 +836,30 @@ sub realtime_bop {
 
   }
 
+  #remove paycvv after initial transaction
+  #make this disable-able via a config option if anyone insists?  
+  # (though that probably violates cardholder agreements)
+  use Business::CreditCard;
+  if ( defined $cust_main->dbdef_table->column('paycvv')
+       && length($cust_main->paycvv)
+       && ! grep { $_ eq cardtype($cust_main->payinfo) } $conf->config('cvv-save')
+
+  ) {
+    my $new = new FS::cust_main { $cust_main->hash };
+    $new->paycvv('');
+    my $error = $new->replace($cust_main);
+    if ( $error ) {
+      warn "error removing cvv: $error\n";
+    }
+  }
+
+  #result handling
   if ( $transaction->is_success() ) {
 
     my %method2payby = (
       'CC'     => 'CARD',
       'ECHECK' => 'CHEK',
+      'LEC'    => 'LECB',
     );
 
     my $cust_pay = new FS::cust_pay ( {
@@ -794,8 +887,10 @@ sub realtime_bop {
     my $perror = "$processor error, invnum #". $self->invnum. ': '.
                  $transaction->result_code. ": ". $transaction->error_message;
 
-    if ( $conf->exists('emaildecline')
+    if ( !$realtime_bop_decline_quiet && $conf->exists('emaildecline')
          && grep { $_ ne 'POST' } $cust_main->invoicing_list
+         && ! grep { $_ eq $transaction->error_message }
+                   $conf->config('emaildecline-exclude')
     ) {
       my @templ = $conf->config('declinetemplate');
       my $template = new Text::Template (
@@ -847,7 +942,7 @@ sub realtime_card_cybercash {
   my $amount = $self->owed;
 
   return "CyberCash CashRegister real-time card processing not enabled!"
-    unless $processor eq 'cybercash3.2';
+    unless $cybercash eq 'cybercash3.2';
 
   my $address = $cust_main->address1;
   $address .= ", ". $cust_main->address2 if $cust_main->address2;
@@ -890,7 +985,7 @@ sub realtime_card_cybercash {
        '_date'     => '',
        'payby'    => 'CARD',
        'payinfo'  => $cust_main->payinfo,
-       'paybatch' => "$processor:$paybatch",
+       'paybatch' => "$cybercash:$paybatch",
     } );
     my $error = $cust_pay->insert;
     if ( $error ) {
@@ -935,7 +1030,6 @@ sub batch_card {
     'state'    => $cust_main->getfield('state'),
     'zip'      => $cust_main->getfield('zip'),
     'country'  => $cust_main->getfield('country'),
-    'trancode' => 77,
     'cardnum'  => $cust_main->getfield('payinfo'),
     'exp'      => $cust_main->getfield('paydate'),
     'payname'  => $cust_main->getfield('payname'),
@@ -992,33 +1086,48 @@ sub print_text {
   }
 
   #new charges
-  foreach ( $self->cust_bill_pkg ) {
+  foreach my $cust_bill_pkg (
+    ( grep {   $_->pkgnum } $self->cust_bill_pkg ),  #packages first
+    ( grep { ! $_->pkgnum } $self->cust_bill_pkg ),  #then taxes
+  ) {
 
-    if ( $_->pkgnum ) {
+    if ( $cust_bill_pkg->pkgnum ) {
 
-      my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
-      my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
-      my($pkg)=$part_pkg->pkg;
+      my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } );
+      my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
+      my $pkg = $part_pkg->pkg;
 
-      if ( $_->setup != 0 ) {
-        push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ];
+      if ( $cust_bill_pkg->setup != 0 ) {
+        push @buf, [ "$pkg Setup",
+                     $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
         push @buf,
           map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
       }
 
-      if ( $_->recur != 0 ) {
+      if ( $cust_bill_pkg->recur != 0 ) {
         push @buf, [
-          "$pkg (" . time2str("%x",$_->sdate) . " - " .
-                                time2str("%x",$_->edate) . ")",
-          $money_char. sprintf("%10.2f",$_->recur)
+          "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " .
+                                time2str("%x", $cust_bill_pkg->edate) . ")",
+          $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
         ];
         push @buf,
           map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
       }
 
-    } else { #pkgnum Tax
-      push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ] 
-        if $_->setup != 0;
+    } else { #pkgnum tax or one-shot line item
+      my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+                     ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+                     : 'Tax';
+      if ( $cust_bill_pkg->setup != 0 ) {
+        push @buf, [ $itemdesc,
+                     $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
+      }
+      if ( $cust_bill_pkg->recur != 0 ) {
+        push @buf, [ "$itemdesc (". time2str("%x", $cust_bill_pkg->sdate). " - "
+                                  . time2str("%x", $cust_bill_pkg->edate). ")",
+                     $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
+                   ];
+      }
     }
   }
 
@@ -1076,9 +1185,9 @@ sub print_text {
   or die "cannot load config file $templatefile";
   $invoice_lines = 0;
   my $wasfunc = 0;
-  foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
-    /invoice_lines\((\d+)\)/;
-    $invoice_lines += $1;
+  foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
+    /invoice_lines\((\d*)\)/;
+    $invoice_lines += $1 || scalar(@buf);
     $wasfunc=1;
   }
   die "no invoice_lines() functions in template?" unless $wasfunc;
@@ -1091,11 +1200,12 @@ sub print_text {
 
   #setup template variables
   package FS::cust_bill::_template; #!
-  use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
+  use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent );
 
   $invnum = $self->invnum;
   $date = $self->_date;
   $page = 1;
+  $agent = $self->cust_main->agent->agent;
 
   if ( $FS::cust_bill::invoice_lines ) {
     $total_pages =
@@ -1136,16 +1246,14 @@ sub print_text {
        #  );
 
   #and subroutine for the template
-
   sub FS::cust_bill::_template::invoice_lines {
-    my $lines = shift or return @buf;
+    my $lines = shift || scalar(@buf);
     map { 
       scalar(@buf) ? shift @buf : [ '', '' ];
     }
     ( 1 .. $lines );
   }
 
-
   #and fill it in
   $FS::cust_bill::_template::page = 1;
   my $lines;
@@ -1163,10 +1271,6 @@ sub print_text {
 
 =back
 
-=head1 VERSION
-
-$Id: cust_bill.pm,v 1.41.2.5 2002-10-12 10:15:53 ivan Exp $
-
 =head1 BUGS
 
 The delete method.