X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling_Realtime.pm;h=97e7c94eb11f57852298d2536183881063195d24;hb=a816e075c54631250faed653d4ec7b69f727e93a;hp=46d531af82cf28f31957c87d759aaea65a4f9ecb;hpb=5c9d03cf63378dbca8fc062ed25e781f9c7bb61b;p=freeside.git diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index 46d531af8..97e7c94eb 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -4,7 +4,6 @@ use strict; use vars qw( $conf $DEBUG $me ); use vars qw( $realtime_bop_decline_quiet ); #ugh use Data::Dumper; -use Digest::MD5 qw(md5_base64); use Business::CreditCard 0.28; use FS::UID qw( dbh ); use FS::Record qw( qsearch qsearchs ); @@ -13,6 +12,7 @@ use FS::payby; use FS::cust_pay; use FS::cust_pay_pending; use FS::cust_refund; +use FS::banned_pay; $realtime_bop_decline_quiet = 0; @@ -43,14 +43,11 @@ These methods are available on FS::cust_main objects. =item realtime_collect [ OPTION => VALUE ... ] -Runs a realtime credit card, ACH (electronic check) or phone bill transaction -via a Business::OnlinePayment or Business::OnlineThirdPartyPayment realtime -gateway. See L and -L for supported gateways. +Attempt to collect the customer's current balance with a realtime credit +card, electronic check, or phone bill transaction (see realtime_bop() below). -On failure returns an error message. - -Returns false or a hashref upon success. The hashref contains keys popup_url reference, and collectitems. The first is a URL to which a browser should be redirected for completion of collection. The second is a reference id for the transaction suitable for the end user. The collectitems is a reference to a list of name value pairs suitable for assigning to a html form and posted to popup_url. +Returns the result of realtime_bop(): nothing, an error message, or a +hashref of state information for a third-party transaction. Available options are: I, I, I, I, I, I, I, I, I @@ -68,12 +65,11 @@ the value defined by the business-onlinepayment-description configuration option, or "Internet services" if that is unset. If an I is specified, this payment (if successful) is applied to the -specified invoice. If you don't specify an I you might want to -call the B method or set the I option. +specified invoice. -I can be set to true to apply a resulting payment. +I will automatically apply a resulting payment. -I can be set true to surpress email decline notices. +I can be set true to suppress email decline notices. I can be set to a scalar reference. It will be filled in with the resulting paynum, if any. @@ -89,7 +85,7 @@ I allows payment capture to unlock export jobs sub realtime_collect { my( $self, %options ) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; if ( $DEBUG ) { warn "$me realtime_collect:\n"; @@ -125,8 +121,9 @@ the value defined by the business-onlinepayment-description configuration option, or "Internet services" if that is unset. If an I is specified, this payment (if successful) is applied to the -specified invoice. If you don't specify an I you might want to -call the B method or set the I option. +specified invoice. If the customer has exactly one open invoice, that +invoice number will be assumed. If you don't specify an I you might +want to call the B method or set the I option. I can be set to true to apply a resulting payment. @@ -143,6 +140,16 @@ I allows payment capture to unlock export jobs I attempts to take a discount by prepaying for discount_term +A direct (Business::OnlinePayment) transaction will return nothing on success, +or an error message on failure. + +A third-party transaction will return a hashref containing: + +- popup_url: the URL to which a browser should be redirected to complete + the transaction. +- collectitems: an arrayref of name-value pairs to be posted to popup_url. +- reference: a reference ID for the transaction, to show the customer. + (moved from cust_bill) (probably should get realtime_{card,ach,lec} here too) =cut @@ -178,6 +185,14 @@ sub _bop_recurring_billing { sub _payment_gateway { my ($self, $options) = @_; + if ( $options->{'selfservice'} ) { + my $gatewaynum = FS::Conf->new->config('selfservice-payment_gateway'); + if ( $gatewaynum ) { + return $options->{payment_gateway} ||= + qsearchs('payment_gateway', { gatewaynum => $gatewaynum }); + } + } + $options->{payment_gateway} = $self->agent->payment_gateway( %$options ) unless exists($options->{payment_gateway}); @@ -218,7 +233,14 @@ sub _bop_defaults { } $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} ); - $options->{invnum} ||= ''; + + # Default invoice number if the customer has exactly one open invoice. + if( ! $options->{'invnum'} ) { + $options->{'invnum'} = ''; + my @open = $self->open_cust_bill; + $options->{'invnum'} = $open[0]->invnum if scalar(@open) == 1; + } + $options->{payname} = $self->payname unless exists( $options->{payname} ); } @@ -289,7 +311,7 @@ my %bop_method2payby = ( sub realtime_bop { my $self = shift; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my %options = (); if (ref($_[0]) eq 'HASH') { @@ -345,11 +367,11 @@ sub realtime_bop { # check for banned credit card/ACH ### - my $ban = qsearchs('banned_pay', { + my $ban = FS::banned_pay->ban_search( 'payby' => $bop_method2payby{$options{method}}, - 'payinfo' => md5_base64($options{payinfo}), - } ); - return "Banned credit card" if $ban; + 'payinfo' => $options{payinfo}, + ); + return "Banned credit card" if $ban && $ban->bantype ne 'warn'; ### # massage data @@ -419,9 +441,9 @@ sub realtime_bop { $content{bank_state} = exists($options{'paystate'}) ? $options{'paystate'} : $self->getfield('paystate'); - $content{account_type} = exists($options{'paytype'}) - ? uc($options{'paytype'}) || 'CHECKING' - : uc($self->getfield('paytype')) || 'CHECKING'; + $content{account_type}= (exists($options{'paytype'}) && $options{'paytype'}) + ? uc($options{'paytype'}) + : uc($self->getfield('paytype')) || 'PERSONAL CHECKING'; $content{account_name} = $self->getfield('first'). ' '. $self->getfield('last'); @@ -459,7 +481,6 @@ sub realtime_bop { #check the balance return "The customer's balance has changed; $options{method} transaction aborted." if $self->balance < $balance; - #&& $self->balance < $options{amount}; #might as well anyway? #also check and make sure there aren't *other* pending payments for this cust @@ -467,6 +488,19 @@ sub realtime_bop { 'custnum' => $self->custnum, 'status' => { op=>'!=', value=>'done' } }); + + #for third-party payments only, remove pending payments if they're in the + #'thirdparty' (waiting for customer action) state. + if ( $namespace eq 'Business::OnlineThirdPartyPayment' ) { + foreach ( grep { $_->status eq 'thirdparty' } @pending ) { + my $error = $_->delete; + warn "error deleting unfinished third-party payment ". + $_->paypendingnum . ": $error\n" + if $error; + } + @pending = grep { $_->status ne 'thirdparty' } @pending; + } + return "A payment is already being processed for this customer (". join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ). "); $options{method} transaction aborted." @@ -476,7 +510,6 @@ sub realtime_bop { my $cust_pay_pending = new FS::cust_pay_pending { 'custnum' => $self->custnum, - #'invnum' => $options{'invnum'}, 'paid' => $options{amount}, '_date' => '', 'payby' => $bop_method2payby{$options{method}}, @@ -511,6 +544,7 @@ sub realtime_bop { 'customer_id' => $self->custnum, %$bop_content, 'reference' => $cust_pay_pending->paypendingnum, #for now + 'callback_url' => $payment_gateway->gateway_callback_url, 'email' => $email, %content, #after ); @@ -539,6 +573,9 @@ sub realtime_bop { if ( $transaction->is_success() && $namespace eq 'Business::OnlineThirdPartyPayment' ) { + $cust_pay_pending->status('thirdparty'); + my $cpp_err = $cust_pay_pending->replace; + return { error => $cpp_err } if $cpp_err; return { reference => $cust_pay_pending->paypendingnum, map { $_ => $transaction->$_ } qw ( popup_url collectitems ) }; @@ -717,7 +754,7 @@ sub fake_bop { sub _realtime_bop_result { my( $self, $cust_pay_pending, $transaction, %options ) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; if ( $DEBUG ) { warn "$me _realtime_bop_result: pending transaction ". @@ -771,13 +808,16 @@ sub _realtime_bop_result { my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () ); if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; $cust_pay->invnum(''); #try again with no specific invnum + $cust_pay->paynum(''); my $error2 = $cust_pay->insert( $options{'manual'} ? ( 'manual' => 1 ) : () ); if ( $error2 ) { # gah. but at least we have a record of the state we had to abort in # from cust_pay_pending now. + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; my $e = "WARNING: $options{method} captured but payment not recorded -". " error inserting payment (". $payment_gateway->gateway_module. "): $error2". @@ -862,7 +902,7 @@ sub _realtime_bop_result { my $error = $placeholder->depended_delete; $error ||= $placeholder->delete; warn "error removing provisioning jobs after declined paypendingnum ". - $cust_pay_pending->paypendingnum. "\n"; + $cust_pay_pending->paypendingnum. ": $error\n"; } else { my $e = "error finding job $jobnum for declined paypendingnum ". $cust_pay_pending->paypendingnum. "\n"; @@ -902,10 +942,10 @@ sub _realtime_bop_result { } if ( !$options{'quiet'} && !$realtime_bop_decline_quiet - && $conf->exists('emaildecline') + && $conf->exists('emaildecline', $self->agentnum) && grep { $_ ne 'POST' } $self->invoicing_list && ! grep { $transaction->error_message =~ /$_/ } - $conf->config('emaildecline-exclude') + $conf->config('emaildecline-exclude', $self->agentnum) ) { # Send a decline alert to the customer. @@ -1000,7 +1040,7 @@ upon success) and session_id of any associated session. sub realtime_botpp_capture { my( $self, $cust_pay_pending, %options ) = @_; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; if ( $DEBUG ) { warn "$me realtime_botpp_capture: pending transaction $cust_pay_pending\n"; @@ -1016,9 +1056,10 @@ sub realtime_botpp_capture { my $method = FS::payby->payby2bop($cust_pay_pending->payby); - my $payment_gateway = $cust_pay_pending->gatewaynum - ? qsearchs( 'payment_gateway', - { gatewaynum => $cust_pay_pending->gatewaynum } + my $payment_gateway; + my $gatewaynum = $cust_pay_pending->getfield('gatewaynum'); + $payment_gateway = $gatewaynum ? qsearchs( 'payment_gateway', + { gatewaynum => $gatewaynum } ) : $self->agent->payment_gateway( 'method' => $method, # 'invnum' => $cust_pay_pending->invnum, @@ -1080,7 +1121,14 @@ sub realtime_botpp_capture { my $error = $self->_realtime_bop_result( $cust_pay_pending, $transaction, %options ); - { + if ( $options{'apply'} ) { + my $apply_error = $self->apply_payments_and_credits; + if ( $apply_error ) { + warn "WARNING: error applying payment: $apply_error\n"; + } + } + + return { bill_error => $error, session_id => $cust_pay_pending->session_id, } @@ -1157,7 +1205,7 @@ gateway is attempted. sub realtime_refund_bop { my $self = shift; - local($DEBUG) = $cust_main::DEBUG if $cust_main::DEBUG > $DEBUG; + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; my %options = (); if (ref($_[0]) eq 'HASH') {