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.
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
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',
});
# 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;
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.
# 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;
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 {
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";
- 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 {
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 $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;
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";
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');
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...";
-<% $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...
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>