Merge branch 'FREESIDE_3_BRANCH' of ssh://git.freeside.biz/home/git/freeside into...
authorChristopher Burger <burgerc@freeside.biz>
Wed, 13 Mar 2019 13:22:42 +0000 (09:22 -0400)
committerChristopher Burger <burgerc@freeside.biz>
Wed, 13 Mar 2019 13:22:42 +0000 (09:22 -0400)
14 files changed:
FS/FS/Cron/pay_batch.pm
FS/FS/Schema.pm
FS/FS/cust_main.pm
FS/FS/cust_main/Billing_Batch.pm
FS/FS/pay_batch.pm
FS/FS/pay_batch/RBC.pm
FS/bin/freeside-eftca-upload
FS/bin/freeside-paymentech-upload
FS/bin/freeside-rbc-upload
httemplate/edit/process/cust_refund.cgi
httemplate/misc/download-batch.cgi
httemplate/search/elements/cust_pay_batch_top.html
httemplate/search/pay_batch.cgi
httemplate/view/cust_main/payment_history/payment.html

index c9039b8..faa9cd9 100644 (file)
@@ -67,7 +67,7 @@ sub pay_batch_submit {
     my ($gateway, $payby, $agentnum) = @$config;
     if ( $gateway->batch_processor->can('default_transport') ) {
 
-      my $search = { status => 'O', payby => $payby };
+      my $search = { status => 'O', payby => $payby, type => 'DEBIT' };
       $search->{agentnum} = $agentnum if $agentnum;
 
       foreach my $pay_batch ( qsearch('pay_batch', $search) ) {
index a804b12..b043afc 100644 (file)
@@ -1881,6 +1881,7 @@ sub tables_hashref {
         'upload',         @date_type,     '', '', 
         'title',   'varchar', 'NULL',255, '', '',
         'processor_id',   'varchar', 'NULL',255, '', '',
+        'type',      'char',  '',  6, 'DEBIT', '', # DEBIT/CREDIT
       ],
       'primary_key' => 'batchnum',
       'unique' => [],
index ff75eb6..03300a0 100644 (file)
@@ -2775,6 +2775,10 @@ sub batch_card {
                               );
   }
 
+  my $paycode = $options{paycode} || '';
+  my $batch_type = "DEBIT";
+  $batch_type = "CREDIT" if $paycode eq 'C';
+
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
@@ -2787,6 +2791,7 @@ sub batch_card {
   my %pay_batch = (
     'status' => 'O',
     'payby'  => FS::payby->payby2payment($payby),
+    'type' => $batch_type,
   );
   $pay_batch{agentnum} = $self->agentnum if $conf->exists('batch-spoolagent');
 
index 0e713e9..c1bb35f 100644 (file)
@@ -83,6 +83,10 @@ sub batch_card {
                               );
   }
 
+  my $paycode= $options{paycode} || '';
+  my $batch_type = "DEBIT";
+  $batch_type = "CREDIT" if $paycode eq 'C';
+
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
@@ -95,6 +99,7 @@ sub batch_card {
   my %pay_batch = (
     'status' => 'O',
     'payby'  => FS::payby->payby2payment($payby),
+    'type'   => $batch_type,
   );
   $pay_batch{agentnum} = $self->agentnum if $conf->exists('batch-spoolagent');
 
index 1c0a28a..c3cd0de 100644 (file)
@@ -58,6 +58,10 @@ from FS::Record.  The following fields are currently supported:
 
 =item title - unique batch identifier
 
+=item processor_id -
+
+=item type - batch type payents (DEBIT), or refunds (CREDIT)
+
 For incoming batches, the combination of 'title', 'payby', and 'agentnum'
 must be unique.
 
@@ -1148,7 +1152,136 @@ sub manual_approve {
   return;
 }
 
+=item batch_download_formats
+
+returns a hash of batch download formats.
+
+my %download_formats = FS::pay_batch::batch_download_formats;
+
+=cut
+
+sub batch_download_formats {
+
+  my @formats = (
+    ''              =>
+        'Default batch mode',
+    'NACHA'         =>
+        '94 byte NACHA',
+    'csv-td_canada_trust-merchant_pc_batch' =>
+        'CSV file for TD Canada Trust Merchant PC Batch',
+    'csv-chase_canada-E-xactBatch' =>
+        'CSV file for Chase Canada E-xactBatch',
+    'PAP'           =>
+        '80 byte file for TD Canada Trust PAP Batch',
+    'BoM'           =>
+        'Bank of Montreal ECA batch',
+    'ach-spiritone' =>
+        'Spiritone ACH batch',
+    'paymentech'    =>
+        'XML file for Chase Paymentech',
+    'RBC'           =>
+        'Royal Bank of Canada PDS batch',
+    'td_eft1464'    =>
+        '1464 byte file for TD Commercial Banking EFT',
+    'eft_canada'    =>
+        'EFT Canada CSV batch',
+    'CIBC'          =>
+        '80 byte file for Canadian Imperial Bank of Commerce',
+    # insert new batch formats here
+  );
+
+}
+
+=item batch_download_formats
+
+returns a hash of batch download formats.
+
+my %download_formats = FS::pay_batch::batch_download_formats;
+
+=cut
+
+sub can_handle_electronic_refunds {
+
+  my $self = shift;
+  my $format = shift;
+  my $conf = new FS::Conf;
+
+  tie my %download_formats, 'Tie::IxHash', batch_download_formats;
+
+  my %paybatch_mods = (
+    'NACHA'                                 => 'nacha',
+    'csv-td_canada_trust-merchant_pc_batch' => 'td_canada_trust',
+    'csv-chase_canada-E-xactBatch'          => 'chase-canada',
+    'PAP'                                   => 'PAP',
+    'BoM'                                   => 'BoM',
+    'ach-spiritone'                         => 'ach_spiritone',
+    'paymentech'                            => 'paymentech',
+    'RBC'                                   => 'RBC',
+    'td_eft1464'                            => 'td_eft1464',
+    'eft_canada'                            => 'eft_canada',
+    'CIBC'                                  => 'CIBC',
+  );
+
+  %download_formats = ( $format => $download_formats{$format}, ) if $format;
+
+  foreach my $key (keys %download_formats) {
+    my $mod = "FS::pay_batch::".$paybatch_mods{$key};
+    if ($mod->can('can_handle_credits')) {
+      return '1' if $conf->exists('batchconfig-'.$key);
+    }
+  }
+
+  return;
+
+}
+
+use FS::upgrade_journal;
 sub _upgrade_data {
+
+  # check if there are any pending batch refunds and no download format configured
+  # that allows electronic refunds.
+  unless ( FS::upgrade_journal->is_done('removed_refunds_nodownload_format') ) {
+
+    ## get a list of all refunds in batches.
+    my $extrasql = " LEFT JOIN pay_batch USING ( batchnum ) WHERE cust_pay_batch.paycode = 'C' AND pay_batch.download IS NULL";
+
+    my @batch_refunds = qsearch({
+      'table'   => 'cust_pay_batch',
+      'select'  => 'cust_pay_batch.*',
+      'extra_sql' => $extrasql,
+    });
+
+    warn "found ".scalar @batch_refunds." batch refunds.\n";
+    warn "Searching for their cust refunds...\n" if (scalar @batch_refunds > 0);
+    my ($delete_cust_refund_error, $delete_cust_pay_batch_error);
+
+    ## find the cust_pay_refund for all those
+    foreach (@batch_refunds) {
+      my $extra_batch_refund_sql = " WHERE custnum = '".$_->{Hash}->{custnum}."' AND refund = '".$_->{Hash}->{amount}."' ORDER BY _date DESC LIMIT 1";
+      my $cust_refund = qsearchs({
+        'table'  => 'cust_refund',
+        'extra_sql' => $extra_batch_refund_sql,
+      });
+
+      warn "found cust refund number ".$cust_refund->{Hash}->{refundnum}.", now to delete it.\n" if $cust_refund;
+
+      ## delete the cust_pay_refund
+      $delete_cust_refund_error = $cust_refund->delete if $cust_refund;
+      warn "could not delete cust refund $delete_cust_refund_error\n" if $delete_cust_refund_error;
+
+      ## delete the refund from the batch.
+      unless ($delete_cust_refund_error) {
+        $delete_cust_pay_batch_error = $_->unbatch_and_delete;
+        warn "could not delete cust refund $delete_cust_pay_batch_error\n" if $delete_cust_pay_batch_error;
+      }
+
+      if ($delete_cust_refund_error || $delete_cust_pay_batch_error) { die "Could no delete cust_pay_batch refund\n"; }
+      else { warn "cust refund ".$cust_refund->{Hash}->{refundnum}." deleted\n"; }
+    }
+
+    FS::upgrade_journal->set_done('removed_refunds_nodownload_format');
+  }
+
   # Set up configuration for gateways that have a Business::BatchPayment
   # module.
   
index 6a23542..00fe9a1 100644 (file)
@@ -94,22 +94,17 @@ $name = 'RBC';
   },
   'begin_condition' => sub {
       my $hash = shift;
-      # Debit Detail Record
-      if ($hash->{recordtype} eq '1') {
+      # Detail Record
+      if ($hash->{recordtype} eq '1' || $hash->{recordtype} eq '2') {
         $declined = {};
         $totaloffset = 0;
         return 1;
-      # Credit Detail Record, will immediately trigger end condition & error
-      } elsif ($hash->{recordtype} eq '2') { 
-        return 1;
       } else {
         return 0;
       }
   },
   'end_hook'    => sub {
       my( $hash, $total, $line ) = @_;
-      return "Can't process Credit Detail Record, aborting import"
-        if ($hash->{'recordtype'} eq '2');
       $total += $totaloffset;
       $total = sprintf("%.2f", $total);
       # We assume here that this is an 'All Records' or 'Input Records' report.
@@ -120,8 +115,7 @@ $name = 'RBC';
   },
   'end_condition' => sub {
       my $hash = shift;
-      return ($hash->{recordtype} eq '4')  # Client Trailer Record
-          || ($hash->{recordtype} eq '2'); # Credit Detail Record, will throw error in end_hook
+      return ($hash->{recordtype} eq '4');  # Client Trailer Record
   },
   'skip_condition' => sub {
       my $hash = shift;
@@ -154,7 +148,8 @@ $name = 'RBC';
     my $pay_batch = shift;
     my $mode = $testmode ? 'TEST' : 'PROD';
     my $filenum = $testmode ? 'TEST' : sprintf("%04u", $pay_batch->batchnum);
-    '$$AAPASTD0152['.$mode.'[NL$$'."\n".
+    my $qualifier = $pay_batch->type eq 'CREDIT' ? 'D' : 'A';
+    '$$AAP'.$qualifier.'STD0152['.$mode.'[NL$$'."\n".
     '000001'.
     'A'.
     'HDR'.
@@ -221,13 +216,15 @@ $name = 'RBC';
   },
   footer => sub {
     my ($pay_batch, $batchcount, $batchtotal) = @_;
+
+    my $batch_info = '0' x 20 . sprintf("%06u", $batchcount) . sprintf("%014.0f", $batchtotal*100);
+    $batch_info = sprintf("%06u", $batchcount) . sprintf("%014.0f", $batchtotal*100) . '0' x 20 if ($pay_batch->type eq 'CREDIT');
+
     sprintf("%06u", $i + 1).
     'Z'.
     'TRL'.
     sprintf("%10s", $client_num).
-    '0' x 20 .
-    sprintf("%06u", $batchcount).
-    sprintf("%014.0f", $batchtotal*100).
+    $batch_info.
     '00' .
     '000000' . # total number of customer information records
     ' ' x 84
index 55e9774..788de16 100755 (executable)
@@ -32,8 +32,12 @@ my @batches;
 
 if($opt_a) {
   local $@;
+
+  my %criteria= ( 'status' => 'O', 'payby' => 'CHEK' );
+  $criteria{'type'} = 'DEBIT' unless FS::pay_batch->can_handle_electronic_refunds('eft_canada');
+
   eval {
-    @batches = qsearch('pay_batch', { 'status' => 'O', 'payby' => 'CHEK' })
+    @batches = qsearch('pay_batch', \%criteria)
   };
   log_error_and_die ("Fatal database error: $@")
     if $@;
@@ -51,6 +55,12 @@ else {
     if $@;
 
   log_error_and_die( "Can't find payment batch '$batchnum'\n" ) if !@batches;
+
+  if ($batches[0]->type eq "CREDIT") {
+    warn "running credit\n";
+    log_error_and_die( "Batch number $batchnum is a credit (batch refund) batch, and this format can not handle batch refunds.\n" )
+      unless FS::pay_batch->can_handle_electronic_refunds('eft_canada');
+  }
 }
 
 my $conf = new FS::Conf;
index 137c38f..8e504d2 100755 (executable)
@@ -41,6 +41,7 @@ my @batches;
 if($opt_a) {
   my %criteria = (status => 'O');
   $criteria{'payby'} = uc($opt_p) if $opt_p;
+  $criteria{'type'} = 'DEBIT' unless FS::pay_batch->can_handle_electronic_refunds('paymentech');
   @batches = qsearch('pay_batch', \%criteria);
   log_and_die("No open batches found".($opt_p ? " of type '$opt_p'" : '').".\n")
     if !@batches;
@@ -50,6 +51,12 @@ else {
   log_and_die("batchnum not passed\n".&usage) if !$batchnum;
   @batches = qsearchs('pay_batch', { batchnum => $batchnum } );
   log_and_die("Can't find payment batch '$batchnum'\n") if !@batches;
+
+  if ($batches[0]->type eq "CREDIT") {
+    warn "running credit\n";
+    log_and_die( "Batch number $batchnum is a credit (batch refund) batch, and this format can not handle batch refunds.\n" )
+      unless FS::pay_batch->can_handle_electronic_refunds('paymentech');
+  }
 }
 
 my $conf = new FS::Conf;
index 5250102..5be31ae 100755 (executable)
@@ -33,6 +33,7 @@ my @batches;
 if($opt_a) {
   my %criteria = (status => 'O');
   $criteria{'payby'} = uc($opt_p) if $opt_p;
+  $criteria{'type'} = 'DEBIT' unless FS::pay_batch->can_handle_electronic_refunds('RBC');
   @batches = qsearch('pay_batch', \%criteria);
   die "No open batches found".($opt_p ? " of type '$opt_p'" : '').".\n" 
     if !@batches;
@@ -42,6 +43,11 @@ else {
   die &usage if !$batchnum;
   @batches = qsearchs('pay_batch', { batchnum => $batchnum } );
   die "Can't find payment batch '$batchnum'\n" if !@batches;
+  if ($batches[0]->type eq "CREDIT") {
+    warn "running credit\n";
+    log_and_die( "Batch number $batchnum is a credit (batch refund) batch, and this format can not handle batch refunds.\n" )
+      unless FS::pay_batch->can_handle_electronic_refunds('RBC');
+  }
 }
 
 my $conf = new FS::Conf;
index 33bc886..3a175ea 100755 (executable)
@@ -39,6 +39,8 @@ $cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum";
 my ($reasonnum, $error) = $m->comp('/misc/process/elements/reason');
 $cgi->param('reasonnum', $reasonnum) unless $error;
 
+$error = "No batch download format configured that allows electronic refunds" unless (FS::pay_batch->can_handle_electronic_refunds && !$error);
+
 if ( $error ) {
   # do nothing
 } elsif ( $payby =~ /^(CARD|CHEK)$/ ) { 
index 7b56f2a..5db563a 100644 (file)
@@ -4,6 +4,7 @@
 http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
 
 my $batchnum;
+
 if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
   $batchnum = $1;
 } else {
@@ -29,7 +30,7 @@ die "Batch not found: '$batchnum'" if !$pay_batch;
 
 if ($pay_batch->{Hash}->{arecredits}) {
   my $export_format = "FS::pay_batch::".$opt{'format'};
-    die "This format can not handle refunds." unless $export_format->can('can_handle_credits');
+  die "You are trying to download a credit (batch refund) batch and The format ".$opt{'format'}." can not handle refunds.\n" unless $export_format->can('can_handle_credits');
 }
 
 </%init>
index 626d7c3..eee81dd 100644 (file)
@@ -135,23 +135,7 @@ my $batchnum = $pay_batch->batchnum;
 
 my $fixed = $conf->config("batch-fixed_format-$payby");
 
-tie my %download_formats, 'Tie::IxHash', (
-  '' => 'Default batch mode',
-  'NACHA' => '94 byte NACHA',
-  'csv-td_canada_trust-merchant_pc_batch' => 
-                'CSV file for TD Canada Trust Merchant PC Batch',
-  'csv-chase_canada-E-xactBatch' =>
-                'CSV file for Chase Canada E-xactBatch',
-  'PAP' => '80 byte file for TD Canada Trust PAP Batch',
-  'BoM' => 'Bank of Montreal ECA batch',
-  'ach-spiritone' => 'Spiritone ACH batch',
-  'paymentech' => 'XML file for Chase Paymentech',
-  'RBC' => 'Royal Bank of Canada PDS batch',
-  'td_eft1464' => '1464 byte file for TD Commercial Banking EFT',
-  'eft_canada' => 'EFT Canada CSV batch',
-  'CIBC'       => '80 byte file for Canadian Imperial Bank of Commerce',
-# insert new batch formats here
-);
+tie my %download_formats, 'Tie::IxHash', FS::pay_batch::batch_download_formats;
 
 tie my %upload_formats, 'Tie::IxHash', (
   %download_formats,
@@ -160,7 +144,13 @@ tie my %upload_formats, 'Tie::IxHash', (
   'td_eftret' => 'TD EFT Returned Items',
 );
 delete $upload_formats{'td_eft1464'};
-$upload_formats{'PAP'} = '264 byte results for TD Canada Trust PAP Batch',
+$upload_formats{'PAP'} = '264 byte results for TD Canada Trust PAP Batch';
+
+if ($pay_batch->type eq "CREDIT") {
+  foreach my $key (keys %download_formats) {
+    delete $download_formats{$key} unless FS::pay_batch->can_handle_electronic_refunds($key);
+  }
+}
 
 my %statustext = ( 'O' => 'open', 'I' => 'in transit', 'R' => 'resolved' );
 
index 40df5aa..8fe4351 100755 (executable)
                                     ],
                 'align'         => 'rcllrrrrc',
                 'fields'        => [ 'batchnum',
-                                     sub { 
-                                       FS::payby->shortname(shift->payby);
-                                     },
+                                     sub {
+                my $self = shift;
+                my $type = $self->type eq 'CREDIT' ? 'CREDIT' : '';
+                $type ." " . FS::payby->shortname($self->payby);
+                                                 },
                                       sub {
                                        my $self = shift;
                                        my $_date = $self->download;
index cc5789b..3fe588b 100644 (file)
@@ -177,6 +177,7 @@ if (    $cust_pay->closed !~ /^Y/i
             qq! TITLE="! . $refundtitle
             . '">' . emt('refund') . '</A>)';
 }
+$refund = '' if ($cust_pay->batchnum && !FS::pay_batch->can_handle_electronic_refunds);
 
 my $void = '';
 my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK)$/