From f3738a9fcd588d32eea9b0af6a3e884293c0c6e5 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Fri, 28 Aug 2015 00:56:49 -0500 Subject: [PATCH] RT#37064: Add action link to manually refund a payment --- FS/FS/access_user.pm | 37 +++++++++ FS/FS/cust_main/Billing_Realtime.pm | 1 + FS/FS/cust_pay.pm | 96 ++++++++++++++++++++++ httemplate/edit/cust_refund.cgi | 10 +-- httemplate/edit/process/cust_refund.cgi | 31 ++++--- httemplate/misc/unapply-cust_pay.cgi | 6 +- httemplate/view/cust_main/payment_history.html | 1 + .../view/cust_main/payment_history/payment.html | 13 +-- 8 files changed, 160 insertions(+), 35 deletions(-) diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 605eaeef1..ffbe7ac75 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -633,6 +633,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 for any L +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 diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index 5980bf5de..790135c19 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -1622,6 +1622,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; diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index 61d29750e..424fab011 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -833,6 +833,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.) + +=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 diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 486534375..3c189afe6 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -136,16 +136,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+)$/ ) { diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 81f0da4df..6d6ff389e 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -12,7 +12,7 @@ % } 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); my $error = ''; if ( $payby =~ /^(CARD|CHEK)$/ ) { @@ -58,12 +50,19 @@ if ( $payby =~ /^(CARD|CHEK)$/ ) { '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; + } } diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi index 8cdac180b..b0343d034 100755 --- a/httemplate/misc/unapply-cust_pay.cgi +++ b/httemplate/misc/unapply-cust_pay.cgi @@ -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; diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 69571c78e..1cd838604 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -432,6 +432,7 @@ my %opt = ( ( 'View invoices', 'Void invoices', 'Unvoid invoices', 'Delete 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', diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html index bf88a6607..0ed2f8003 100644 --- a/httemplate/view/cust_main/payment_history/payment.html +++ b/httemplate/view/cust_main/payment_history/payment.html @@ -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! (' . emt('refund') . ')'; } -- 2.11.0