X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=55a66b77ef0865b4005577cf1ed3a7f7c734b170;hb=9a391bca139023b92e876cef69ebc02cc96323d5;hp=708b99746d9ed7667acc7530325566bd635740c3;hpb=9efddfba948a749413d11970e6106651b9a41d2d;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 708b99746..55a66b77e 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3,10 +3,12 @@ package FS::cust_bill; use strict; use vars qw( @ISA $conf $money_char ); use vars qw( $lpr $invoice_from $smtpmachine ); +use vars qw( $cybercash ); use vars qw( $xaction $E_NoErr ); use vars qw( $bop_processor $bop_login $bop_password $bop_action @bop_options ); use vars qw( $ach_processor $ach_login $ach_password $ach_action @ach_options ); use vars qw( $invoice_lines @buf ); #yuck +use vars qw( $realtime_bop_decline_quiet ); use Date::Format; use Mail::Internet 1.44; use Mail::Header; @@ -24,6 +26,8 @@ use FS::cust_bill_event; @ISA = qw( FS::Record ); +$realtime_bop_decline_quiet = 0; + #ask FS::UID to run this stuff for us later $FS::UID::callback{'FS::cust_bill'} = sub { @@ -35,7 +39,33 @@ $FS::UID::callback{'FS::cust_bill'} = sub { $invoice_from = $conf->config('invoice_from'); $smtpmachine = $conf->config('smtpmachine'); - if ( $conf->exists('business-onlinepayment') ) { + ( $bop_processor,$bop_login, $bop_password, $bop_action ) = ( '', '', '', ''); + @bop_options = (); + ( $ach_processor,$ach_login, $ach_password, $ach_action ) = ( '', '', '', ''); + @ach_options = (); + + if ( $conf->exists('cybercash3.2') ) { + require CCMckLib3_2; + #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2); + require CCMckDirectLib3_2; + #qw(SendCC2_1Server); + require CCMckErrno3_2; + #qw(MCKGetErrorMessage $E_NoErr); + import CCMckErrno3_2 qw($E_NoErr); + + my $merchant_conf; + ($merchant_conf,$xaction)= $conf->config('cybercash3.2'); + my $status = &CCMckLib3_2::InitConfig($merchant_conf); + if ( $status != $E_NoErr ) { + warn "CCMckLib3_2::InitConfig error:\n"; + foreach my $key (keys %CCMckLib3_2::Config) { + warn " $key => $CCMckLib3_2::Config{$key}\n" + } + my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status); + die "CCMckLib3_2::InitConfig fatal error: $errmsg\n"; + } + $cybercash='cybercash3.2'; + } elsif ( $conf->exists('business-onlinepayment') ) { ( $bop_processor, $bop_login, $bop_password, @@ -364,7 +394,7 @@ sub send { my @print_text = $self->print_text('', $template); my @invoicing_list = $self->cust_main->invoicing_list; - if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list ) { #email + if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list ) { #email #better to notify this person than silence @invoicing_list = ($invoice_from) unless @invoicing_list; @@ -526,13 +556,10 @@ sub send_csv { time2str("%x", $cust_bill_pkg->edate), ); - } else { #pkgnum tax + } else { #pkgnum Tax next unless $cust_bill_pkg->setup != 0; - my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') - ? ( $cust_bill_pkg->itemdesc || 'Tax' ) - : 'Tax'; ($pkg, $setup, $recur, $sdate, $edate) = - ( $itemdesc, sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' ); + ( 'Tax', sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' ); } $csv->combine( @@ -627,7 +654,7 @@ for supported processors. sub realtime_ach { my $self = shift; $self->realtime_bop( - 'CHECK', + 'ECHECK', $ach_processor, $ach_login, $ach_password, @@ -637,8 +664,34 @@ sub realtime_ach { ); } +=item realtime_lec + +Attempts to pay this invoice with phone bill (LEC) payment via a +Business::OnlinePayment realtime gateway. See +http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment +for supported processors. + +=cut + +sub realtime_lec { + my $self = shift; + $self->realtime_bop( + 'LEC', + $bop_processor, + $bop_login, + $bop_password, + $bop_action, + \@bop_options, + @_ + ); +} + sub realtime_bop { my( $self, $method, $processor, $login, $password, $action, $options ) = @_; + + #trim an extraneous blank line + pop @$options if scalar(@$options) % 2 && $options->[-1] =~ /^\s*$/; + my $cust_main = $self->cust_main; my $amount = $self->owed; @@ -646,7 +699,7 @@ sub realtime_bop { $address .= ", ". $cust_main->address2 if $cust_main->address2; my($payname, $payfirst, $paylast); - if ( $cust_main->payname && $method ne 'CHECK' ) { + if ( $cust_main->payname && $method ne 'ECHECK' ) { $payname = $cust_main->payname; $payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/ or do { @@ -687,20 +740,36 @@ sub realtime_bop { my %content; if ( $method eq 'CC' ) { + $content{card_number} = $cust_main->payinfo; $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; $content{expiration} = "$2/$1"; - } elsif ( $method eq 'CHECK' ) { + + $content{cvv2} = $cust_main->paycvv + if defined $cust_main->dbdef_table->column('paycvv') + && length($cust_main->paycvv); + + $content{recurring_billing} = 'YES' + if qsearch('cust_pay', { 'custnum' => $cust_main->custnum, + 'payby' => 'CARD', + 'payinfo' => $cust_main->payinfo, } ); + + } elsif ( $method eq 'ECHECK' ) { my($account_number,$routing_code) = $cust_main->payinfo; ( $content{account_number}, $content{routing_code} ) = split('@', $cust_main->payinfo); $content{bank_name} = $cust_main->payname; + $content{account_type} = 'CHECKING'; + $content{account_name} = $payname; + $content{customer_org} = $self->company ? 'B' : 'I'; + $content{customer_ssn} = $self->ss; + } elsif ( $method eq 'LEC' ) { + $content{phone} = $cust_main->payinfo; } my $transaction = new Business::OnlinePayment( $processor, @$options ); $transaction->content( - %content, 'type' => $method, 'login' => $login, 'password' => $password, @@ -720,6 +789,7 @@ sub realtime_bop { 'referer' => 'http://cleanwhisker.420.am/', 'email' => $email, 'phone' => $cust_main->daytime || $cust_main->night, + %content, #after ); $transaction->submit(); @@ -766,18 +836,37 @@ sub realtime_bop { } + #remove paycvv after initial transaction + #make this disable-able via a config option if anyone insists? + # (though that probably violates cardholder agreements) + use Business::CreditCard; + if ( defined $cust_main->dbdef_table->column('paycvv') + && length($cust_main->paycvv) + && ! grep { $_ eq cardtype($cust_main->payinfo) } $conf->config('cvv-save') + + ) { + my $new = new FS::cust_main { $cust_main->hash }; + $new->paycvv(''); + my $error = $new->replace($cust_main); + if ( $error ) { + warn "error removing cvv: $error\n"; + } + } + + #result handling if ( $transaction->is_success() ) { my %method2payby = ( - 'CC' => 'CARD', - 'CHECK' => 'CHEK', + 'CC' => 'CARD', + 'ECHECK' => 'CHEK', + 'LEC' => 'LECB', ); my $cust_pay = new FS::cust_pay ( { 'invnum' => $self->invnum, 'paid' => $amount, '_date' => '', - 'payby' => method2payby{$method}, + 'payby' => $method2payby{$method}, 'payinfo' => $cust_main->payinfo, 'paybatch' => "$processor:". $transaction->authorization, } ); @@ -798,8 +887,10 @@ sub realtime_bop { my $perror = "$processor error, invnum #". $self->invnum. ': '. $transaction->result_code. ": ". $transaction->error_message; - if ( $conf->exists('emaildecline') + if ( !$realtime_bop_decline_quiet && $conf->exists('emaildecline') && grep { $_ ne 'POST' } $cust_main->invoicing_list + && ! grep { $_ eq $transaction->error_message } + $conf->config('emaildecline-exclude') ) { my @templ = $conf->config('declinetemplate'); my $template = new Text::Template ( @@ -839,6 +930,84 @@ sub realtime_bop { } +=item realtime_card_cybercash + +Attempts to pay this invoice with the CyberCash CashRegister realtime gateway. + +=cut + +sub realtime_card_cybercash { + my $self = shift; + my $cust_main = $self->cust_main; + my $amount = $self->owed; + + return "CyberCash CashRegister real-time card processing not enabled!" + unless $cybercash eq 'cybercash3.2'; + + my $address = $cust_main->address1; + $address .= ", ". $cust_main->address2 if $cust_main->address2; + + #fix exp. date + #$cust_main->paydate =~ /^(\d+)\/\d*(\d{2})$/; + $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my $exp = "$2/$1"; + + # + + my $paybatch = $self->invnum. + '-' . time2str("%y%m%d%H%M%S", time); + + my $payname = $cust_main->payname || + $cust_main->getfield('first').' '.$cust_main->getfield('last'); + + my $country = $cust_main->country eq 'US' ? 'USA' : $cust_main->country; + + my @full_xaction = ( $xaction, + 'Order-ID' => $paybatch, + 'Amount' => "usd $amount", + 'Card-Number' => $cust_main->getfield('payinfo'), + 'Card-Name' => $payname, + 'Card-Address' => $address, + 'Card-City' => $cust_main->getfield('city'), + 'Card-State' => $cust_main->getfield('state'), + 'Card-Zip' => $cust_main->getfield('zip'), + 'Card-Country' => $country, + 'Card-Exp' => $exp, + ); + + my %result; + %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction); + + if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3 + my $cust_pay = new FS::cust_pay ( { + 'invnum' => $self->invnum, + 'paid' => $amount, + '_date' => '', + 'payby' => 'CARD', + 'payinfo' => $cust_main->payinfo, + 'paybatch' => "$cybercash:$paybatch", + } ); + my $error = $cust_pay->insert; + if ( $error ) { + # gah, even with transactions. + my $e = 'WARNING: Card debited but database not updated - '. + 'error applying payment, invnum #' . $self->invnum. + " (CyberCash Order-ID $paybatch): $error"; + warn $e; + return $e; + } else { + return ''; + } +# } elsif ( $result{'Mstatus'} ne 'failure-bad-money' +# || $options{'report_badcard'} +# ) { + } else { + return 'Cybercash error, invnum #' . + $self->invnum. ':'. $result{'MErrMsg'}; + } + +} + =item batch_card Adds a payment for this invoice to the pending credit card batch (see @@ -861,7 +1030,6 @@ sub batch_card { 'state' => $cust_main->getfield('state'), 'zip' => $cust_main->getfield('zip'), 'country' => $cust_main->getfield('country'), - 'trancode' => 77, 'cardnum' => $cust_main->getfield('payinfo'), 'exp' => $cust_main->getfield('paydate'), 'payname' => $cust_main->getfield('payname'), @@ -918,38 +1086,48 @@ sub print_text { } #new charges - foreach ( ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first - ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes + foreach my $cust_bill_pkg ( + ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first + ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes ) { - if ( $_->pkgnum ) { + if ( $cust_bill_pkg->pkgnum ) { - my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } ); - my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart}); - my($pkg)=$part_pkg->pkg; + my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); + my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); + my $pkg = $part_pkg->pkg; - if ( $_->setup != 0 ) { - push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ]; + if ( $cust_bill_pkg->setup != 0 ) { + push @buf, [ "$pkg Setup", + $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; } - if ( $_->recur != 0 ) { + if ( $cust_bill_pkg->recur != 0 ) { push @buf, [ - "$pkg (" . time2str("%x",$_->sdate) . " - " . - time2str("%x",$_->edate) . ")", - $money_char. sprintf("%10.2f",$_->recur) + "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " . + time2str("%x", $cust_bill_pkg->edate) . ")", + $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; } - } else { #pkgnum tax - my $itemdesc = defined $_->dbdef_table->column('itemdesc') - ? ( $_->itemdesc || 'Tax' ) + } else { #pkgnum tax or one-shot line item + my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') + ? ( $cust_bill_pkg->itemdesc || 'Tax' ) : 'Tax'; - push @buf,[$itemdesc, $money_char. sprintf("%10.2f",$_->setup) ] - if $_->setup != 0; + if ( $cust_bill_pkg->setup != 0 ) { + push @buf, [ $itemdesc, + $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; + } + if ( $cust_bill_pkg->recur != 0 ) { + push @buf, [ "$itemdesc (". time2str("%x", $cust_bill_pkg->sdate). " - " + . time2str("%x", $cust_bill_pkg->edate). ")", + $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) + ]; + } } } @@ -1007,9 +1185,9 @@ sub print_text { or die "cannot load config file $templatefile"; $invoice_lines = 0; my $wasfunc = 0; - foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy - /invoice_lines\((\d+)\)/; - $invoice_lines += $1; + foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy + /invoice_lines\((\d*)\)/; + $invoice_lines += $1 || scalar(@buf); $wasfunc=1; } die "no invoice_lines() functions in template?" unless $wasfunc; @@ -1022,11 +1200,12 @@ sub print_text { #setup template variables package FS::cust_bill::_template; #! - use vars qw( $invnum $date $page $total_pages @address $overdue @buf ); + use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent ); $invnum = $self->invnum; $date = $self->_date; $page = 1; + $agent = $self->cust_main->agent->agent; if ( $FS::cust_bill::invoice_lines ) { $total_pages = @@ -1067,16 +1246,14 @@ sub print_text { # ); #and subroutine for the template - sub FS::cust_bill::_template::invoice_lines { - my $lines = shift or return @buf; + my $lines = shift || scalar(@buf); map { scalar(@buf) ? shift @buf : [ '', '' ]; } ( 1 .. $lines ); } - #and fill it in $FS::cust_bill::_template::page = 1; my $lines; @@ -1094,10 +1271,6 @@ sub print_text { =back -=head1 VERSION - -$Id: cust_bill.pm,v 1.51 2002-11-16 10:33:16 ivan Exp $ - =head1 BUGS The delete method.