X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling_Realtime.pm;h=9112607d2806f22b9d2aae17eeabfde75f676246;hp=f9f90a7dccf9d2f570d00ec4007cdd86565ad756;hb=e334c4383752a12527396a498b629537e92f05bd;hpb=fb4ab1073f0d15d660c6cdc4e07afebf68ef3924 diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index f9f90a7dc..9112607d2 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -3,11 +3,11 @@ package FS::cust_main::Billing_Realtime; use strict; use vars qw( $conf $DEBUG $me ); use vars qw( $realtime_bop_decline_quiet ); #ugh +use Carp; use Data::Dumper; use Business::CreditCard 0.28; use FS::UID qw( dbh ); use FS::Record qw( qsearch qsearchs ); -use FS::Misc qw( send_email ); use FS::payby; use FS::cust_pay; use FS::cust_pay_pending; @@ -44,6 +44,36 @@ These methods are available on FS::cust_main objects. =over 4 +=item realtime_cust_payby + +=cut + +sub realtime_cust_payby { + my( $self, %options ) = @_; + + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; + + $options{amount} = $self->balance unless exists( $options{amount} ); + + my @cust_payby = qsearch({ + 'table' => 'cust_payby', + 'hashref' => { 'custnum' => $self->custnum, }, + 'extra_sql' => " AND payby IN ( 'CARD', 'CHEK' ) ", + 'order_by' => 'ORDER BY weight ASC', + }); + + my $error; + foreach my $cust_payby (@cust_payby) { + $error = $cust_payby->realtime_bop( %options, ); + last unless $error; + } + + #XXX what about the earlier errors? + + $error; + +} + =item realtime_collect [ OPTION => VALUE ... ] Attempt to collect the customer's current balance with a realtime credit @@ -111,7 +141,7 @@ L for supported gateways. Required arguments in the hashref are I, and I -Available methods are: I, I and I +Available methods are: I, I, I, and I Available optional arguments are: I, I, I, I, I, I, I @@ -170,15 +200,8 @@ sub _bop_recurring_billing { } else { - my %hash = ( 'custnum' => $self->custnum, - 'payby' => 'CARD', - ); - - return 1 - if qsearch('cust_pay', { %hash, 'payinfo' => $opt{'payinfo'} } ) - || qsearch('cust_pay', { %hash, 'paymask' => $self->mask_payinfo('CARD', - $opt{'payinfo'} ) - } ); + # return 1 if the payinfo has been used for another payment + return $self->payinfo_used($opt{'payinfo'}); # in payinfo_Mixin } @@ -307,7 +330,6 @@ sub _bop_content { ? $options->{country} : $self->country; - $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/ $content{phone} = $self->daytime || $self->night; my $currency = $conf->exists('business-onlinepayment-currency') @@ -321,11 +343,16 @@ my %bop_method2payby = ( 'CC' => 'CARD', 'ECHECK' => 'CHEK', 'LEC' => 'LECB', + 'PAYPAL' => 'PPAL', ); sub realtime_bop { my $self = shift; + confess "Can't call realtime_bop within another transaction ". + '($FS::UID::AutoCommit is false)' + unless $FS::UID::AutoCommit; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my %options = (); @@ -346,8 +373,9 @@ sub realtime_bop { my $cc_surcharge = 0; my $cc_surcharge_pct = 0; $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage') - if $conf->config('credit-card-surcharge-percentage'); - + if $conf->config('credit-card-surcharge-percentage') + && $options{method} eq 'CC'; + # always add cc surcharge if called from event if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) { $cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100; @@ -367,6 +395,8 @@ sub realtime_bop { if ( $DEBUG ) { warn "$me realtime_bop (new): $options{method} $options{amount}\n"; warn " cc_surcharge = $cc_surcharge\n"; + } + if ( $DEBUG > 2 ) { warn " $_ => $options{$_}\n" foreach keys %options; } @@ -510,8 +540,13 @@ sub realtime_bop { (exists($options{'paytype'}) && $options{'paytype'}) ? uc($options{'paytype'}) : uc($self->getfield('paytype')) || 'PERSONAL CHECKING'; - $content{account_name} = $self->getfield('first'). ' '. - $self->getfield('last'); + + if ( $content{account_type} =~ /BUSINESS/i && $self->company ) { + $content{account_name} = $self->company; + } else { + $content{account_name} = $self->getfield('first'). ' '. + $self->getfield('last'); + } $content{customer_org} = $self->company ? 'B' : 'I'; $content{state_id} = exists($options{'stateid'}) @@ -544,7 +579,9 @@ sub realtime_bop { ? $options{'balance'} : $self->balance; + warn "claiming mutex on customer ". $self->custnum. "\n" if $DEBUG > 1; $self->select_for_update; #mutex ... just until we get our pending record in + warn "obtained mutex on customer ". $self->custnum. "\n" if $DEBUG > 1; #the checks here are intended to catch concurrent payments #double-form-submission prevention is taken care of in cust_pay_pending::check @@ -585,6 +622,7 @@ sub realtime_bop { '_date' => '', 'payby' => $bop_method2payby{$options{method}}, 'payinfo' => $options{payinfo}, + 'paymask' => $options{paymask}, 'paydate' => $paydate, 'recurring_billing' => $content{recurring_billing}, 'pkgnum' => $options{'pkgnum'}, @@ -595,9 +633,16 @@ sub realtime_bop { }; $cust_pay_pending->payunique( $options{payunique} ) if defined($options{payunique}) && length($options{payunique}); + + warn "inserting cust_pay_pending record for customer ". $self->custnum. "\n" + if $DEBUG > 1; my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted return $cpp_new_err if $cpp_new_err; + warn "inserted cust_pay_pending record for customer ". $self->custnum. "\n" + if $DEBUG > 1; + warn Dumper($cust_pay_pending) if $DEBUG > 2; + my( $action1, $action2 ) = split( /\s*\,\s*/, $payment_gateway->gateway_action ); @@ -616,6 +661,7 @@ sub realtime_bop { %$bop_content, 'reference' => $cust_pay_pending->paypendingnum, #for now 'callback_url' => $payment_gateway->gateway_callback_url, + 'cancel_url' => $payment_gateway->gateway_cancel_url, 'email' => $email, %content, #after ); @@ -720,8 +766,6 @@ sub realtime_bop { if ( $transaction->can('card_token') && $transaction->card_token ) { - $self->card_token($transaction->card_token); - if ( $options{'payinfo'} eq $self->payinfo ) { $self->payinfo($transaction->card_token); my $error = $self->replace; @@ -761,19 +805,6 @@ sub fake_bop { return "Error: No error; test failure requested with fake_failure"; } - #my $paybatch = ''; - #if ( $payment_gateway->gatewaynum ) { # agent override - # $paybatch = $payment_gateway->gatewaynum. '-'; - #} - # - #$paybatch .= "$processor:". $transaction->authorization; - # - #$paybatch .= ':'. $transaction->order_number - # if $transaction->can('order_number') - # && length($transaction->order_number); - - my $paybatch = 'FakeProcessor:54:32'; - my $cust_pay = new FS::cust_pay ( { 'custnum' => $self->custnum, 'invnum' => $options{'invnum'}, @@ -782,9 +813,11 @@ sub fake_bop { 'payby' => $bop_method2payby{$options{method}}, #'payinfo' => $payinfo, 'payinfo' => '4111111111111111', - 'paybatch' => $paybatch, #'paydate' => $paydate, 'paydate' => '2012-05-01', + 'processor' => 'FakeProcessor', + 'auth' => '54', + 'order_number' => '32', } ); $cust_pay->payunique( $options{payunique} ) if length($options{payunique}); @@ -845,17 +878,8 @@ sub _realtime_bop_result { if ( $transaction->is_success() ) { - my $paybatch = ''; - if ( $payment_gateway->gatewaynum ) { # agent override - $paybatch = $payment_gateway->gatewaynum. '-'; - } - - $paybatch .= $payment_gateway->gateway_module. ":". - $transaction->authorization; - - $paybatch .= ':'. $transaction->order_number - if $transaction->can('order_number') - && length($transaction->order_number); + my $order_number = $transaction->order_number + if $transaction->can('order_number'); my $cust_pay = new FS::cust_pay ( { 'custnum' => $self->custnum, @@ -864,10 +888,15 @@ sub _realtime_bop_result { '_date' => '', 'payby' => $cust_pay_pending->payby, 'payinfo' => $options{'payinfo'}, - 'paybatch' => $paybatch, + 'paymask' => $options{'paymask'} || $cust_pay_pending->paymask, 'paydate' => $cust_pay_pending->paydate, 'pkgnum' => $cust_pay_pending->pkgnum, - 'discount_term' => $options{'discount_term'}, + 'discount_term' => $options{'discount_term'}, + 'gatewaynum' => ($payment_gateway->gatewaynum || ''), + 'processor' => $payment_gateway->gateway_module, + 'auth' => $transaction->authorization, + 'order_number' => $order_number || '', + } ); #doesn't hurt to know, even though the dup check is in cust_pay_pending now $cust_pay->payunique( $options{payunique} ) @@ -926,6 +955,8 @@ sub _realtime_bop_result { return $e; } + $cust_pay_pending->set('jobnum',''); + } if ( $options{'paynum_ref'} ) { @@ -1023,8 +1054,9 @@ sub _realtime_bop_result { } else { - my $perror = $payment_gateway->gateway_module. " error: ". - $transaction->error_message; + my $perror = $transaction->error_message; + #$payment_gateway->gateway_module. " error: ". + # removed for conciseness my $jobnum = $cust_pay_pending->jobnum; if ( $jobnum ) { @@ -1033,8 +1065,9 @@ sub _realtime_bop_result { if ( $placeholder ) { my $error = $placeholder->depended_delete; $error ||= $placeholder->delete; + $cust_pay_pending->set('jobnum',''); warn "error removing provisioning jobs after declined paypendingnum ". - $cust_pay_pending->paypendingnum. ": $error\n"; + $cust_pay_pending->paypendingnum. ": $error\n" if $error; } else { my $e = "error finding job $jobnum for declined paypendingnum ". $cust_pay_pending->paypendingnum. "\n"; @@ -1090,31 +1123,7 @@ sub _realtime_bop_result { $error = $msg_template->send( 'cust_main' => $self, 'object' => $cust_pay_pending ); } - else { #!$msgnum - - my @templ = $conf->config('declinetemplate'); - my $template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", @templ ], - ) or return "($perror) can't create template: $Text::Template::ERROR"; - $template->compile() - or return "($perror) can't compile template: $Text::Template::ERROR"; - - my $templ_hash = { - 'company_name' => - scalar( $conf->config('company_name', $self->agentnum ) ), - 'company_address' => - join("\n", $conf->config('company_address', $self->agentnum ) ), - 'error' => $transaction->error_message, - }; - - my $error = send_email( - 'from' => $conf->config('invoice_from', $self->agentnum ), - 'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ], - 'subject' => 'Your payment could not be processed', - 'body' => [ $template->fill_in(HASH => $templ_hash) ], - ); - } + $perror .= " (also received error sending decline notification: $error)" if $error; @@ -1122,7 +1131,11 @@ sub _realtime_bop_result { } $cust_pay_pending->status('done'); - $cust_pay_pending->statustext("declined: $perror"); + $cust_pay_pending->statustext($perror); + #'declined:': no, that's failure_status + if ( $transaction->can('failure_status') ) { + $cust_pay_pending->failure_status( $transaction->failure_status ); + } my $cpp_done_err = $cust_pay_pending->replace; if ( $cpp_done_err ) { my $e = "WARNING: $options{method} declined but pending payment not ". @@ -1240,7 +1253,6 @@ sub realtime_botpp_capture { 'amount' => $cust_pay_pending->paid, #'invoice_number' => $options{'invnum'}, 'customer_id' => $self->custnum, - 'referer' => 'http://cleanwhisker.420.am/', 'reference' => $cust_pay_pending->paypendingnum, 'email' => $email, 'phone' => $self->daytime || $self->night, @@ -1363,6 +1375,7 @@ sub realtime_refund_bop { my( $processor, $login, $password, @bop_options, $namespace ) ; my( $auth, $order_number ) = ( '', '', '' ); + my $gatewaynum = ''; if ( $options{'paynum'} ) { @@ -1371,11 +1384,22 @@ sub realtime_refund_bop { or return "Unknown paynum $options{'paynum'}"; $amount ||= $cust_pay->paid; - $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/ - or return "Can't parse paybatch for paynum $options{'paynum'}: ". - $cust_pay->paybatch; - my $gatewaynum = ''; - ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 ); + if ( $cust_pay->get('processor') ) { + ($gatewaynum, $processor, $auth, $order_number) = + ( + $cust_pay->gatewaynum, + $cust_pay->processor, + $cust_pay->auth, + $cust_pay->order_number, + ); + } else { + # this payment wasn't upgraded, which probably means this won't work, + # but try it anyway + $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/ + or return "Can't parse paybatch for paynum $options{'paynum'}: ". + $cust_pay->paybatch; + ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 ); + } if ( $gatewaynum ) { #gateway for the payment to be refunded @@ -1438,12 +1462,15 @@ sub realtime_refund_bop { 'password' => $password, 'order_number' => $order_number, 'amount' => $amount, - 'referer' => 'http://cleanwhisker.420.am/', #XXX fix referer :/ ); $content{authorization} = $auth if length($auth); #echeck/ACH transactions have an order # but no auth #(at least with authorize.net) + my $currency = $conf->exists('business-onlinepayment-currency') + && $conf->config('business-onlinepayment-currency'); + $content{currency} = $currency if $currency; + my $disable_void_after; if ($conf->exists('disable_void_after') && $conf->config('disable_void_after') =~ /^(\d+)$/) { @@ -1598,10 +1625,9 @@ sub realtime_refund_bop { return "$processor error: ". $refund->error_message unless $refund->is_success(); - my $paybatch = "$processor:". $refund->authorization; - $paybatch .= ':'. $refund->order_number - if $refund->can('order_number') && $refund->order_number; + $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; @@ -1617,8 +1643,11 @@ sub realtime_refund_bop { '_date' => '', 'payby' => $bop_method2payby{$options{method}}, 'payinfo' => $payinfo, - 'paybatch' => $paybatch, 'reason' => $options{'reason'} || 'card or ACH refund', + 'gatewaynum' => $gatewaynum, # may be null + 'processor' => $processor, + 'auth' => $refund->authorization, + 'order_number' => $order_number, } ); my $error = $cust_refund->insert; if ( $error ) {