+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', 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;
+ my $old = qsearchs('cust_pay_batch', { paybatchnum => $paybatchnum })
+ or return "paybatchnum $paybatchnum not found";
+ if ( $old->status ) {
+ # Handle the case where payments are rejected after the batch has been
+ # approved. FS::pay_batch::import_results won't allow results to be
+ # imported to a closed batch unless batch-manual_approval is enabled,
+ # so we don't check it here.
+# if ( $conf->exists('batch-manual_approval') and
+ if ( lc($old->status) eq 'approved' ) {
+ # 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 ) {
+ # should never happen...
+ return "failed to revoke paybatchnum $paybatchnum, payment not found";
+ }
+ $cust_pay->void($reason);
+ }
+ else {
+ # normal case: refuse to do anything
+ return "paybatchnum $paybatchnum already resolved ('".$old->status."')";
+ }
+ } # !$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";
+ }
+ my $due_cust_event = $new->cust_main->due_cust_event(
+ 'eventtable' => 'cust_pay_batch',
+ 'objects' => [ $new ],
+ );
+ if ( !ref($due_cust_event) ) {
+ return $due_cust_event;
+ }
+ # XXX breaks transaction integrity
+ foreach my $cust_event (@$due_cust_event) {
+ next unless $cust_event->test_conditions;
+ if ( my $error = $cust_event->do_event() ) {
+ return $error;