RT#37064: Add action link to manually refund a payment
authorJonathan Prykop <jonathan@freeside.biz>
Fri, 28 Aug 2015 05:56:49 +0000 (00:56 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Fri, 28 Aug 2015 05:56:49 +0000 (00:56 -0500)
FS/FS/access_user.pm
FS/FS/cust_main/Billing_Realtime.pm
FS/FS/cust_pay.pm
httemplate/edit/cust_refund.cgi
httemplate/edit/process/cust_refund.cgi
httemplate/misc/unapply-cust_pay.cgi
httemplate/view/cust_main/payment_history.html
httemplate/view/cust_main/payment_history/payment.html

index a3f55bc..ecab32d 100644 (file)
@@ -587,6 +587,43 @@ sub access_right {
 
 }
 
+=item refund_rights PAYBY
+
+Accepts payment $payby (BILL,CASH,MCRD,MCHK,CARD,CHEK) and returns a
+list of the refund rights associated with that $payby.
+
+Returns empty list if $payby wasn't recognized.
+
+=cut
+
+sub refund_rights {
+  my $self = shift;
+  my $payby = shift;
+  my @rights = ();
+  push @rights, 'Post refund'                if $payby =~ /^(BILL|CASH|MCRD|MCHK)$/;
+  push @rights, 'Post check refund'          if $payby eq 'BILL';
+  push @rights, 'Post cash refund '          if $payby eq 'CASH';
+  push @rights, 'Refund payment'             if $payby =~ /^(CARD|CHEK)$/;
+  push @rights, 'Refund credit card payment' if $payby eq 'CARD';
+  push @rights, 'Refund Echeck payment'      if $payby eq 'CHEK';
+  return @rights;
+}
+
+=item refund_access_right PAYBY
+
+Returns true if user has L</access_right> for any L</refund_rights>
+for the specified payby.
+
+=cut
+
+sub refund_access_right {
+  my $self = shift;
+  my $payby = shift;
+  my @rights = $self->refund_rights($payby);
+  return '' unless @rights;
+  return $self->access_right(\@rights);
+}
+
 =item default_customer_view
 
 Returns the default customer view for this user, from the 
index d973896..fda3ae0 100644 (file)
@@ -1649,6 +1649,7 @@ sub realtime_refund_bop {
 
   $order_number = $refund->order_number if $refund->can('order_number');
 
+  # change this to just use $cust_pay->delete_cust_bill_pay?
   while ( $cust_pay && $cust_pay->unapplied < $amount ) {
     my @cust_bill_pay = $cust_pay->cust_bill_pay;
     last unless @cust_bill_pay;
index 5d4f67f..59d7774 100644 (file)
@@ -821,6 +821,102 @@ sub amount {
   $self->paid();
 }
 
+=item delete_cust_bill_pay OPTIONS
+
+Deletes all associated cust_bill_pay records.
+
+If option 'unapplied' is a specified, only deletes until
+this object's 'unapplied' value is >= the specified amount.  
+(Deletes in order returned by L</cust_bill_pay>.)
+
+=cut
+
+sub delete_cust_bill_pay {
+  my $self = shift;
+  my %opt = @_;
+
+  local $SIG{HUP} = 'IGNORE';
+  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 $unapplied = $self->unapplied; #only need to look it up once
+
+  my $error = '';
+
+  # Maybe we should reverse the order these get deleted in?
+  # ie delete newest first?
+  # keeping consistent with how bop refunds work, for now...
+  foreach my $cust_bill_pay ( $self->cust_bill_pay ) {
+    last if $opt{'unapplied'} && ($unapplied > $opt{'unapplied'});
+    $unapplied += $cust_bill_pay->amount;
+    $error = $cust_bill_pay->delete;
+    last if $error;
+  }
+
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  return '';
+}
+
+=item refund HASHREF
+
+Accepts input for creating a new FS::cust_refund object.
+Unapplies payment from invoices up to the amount of the refund,
+creates the refund and applies payment to refund.  Allows entire
+process to be handled in one transaction.
+
+Causes a fatal error if called on CARD or CHEK payments.
+
+=cut
+
+sub refund {
+  my $self = shift;
+  my $hash = shift;
+  die "Cannot call cust_pay->refund on " . $self->payby
+    if grep { $_ eq $self->payby } qw(CARD CHEK);
+
+  local $SIG{HUP} = 'IGNORE';
+  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 $error = $self->delete_cust_bill_pay('amount' => $hash->{'amount'});
+
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $hash->{'paynum'} = $self->paynum;
+  my $new = new FS::cust_refund ( $hash );
+  $error = $new->insert;
+
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  return '';
+}
+
 =back
 
 =head1 CLASS METHODS
index fa049a3..bfcbfe7 100755 (executable)
@@ -139,16 +139,8 @@ my $payinfo = $cgi->param('payinfo');
 my $reason  = $cgi->param('reason');
 my $link    = $cgi->param('popup') ? 'popup' : '';
 
-my @rights = ();
-push @rights, 'Post refund'                if $payby =~ /^(BILL|CASH|MCRD|MCHK)$/;
-push @rights, 'Post check refund'          if $payby eq 'BILL';
-push @rights, 'Post cash refund '          if $payby eq 'CASH';
-push @rights, 'Refund payment'             if $payby =~ /^(CARD|CHEK)$/;
-push @rights, 'Refund credit card payment' if $payby eq 'CARD';
-push @rights, 'Refund Echeck payment'      if $payby eq 'CHEK';
-
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right(\@rights);
+  unless $FS::CurrentUser::CurrentUser->refund_access_right($payby);
 
 my( $paynum, $cust_pay ) = ( '', '' );
 if ( $cgi->param('paynum') =~ /^(\d+)$/ ) {
index 52fede8..ce72c92 100755 (executable)
@@ -12,7 +12,7 @@
 
     </BODY></HTML>
 %  } else {
-<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum;show=payment_history") %>
 %  }
 %}
 <%init>
@@ -30,16 +30,8 @@ my $link    = $cgi->param('popup') ? 'popup' : '';
 
 my $payby = $cgi->param('payby');
 
-my @rights = ();
-push @rights, 'Post refund'                if $payby =~ /^(BILL|CASH|MCRD|MCHK)$/;
-push @rights, 'Post check refund'          if $payby eq 'BILL';
-push @rights, 'Post cash refund '          if $payby eq 'CASH';
-push @rights, 'Refund payment'             if $payby =~ /^(CARD|CHEK)$/;
-push @rights, 'Refund credit card payment' if $payby eq 'CARD';
-push @rights, 'Refund Echeck payment'      if $payby eq 'CHEK';
-
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right(\@rights);
+  unless $FS::CurrentUser::CurrentUser->refund_access_right($payby);
 
 $cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum";
 my ($reasonnum, $error) = $m->comp('/misc/process/elements/reason');
@@ -63,12 +55,19 @@ if ( $error ) {
                                                   'reason' => $reason,
                                                   %options );
 } else {
-  my $new = new FS::cust_refund ( {
-    map {
-      $_, scalar($cgi->param($_));
-    } fields('cust_refund') #huh? , 'paynum' )
-  } );
-  $error = $new->insert;
+  my %hash = map {
+    $_, scalar($cgi->param($_))
+  } fields('cust_refund');
+  my $paynum = $cgi->param('paynum');
+  $paynum =~ /^(\d*)$/ or die "Illegal paynum!";
+  if ($paynum) {
+    my $cust_pay = qsearchs('cust_pay',{ 'paynum' => $paynum });
+    die "Could not find paynum $paynum" unless $cust_pay;
+    $error = $cust_pay->refund(\%hash);
+  } else {
+    my $new = new FS::cust_refund ( \%hash );
+    $error = $new->insert;
+  }
 }
 
 </%init>
index 8cdac18..b0343d0 100755 (executable)
@@ -12,9 +12,7 @@ my $paynum = $1;
 my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
 my $custnum = $cust_pay->custnum;
 
-foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) {
-  my $error = $cust_bill_pay->delete;
-  errorpage($error) if $error;
-}
+my $error = $cust_pay->delete_cust_bill_pay;
+errorpage($error) if $error;
 
 </%init>
index e3599bc..1525e93 100644 (file)
@@ -228,6 +228,7 @@ my %opt = (
       (
         'View invoices', 'Void invoices', 'Unvoid invoices',
         'Apply payment', 'Refund credit card payment', 'Refund Echeck payment',
+        'Post refund', 'Post check refund', 'Post cash refund ', 'Refund payment',
         'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments',
         'Delete payment', 'Unapply payment',
         'Apply credit', 'Delete credit', 'Unapply credit', 'Void credit', 'Unvoid credit',
index bf88a66..0ed2f80 100644 (file)
@@ -154,18 +154,19 @@ if ( $apply && $opt{'pkg-balances'} && $cust_pay->pkgnum ) {
 
 my $refund = '';
 my $refund_days = $opt{'card_refund-days'} || 120;
-my $refund_right = '';
-$refund_right = 'Refund credit card payment' if $cust_pay->payby eq 'CARD';
-$refund_right = 'Refund Echeck payment'      if $cust_pay->payby eq 'CHEK';
+my @refund_right = grep { $opt{$_} } $FS::CurrentUser::CurrentUser->refund_rights($cust_pay->payby);
 if (    $cust_pay->closed !~ /^Y/i
-     && $cust_pay->payby =~ /^(CARD|CHEK)$/
+     && $cust_pay->payby =~ /^(CARD|CHEK|BILL)$/
      && time-$cust_pay->_date < $refund_days*86400
      && $cust_pay->unrefunded > 0
-     && $opt{$refund_right}
+     && scalar(@refund_right)
 ) {
+  my $refundtitle = ($cust_pay->payby =~ /^(CARD|CHEK)$/)
+            ? emt('Send a refund for this payment to the payment gateway')
+            : emt('Record a refund for this payment');
   $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
             qq!paynum=!. $cust_pay->paynum. '"'.
-            qq! TITLE="! .emt('Send a refund for this payment to the payment gateway') 
+            qq! TITLE="! . $refundtitle
             . '">' . emt('refund') . '</A>)';
 }