From 639e88d37ab33deeea4db7c8d1f15e8967f1826f Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 18 May 2017 11:58:26 -0400 Subject: [PATCH] RT# 74435 - Adding option to allow refunds using electronic check batch with RBC format. Conflicts: FS/FS/Schema.pm FS/FS/cust_main/Billing_Batch.pm httemplate/view/cust_main/menu.html --- FS/FS/Schema.pm | 1 + FS/FS/cust_main.pm | 3 +- FS/FS/pay_batch/RBC.pm | 7 +- httemplate/edit/cust_refund.cgi | 24 +++++ httemplate/edit/process/cust_refund.cgi | 179 +++++++++++++++++++++++++++++++- 5 files changed, 211 insertions(+), 3 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 1567b0030..699143239 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1909,6 +1909,7 @@ sub tables_hashref { 'amount', @money_type, '', '', 'status', 'varchar', 'NULL', $char_d, '', '', 'error_message', 'varchar', 'NULL', $char_d, '', '', + 'paycode', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'paybatchnum', 'unique' => [], diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 7d913b955..d4214d1c8 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2790,7 +2790,7 @@ sub batch_card { } ); foreach (qw( address1 address2 city state zip country latitude longitude - payby payinfo paydate payname )) + payby payinfo paydate payname paycode )) { $options{$_} = '' unless exists($options{$_}); } @@ -2817,6 +2817,7 @@ sub batch_card { 'exp' => $options{paydate} || $self->paydate, 'payname' => $options{payname} || $self->payname, 'amount' => $amount, # consolidating + 'paycode' => $options{paycode} || $cust_payby->paycode, } ); $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum) diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm index 142c50b79..05ee4e501 100644 --- a/FS/FS/pay_batch/RBC.pm +++ b/FS/FS/pay_batch/RBC.pm @@ -180,8 +180,13 @@ $name = 'RBC'; if (($cust_pay_batch->cust_main->paytype eq "Business checking" || $cust_pay_batch->cust_main->paytype eq "Business savings") && $cust_pay_batch->cust_main->company); $i++; + + ## set to D for debit by default, then override to what cust_pay_batch has as payments may not have paycode. + my $debitorcredit = 'D'; + $debitorcredit = $cust_pay_batch->paycode unless !$cust_pay_batch->paycode; + sprintf("%06u", $i). - 'D'. + $debitorcredit. sprintf("%3s",$trans_code). sprintf("%10s",$client_num). ' '. diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 32da4543e..e1975ed70 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -102,7 +102,28 @@ Check # +% } +% elsif ($payby eq 'CHEK') { +% +% my @cust_payby = (); +% if ( $payby eq 'CARD' ) { +% @cust_payby = $cust_main->cust_payby('CARD','DCRD'); +% } elsif ( $payby eq 'CHEK' ) { +% @cust_payby = $cust_main->cust_payby('CHEK','DCHK'); % } else { +% die "unknown payby $payby"; +% } +% +% my $custpaybynum = length(scalar($cgi->param('custpaybynum'))) +% ? scalar($cgi->param('custpaybynum')) +% : scalar(@cust_payby) && $cust_payby[0]->custpaybynum; +<& /elements/tr-select-cust_payby.html, + 'cust_payby' => \@cust_payby, + 'curr_value' => $custpaybynum, + 'onchange' => 'cust_payby_changed(this)', +&> + +% } else { % } @@ -157,6 +178,9 @@ if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { } die "no custnum or paynum specified!" unless $custnum; +my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); +die "unknown custnum $custnum" unless $cust_main; + my $_date = time; my $p1 = popurl(1); diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 764f2deb7..b1b5c80bd 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -21,6 +21,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Refund payment') || $FS::CurrentUser::CurrentUser->access_right('Post refund'); +my $conf = new FS::Conf; + $cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; my $custnum = $1; my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) @@ -42,6 +44,149 @@ if ( $error ) { } elsif ( $payby =~ /^(CARD|CHEK)$/ ) { my %options = (); my $bop = $FS::payby::payby2bop{$1}; + + my %payby2fields = ( + 'CARD' => [ qw( address1 address2 city county state zip country ) ], + 'CHEK' => [ qw( ss paytype paystate stateid stateid_state ) ], + ); + my %type = ( 'CARD' => 'credit card', + 'CHEK' => 'electronic check (ACH)', + ); + +my( $cust_payby, $payinfo, $paycvv, $month, $year, $payname ); +my $paymask = ''; +if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { + + ## + # use stored cust_payby info + ## + + $cust_payby = qsearchs('cust_payby', { custnum => $custnum, + custpaybynum => $custpaybynum, } ) + or die "unknown custpaybynum $custpaybynum"; + + # not needed for realtime_bop, but still needed for batch_card + $payinfo = $cust_payby->payinfo; + $paymask = $cust_payby->paymask; + $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it + ( $month, $year ) = $cust_payby->paydate_mon_year; + $payname = $cust_payby->payname; + +} else { + + ## + # use new info + ## + + $cgi->param('year') =~ /^(\d+)$/ + or errorpage("illegal year ". $cgi->param('year')); + $year = $1; + + $cgi->param('month') =~ /^(\d+)$/ + or errorpage("illegal month ". $cgi->param('month')); + $month = $1; + + $cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ + or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname')); + $payname = $1; + + if ( $payby eq 'CHEK' ) { + + $cgi->param('payinfo1') =~ /^(\d+)$/ + or errorpage("Illegal account number ". $cgi->param('payinfo1')); + my $payinfo1 = $1; + $cgi->param('payinfo2') =~ /^(\d+)$/ + or errorpage("Illegal ABA/routing number ". $cgi->param('payinfo2')); + my $payinfo2 = $1; + if ( $conf->config('echeck-country') eq 'CA' ) { + $cgi->param('payinfo3') =~ /^(\d{5})$/ + or errorpage("Illegal branch number ". $cgi->param('payinfo2')); + $payinfo2 = "$1.$payinfo2"; + } + $payinfo = $payinfo1 . '@'. $payinfo2; + + } elsif ( $payby eq 'CARD' ) { + + $payinfo = $cgi->param('payinfo'); + + $payinfo =~ s/\D//g; + $payinfo =~ /^(\d{13,19}|\d{8,9})$/ + or errorpage(gettext('invalid_card')); + $payinfo = $1; + validate($payinfo) + or errorpage(gettext('invalid_card')); + + unless ( $cust_main->tokenized($payinfo) ) { #token + + my $cardtype = cardtype($payinfo); + + errorpage(gettext('unknown_card_type')) + if $cardtype eq "Unknown"; + + my %bop_card_types = map { $_=>1 } values %{ card_types() }; + errorpage("$cardtype not accepted") unless $bop_card_types{$cardtype}; + + } + + if ( length($cgi->param('paycvv') ) ) { + if ( cardtype($payinfo) eq 'American Express card' ) { + $cgi->param('paycvv') =~ /^(\d{4})$/ + or errorpage("CVV2 (CID) for American Express cards is four digits."); + $paycvv = $1; + } else { + $cgi->param('paycvv') =~ /^(\d{3})$/ + or errorpage("CVV2 (CVC2/CID) is three digits."); + $paycvv = $1; + } + } elsif ( $conf->exists('backoffice-require_cvv') ){ + errorpage("CVV2 is required"); + } + + } else { + die "unknown payby $payby"; + } + + # save first, for proper tokenization + if ( $cgi->param('save') ) { + + my %saveopt; + if ( $payby eq 'CARD' ) { + my $bill_location = FS::cust_location->new; + $bill_location->set( $_ => scalar($cgi->param($_)) ) + foreach @{$payby2fields{$payby}}; + $saveopt{'bill_location'} = $bill_location; + $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this + $saveopt{'paydate'} = "$year-$month-01"; + } else { + # ss/stateid/stateid_state won't be saved, but should be harmless to pass + %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}}; + } + + my $error = $cust_main->save_cust_payby( + 'saved_cust_payby' => \$cust_payby, + 'payment_payby' => $payby, + 'auto' => scalar($cgi->param('auto')), + 'weight' => scalar($cgi->param('weight')), + 'payinfo' => $payinfo, + 'payname' => $payname, + %saveopt + ); + + errorpage("error saving info, payment not processed: $error") + if $error; + + } elsif ( $payby eq 'CARD' ) { # not saving + + $paymask = FS::payinfo_Mixin->mask_payinfo('CARD',$payinfo); # for untokenized but tokenizable payinfo + + } + +} + +## +# now run the refund +## + $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/ or die "illegal refund amount ". $cgi->param('refund'); my $refund = "$1$2"; @@ -49,10 +194,42 @@ if ( $error ) { my $paynum = $1; my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/; - $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, + + if ( $cgi->param('batch') ) { + + $error ||= $cust_main->batch_card( + 'payby' => $payby, + 'amount' => $refund, + 'payinfo' => $payinfo, + 'paydate' => "$year-$month-01", + 'payname' => $payname, + 'paycode' => 'C', + map { $_ => scalar($cgi->param($_)) } + @{$payby2fields{$payby}} + ); + errorpage($error) if $error; + +#### post refund ##### + my %hash = map { + $_, scalar($cgi->param($_)) + } fields('cust_refund'); + $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; + } + # if not a batch refund run realtime. + } else { + $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, 'paynum' => $paynum, 'reasonnum' => $reasonnum, %options ); + } } else { my %hash = map { $_, scalar($cgi->param($_)) -- 2.11.0