RT#34295: Error when attempting to create batch payments [better handling of empty...
authorJonathan Prykop <jonathan@freeside.biz>
Tue, 22 Dec 2015 06:28:53 +0000 (00:28 -0600)
committerJonathan Prykop <jonathan@freeside.biz>
Tue, 22 Dec 2015 06:28:53 +0000 (00:28 -0600)
FS/FS/cust_main/Billing_Batch.pm
FS/FS/pay_batch.pm
FS/bin/freeside-eftca-upload
FS/bin/freeside-paymentech-upload
FS/bin/freeside-rbc-upload
httemplate/misc/download-batch.cgi

index cdaf293..f91c5fb 100644 (file)
@@ -23,8 +23,6 @@ Options may include:
 B<amount>: the amount to be paid; defaults to the customer's balance minus
 any payments in transit.
 
 B<amount>: the amount to be paid; defaults to the customer's balance minus
 any payments in transit.
 
-B<payby>: the payment method; defaults to cust_main.payby
-
 B<realtime>: runs this as a realtime payment instead of adding it to a 
 batch.  Deprecated.
 
 B<realtime>: runs this as a realtime payment instead of adding it to a 
 batch.  Deprecated.
 
@@ -34,8 +32,9 @@ B<address1>, B<address2>, B<city>, B<state>, B<zip>, B<country>: sets
 the billing address for the payment; defaults to the customer's billing
 location.
 
 the billing address for the payment; defaults to the customer's billing
 location.
 
-B<payinfo>, B<paydate>, B<payname>: sets the payment account, expiration
-date, and name; defaults to those fields in cust_main.
+B<payby>, B<payinfo>, B<paydate>, B<payname>: sets the payment method, 
+payment account, expiration date, and name; defaults to those fields 
+in cust_main.
 
 =cut
 
 
 =cut
 
@@ -58,6 +57,13 @@ sub batch_card {
   
   my $invnum = delete $options{invnum};
 
   
   my $invnum = delete $options{invnum};
 
+  #pay fields should all come from either cust_payby or options, not both
+  #  in theory, could just pass payby, and use it to select cust_payby,
+  #  but nothing currently needs that, so not implementing it now
+  die "Incomplete payment details" 
+    if  ($options{payby} || $options{payinfo} || $options{paydate} || $options{payname})
+    && !($options{payby} && $options{payinfo} && $options{paydate} && $options{payname});
+
   #false laziness with Billing_Realtime
   my @cust_payby = qsearch({
     'table'     => 'cust_payby',
   #false laziness with Billing_Realtime
   my @cust_payby = qsearch({
     'table'     => 'cust_payby',
@@ -67,7 +73,10 @@ sub batch_card {
   });
 
   # batch can't try out every one like realtime, just use first one
   });
 
   # batch can't try out every one like realtime, just use first one
-  my $cust_payby = $cust_payby[0] || $self; # somewhat dubious
+  my $cust_payby = $cust_payby[0];
+
+  die "No customer payment info found"
+    unless $options{payinfo} || $cust_payby;
                                                    
   my $payby = $options{payby} || $cust_payby->payby;
 
                                                    
   my $payby = $options{payby} || $cust_payby->payby;
 
index e299dd9..35c79f5 100644 (file)
@@ -888,7 +888,8 @@ Prepare the batch to be exported.  This will:
   increment expiration dates that are in the past.
 - If this is the first download for this batch, adjust payment amounts to 
   not be greater than the customer's current balance.  If the customer's 
   increment expiration dates that are in the past.
 - If this is the first download for this batch, adjust payment amounts to 
   not be greater than the customer's current balance.  If the customer's 
-  balance is zero, the entry will be removed.
+  balance is zero, the entry will be removed (caution: all cust_pay_batch
+  entries might be removed!)
 
 Use this within a transaction.
 
 
 Use this within a transaction.
 
@@ -947,15 +948,6 @@ sub prepare_for_export {
       # else $balance >= $cust_pay_batch->amount
     }
 
       # else $balance >= $cust_pay_batch->amount
     }
 
-    # we might end up removing all cust_pay_batch above...
-    # probably the better way to handle this is to commit that removal,
-    # but no time to trace code & test that right now
-    #
-    # additionally, UI currently allows hand-deletion of all payments from a batch, meaning
-    # it's possible to try and process an empty batch...this is where we catch
-    # such an attempt, though it probably shouldn't be possible in the first place
-    return "Batch is empty" unless $self->cust_pay_batch;
-
     #need to do this after unbatch_and_delete
     my $error = $self->set_status('I');
     return "error updating pay_batch status: $error\n" if $error;
     #need to do this after unbatch_and_delete
     my $error = $self->set_status('I');
     return "error updating pay_batch status: $error\n" if $error;
@@ -973,6 +965,10 @@ module, in which case the configuration options are in 'batchconfig-FORMAT'.
 Alternatively, GATEWAY can be an L<FS::payment_gateway> object set to a
 L<Business::BatchPayment> module.
 
 Alternatively, GATEWAY can be an L<FS::payment_gateway> object set to a
 L<Business::BatchPayment> module.
 
+Returns the text of the batch.  If batch contains no cust_pay_batch entries
+(or has them all removed by L</prepare_for_export>) then the batch will be 
+resolved and a blank string will be returned.  All other errors are fatal.
+
 =cut
 
 sub export_batch {
 =cut
 
 sub export_batch {
@@ -1008,6 +1004,12 @@ sub export_batch {
   my $batchcount = 0;
 
   my @cust_pay_batch = $self->cust_pay_batch;
   my $batchcount = 0;
 
   my @cust_pay_batch = $self->cust_pay_batch;
+  unless (@cust_pay_batch) {
+    # if it's empty, just resolve the batch
+    $self->set_status('R');
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
 
   my $delim = exists($info->{'delimiter'}) ? $info->{'delimiter'} : "\n";
 
 
   my $delim = exists($info->{'delimiter'}) ? $info->{'delimiter'} : "\n";
 
@@ -1052,6 +1054,10 @@ that gateway via Business::BatchPayment. OPTIONS may include:
 
 - file: override the default transport and write to this file (name or handle)
 
 
 - file: override the default transport and write to this file (name or handle)
 
+If batch contains no cust_pay_batch entries (or has them all removed by 
+L</prepare_for_export>) then nothing will be transported (or written to 
+the override file) and the batch will be resolved.
+
 =cut
 
 sub export_to_gateway {
 =cut
 
 sub export_to_gateway {
@@ -1072,6 +1078,13 @@ sub export_to_gateway {
   my $processor = $gateway->batch_processor(%proc_opt);
 
   my @items = map { $_->request_item } $self->cust_pay_batch;
   my $processor = $gateway->batch_processor(%proc_opt);
 
   my @items = map { $_->request_item } $self->cust_pay_batch;
+  unless (@items) {
+    # if it's empty, just resolve the batch
+    $self->set_status('R');
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
+
   my $batch = Business::BatchPayment->create(Batch =>
     batch_id  => $self->batchnum,
     items     => \@items
   my $batch = Business::BatchPayment->create(Batch =>
     batch_id  => $self->batchnum,
     items     => \@items
index b66765a..107aa19 100755 (executable)
@@ -47,6 +47,10 @@ foreach my $pay_batch (@batches) {
   my $filename = time2str('%Y%m%d', time) . '-' . sprintf('%06d.csv',$batchnum);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'eft_canada');
   my $filename = time2str('%Y%m%d', time) . '-' . sprintf('%06d.csv',$batchnum);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'eft_canada');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   open OUT, ">$tmpdir/$filename";
   print OUT $text;
   close OUT;
   open OUT, ">$tmpdir/$filename";
   print OUT $text;
   close OUT;
index 5ae147d..799e6c4 100755 (executable)
@@ -68,6 +68,10 @@ foreach my $pay_batch (@batches) {
   my $filename = sprintf('%06d',$batchnum) . '-' .time2str('%Y%m%d%H%M%S', time);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'paymentech');
   my $filename = sprintf('%06d',$batchnum) . '-' .time2str('%Y%m%d%H%M%S', time);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'paymentech');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   $text =~ s!<fileID>FILEID</fileID>!<fileID>$filename</fileID>! 
     or log_and_die("couldn't find FILEID tag\n");
   open OUT, ">$tmpdir/$filename.xml";
   $text =~ s!<fileID>FILEID</fileID>!<fileID>$filename</fileID>! 
     or log_and_die("couldn't find FILEID tag\n");
   open OUT, ">$tmpdir/$filename.xml";
@@ -80,6 +84,7 @@ foreach my $pay_batch (@batches) {
   log_and_die("failed to create zip file\n") if (! -f "$tmpdir/$filename.zip" );
   push @filenames, $filename;
 }
   log_and_die("failed to create zip file\n") if (! -f "$tmpdir/$filename.zip" );
   push @filenames, $filename;
 }
+log_and_die("All batches empty\n") if !@filenames;
 
 my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
                    : 'orbitalbatch.paymentech.net');
 
 my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
                    : 'orbitalbatch.paymentech.net');
index 5250102..3fff32a 100755 (executable)
@@ -70,6 +70,10 @@ foreach my $pay_batch (@batches) {
   debug "Exporting batch $batchnum to $filename\n";
 
   my $text = $pay_batch->export_batch(format => 'RBC');
   debug "Exporting batch $batchnum to $filename\n";
 
   my $text = $pay_batch->export_batch(format => 'RBC');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   write_file("$tmpdir/$filename", $text);
 
   debug "Uploading $filename...";
   write_file("$tmpdir/$filename", $text);
 
   debug "Uploading $filename...";
index f3a31eb..c4bc37e 100644 (file)
@@ -1,4 +1,4 @@
-<% $pay_batch->export_batch(%opt) %><%init>
+<% $exporttext %><%init>
 
 #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
 http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
 
 #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
 http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
@@ -23,4 +23,15 @@ elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
 my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
 die "Batch not found: '$batchnum'" if !$pay_batch;
 
 my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
 die "Batch not found: '$batchnum'" if !$pay_batch;
 
+my $exporttext = $pay_batch->export_batch(%opt);
+unless ($exporttext) {
+  http_header('Content-Type' => 'text/html' );
+  $exporttext = <<EOF;
+<SCRIPT>
+alert('Batch was empty, and has been resolved');
+window.top.location.href = '${p}search/pay_batch.cgi?magic=_date;open=1;intransit=1;resolved=1';
+</SCRIPT>
+EOF
+}
+
 </%init>
 </%init>