remove ancient invoice events
[freeside.git] / FS / FS / cust_pay_batch.pm
index f5e6a4b..a4b4957 100644 (file)
@@ -1,21 +1,20 @@
 package FS::cust_pay_batch;
+use base qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record );
 
 use strict;
-use vars qw( @ISA $DEBUG );
+use vars qw( $DEBUG );
 use Carp qw( confess );
 use Business::CreditCard 0.28;
 use FS::Record qw(dbh qsearch qsearchs);
-use FS::payinfo_Mixin;
-use FS::cust_main;
-use FS::cust_bill;
-
-@ISA = qw( FS::payinfo_Mixin FS::Record );
 
 # 1 is mostly method/subroutine entry and options
 # 2 traces progress of some operations
 # 3 is even more information including possibly sensitive data
 $DEBUG = 0;
 
+#@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
+
 =head1 NAME
 
 FS::cust_pay_batch - Object methods for batch cards
@@ -50,7 +49,7 @@ following fields are currently supported:
 
 =item batchnum - indentifies group in batch
 
-=item payby - CARD/CHEK/LECB/BILL/COMP
+=item payby - CARD/CHEK
 
 =item payinfo
 
@@ -80,7 +79,12 @@ following fields are currently supported:
 
 =item country 
 
-=item status
+=item status - 'Approved' or 'Declined'
+
+=item error_message - the error returned by the gateway if any
+
+=item failure_status - the normalized L<Business::BatchPayment> failure 
+status, if any
 
 =back
 
@@ -150,7 +154,7 @@ sub check {
 
   if ( $self->exp eq '' ) {
     return "Expiration date required"
-      unless $self->payby =~ /^(CHEK|DCHK|LECB|WEST)$/;
+      unless $self->payby =~ /^(CHEK|DCHK|WEST)$/;
     $self->exp('');
   } else {
     if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) {
@@ -197,13 +201,30 @@ sub check {
 Returns the customer (see L<FS::cust_main>) for this batched credit card
 payment.
 
+=item expmmyy
+
+Returns the credit card expiration date in MMYY format.  If this is a 
+CHEK payment, returns an empty string.
+
 =cut
 
-sub cust_main {
+sub expmmyy {
   my $self = shift;
-  qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+  if ( $self->payby eq 'CARD' ) {
+    $self->get('exp') =~ /^(\d{4})-(\d{2})-(\d{2})$/;
+    return sprintf('%02u%02u', $2, ($1 % 100));
+  }
+  else {
+    return '';
+  }
 }
 
+=item pay_batch
+
+Returns the payment batch this payment belongs to (L<FS::pay_batch).
+
+=cut
+
 #you know what, screw this in the new world of events.  we should be able to
 #get the event defs to retry (remove once.pm condition, add every.pm) without
 #mucking about with statuses of previous cust_event records.  right?
@@ -225,57 +246,28 @@ sub retriable {
   confess "deprecated method cust_pay_batch->retriable called; try removing ".
           "the once condition and adding an every condition?";
 
-  my $self = shift;
-
-  local $SIG{HUP} = 'IGNORE';        #Hmm
-  local $SIG{INT} = 'IGNORE';
-  local $SIG{QUIT} = 'IGNORE';
-  local $SIG{TERM} = 'IGNORE';
-  local $SIG{TSTP} = 'IGNORE';
-  local $SIG{PIPE} = 'IGNORE';
-
-  my $oldAutoCommit = $FS::UID::AutoCommit;
-  local $FS::UID::AutoCommit = 0;
-  my $dbh = dbh;
-
-  my $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
-    or return "event $self->eventnum references nonexistant invoice $self->invnum";
-
-  warn "cust_pay_batch->retriable working with self of " . $self->paybatchnum . " and invnum of " . $self->invnum;
-  my @cust_bill_event =
-    sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds }
-      grep {
-        $_->part_bill_event->eventcode =~ /\$cust_bill->batch_card/
-         && $_->status eq 'done'
-         && ! $_->statustext
-       }
-      $cust_bill->cust_bill_event;
-  # complain loudly if scalar(@cust_bill_event) > 1 ?
-  my $error = $cust_bill_event[0]->retriable;
-  if ($error ) {
-    # gah, even with transactions.
-    $dbh->commit if $oldAutoCommit; #well.
-    return "error marking invoice event retriable: $error";
-  }
-  '';
 }
 
-=item approve PAYBATCH
+=item approve OPTIONS
 
 Approve this payment.  This will replace the existing record with the 
 same paybatchnum, set its status to 'Approved', and generate a payment 
 record (L<FS::cust_pay>).  This should only be called from the batch 
 import process.
 
+OPTIONS may contain "gatewaynum", "processor", "auth", and "order_number".
+
 =cut
 
 sub approve {
   # to break up the Big Wall of Code that is import_results
   my $new = shift;
-  my $paybatch = shift;
+  my %opt = @_;
   my $paybatchnum = $new->paybatchnum;
   my $old = qsearchs('cust_pay_batch', { paybatchnum => $paybatchnum })
     or return "paybatchnum $paybatchnum not found";
+  # leave these restrictions in place until TD EFT is converted over
+  # to B::BP
   return "paybatchnum $paybatchnum already resolved ('".$old->status."')" 
     if $old->status;
   $new->status('Approved');
@@ -286,12 +278,17 @@ sub approve {
   my $cust_pay = new FS::cust_pay ( {
       'custnum'   => $new->custnum,
       'payby'     => $new->payby,
-      'paybatch'  => $paybatch,
       'payinfo'   => $new->payinfo || $old->payinfo,
       'paid'      => $new->paid,
       '_date'     => $new->_date,
       'usernum'   => $new->usernum,
+      'batchnum'  => $new->batchnum,
+      'gatewaynum'    => $opt{'gatewaynum'},
+      'processor'     => $opt{'processor'},
+      'auth'          => $opt{'auth'},
+      'order_number'  => $opt{'order_number'} 
     } );
+
   $error = $cust_pay->insert;
   if ( $error ) {
     return "error inserting payment for paybatchnum $paybatchnum: $error\n";
@@ -300,20 +297,24 @@ sub approve {
   return;
 }
 
-=item decline [ REASON ]
+=item decline [ REASON [ STATUS ] ]
 
 Decline this payment.  This will replace the existing record with the 
 same paybatchnum, set its status to 'Declined', and run collection events
 as appropriate.  This should only be called from the batch import process.
 
 REASON is a string description of the decline reason, defaulting to 
-'Returned payment'.
+'Returned payment', and will go into the "error_message" field.
+
+STATUS is a normalized failure status defined by L<Business::BatchPayment>,
+and will go into the "failure_status" field.
 
 =cut
 
 sub decline {
   my $new = shift;
   my $reason = shift || 'Returned payment';
+  my $failure_status = shift || '';
   #my $conf = new FS::Conf;
 
   my $paybatchnum = $new->paybatchnum;
@@ -329,6 +330,12 @@ sub decline {
       # Void the payment
       my $cust_pay = qsearchs('cust_pay', { 
           custnum  => $new->custnum,
+          batchnum => $new->batchnum
+        });
+      # these should all be migrated over, but if it's not found, look for
+      # batchnum in the 'paybatch' field also
+      $cust_pay ||= qsearchs('cust_pay', { 
+          custnum  => $new->custnum,
           paybatch => $new->batchnum
         });
       if ( !$cust_pay ) {
@@ -343,6 +350,8 @@ sub decline {
     }
   } # !$old->status
   $new->status('Declined');
+  $new->error_message($reason);
+  $new->failure_status($failure_status);
   my $error = $new->replace($old);
   if ( $error ) {
     return "error updating status of paybatchnum $paybatchnum: $error\n";
@@ -364,6 +373,62 @@ sub decline {
   return;
 }
 
+=item request_item [ OPTIONS ]
+
+Returns a L<Business::BatchPayment::Item> object for this batch payment
+entry.  This can be submitted to a processor.
+
+OPTIONS can be a list of key/values to append to the attributes.  The most
+useful case of this is "process_date" to set a processing date based on the
+date the batch is being submitted.
+
+=cut
+
+sub request_item {
+  local $@;
+  my $self = shift;
+
+  eval "use Business::BatchPayment;";
+  die "couldn't load Business::BatchPayment: $@" if $@;
+
+  my $cust_main = $self->cust_main;
+  my $location = $cust_main->bill_location;
+  my $pay_batch = $self->pay_batch;
+
+  my %payment;
+  $payment{payment_type} = FS::payby->payby2bop( $pay_batch->payby );
+  if ( $payment{payment_type} eq 'CC' ) {
+    $payment{card_number} = $self->payinfo,
+    $payment{expiration}  = $self->expmmyy,
+  } elsif ( $payment{payment_type} eq 'ECHECK' ) {
+    $self->payinfo =~ /(\d+)@(\d+)/; # or else what?
+    $payment{account_number} = $1;
+    $payment{routing_code} = $2;
+    $payment{account_type} = $cust_main->paytype;
+    # XXX what if this isn't their regular payment method?
+  } else {
+    die "unsupported BatchPayment method: ".$pay_batch->payby;
+  }
+
+  Business::BatchPayment->create(Item =>
+    # required
+    action      => 'payment',
+    tid         => $self->paybatchnum,
+    amount      => $self->amount,
+
+    # customer info
+    customer_id => $self->custnum,
+    first_name  => $cust_main->first,
+    last_name   => $cust_main->last,
+    company     => $cust_main->company,
+    address     => $location->address1,
+    ( map { $_ => $location->$_ } qw(address2 city state country zip) ),
+    
+    invoice_number  => $self->invnum,
+    %payment,
+  );
+}
+
 =back
 
 =head1 BUGS