X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling_Realtime.pm;h=1006b9520afa48a0be96720024d71e80ae317407;hb=7d1cf3b75da3ba9aab3667cdc0125e25bbc76438;hp=ba1e9c86efddcb738d81d2430a45fdd5fc152468;hpb=8aa8e80791b7b99f4ac2e8e242fe83e1421c98d2;p=freeside.git diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index ba1e9c86e..1006b9520 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -178,6 +178,21 @@ 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 }); + } + } + + if ( $options->{'fake_gatewaynum'} ) { + $options->{payment_gateway} = + qsearchs('payment_gateway', + { 'gatewaynum' => $options->{'fake_gatewaynum'}, } + ); + } + $options->{payment_gateway} = $self->agent->payment_gateway( %$options ) unless exists($options->{payment_gateway}); @@ -300,13 +315,40 @@ sub realtime_bop { $options{method} = $method; $options{amount} = $amount; } + + + ### + # optional credit card surcharge + ### + + 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'); + + # 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; + $options{'amount'} += $cc_surcharge; + $options{'amount'} = sprintf("%.2f", $options{'amount'}); # round (again)? + } + elsif($cc_surcharge_pct > 0) { # we're called not from event (i.e. from a + # payment screen), so consider the given + # amount as post-surcharge + $cc_surcharge = $options{'amount'} - ($options{'amount'} / ( 1 + $cc_surcharge_pct/100 )); + } + $cc_surcharge = sprintf("%.2f",$cc_surcharge) if $cc_surcharge > 0; + $options{'cc_surcharge'} = $cc_surcharge; + + if ( $DEBUG ) { warn "$me realtime_bop (new): $options{method} $options{amount}\n"; + warn " cc_surcharge = $cc_surcharge\n"; warn " $_ => $options{$_}\n" foreach keys %options; } - return $self->fake_bop(%options) if $options{'fake'}; + return $self->fake_bop(\%options) if $options{'fake'}; $self->_bop_defaults(\%options); @@ -419,9 +461,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'); @@ -467,6 +509,24 @@ sub realtime_bop { 'custnum' => $self->custnum, 'status' => { op=>'!=', value=>'done' } }); + # This is a problem. A self-service third party payment that fails somehow + # can't be retried, EVER, until someone manually clears it. Totally + # arbitrary fix: if the existing payment is more than two minutes old, + # kill it. This doesn't limit how long it can take the pending payment + # to complete, only how long it will obstruct new payments. + my @still_pending; + foreach (@pending) { + if ( time - $_->_date > 120 ) { + my $error = $_->delete; + warn "error deleting stale pending payment ".$_->paypendingnum.": $error" + if $error; # not fatal, it will fail anyway + } + else { + push @still_pending, $_; + } + } + @pending = @still_pending; + return "A payment is already being processed for this customer (". join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ). "); $options{method} transaction aborted." @@ -511,6 +571,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 ); @@ -682,6 +743,11 @@ sub fake_bop { } ); $cust_pay->payunique( $options{payunique} ) if length($options{payunique}); + if ( $DEBUG ) { + warn "fake_bop\n cust_pay: ". Dumper($cust_pay) . "\n options: "; + warn " $_ => $options{$_}\n" foreach keys %options; + } + my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () ); if ( $error ) { @@ -845,6 +911,64 @@ sub _realtime_bop_result { } } + # have a CC surcharge portion --> one-time charge + if ( $options{'cc_surcharge'} > 0 ) { + # XXX: this whole block needs to be in a transaction? + + my $invnum; + $invnum = $options{'invnum'} if $options{'invnum'}; + unless ( $invnum ) { # probably from a payment screen + # do we have any open invoices? pick earliest + # uses the fact that cust_main->cust_bill sorts by date ascending + my @open = $self->open_cust_bill; + $invnum = $open[0]->invnum if scalar(@open); + } + + unless ( $invnum ) { # still nothing? pick last closed invoice + # again uses fact that cust_main->cust_bill sorts by date ascending + my @closed = $self->cust_bill; + $invnum = $closed[$#closed]->invnum if scalar(@closed); + } + + unless ( $invnum ) { + # XXX: unlikely case - pre-paying before any invoices generated + # what it should do is create a new invoice and pick it + warn 'CC SURCHARGE AND NO INVOICES PICKED TO APPLY IT!'; + return ''; + } + + my $cust_pkg; + my $charge_error = $self->charge({ + 'amount' => $options{'cc_surcharge'}, + 'pkg' => 'Credit Card Surcharge', + 'setuptax' => 'Y', + 'cust_pkg_ref' => \$cust_pkg, + }); + if($charge_error) { + warn 'Unable to add CC surcharge cust_pkg'; + return ''; + } + + $cust_pkg->setup(time); + my $cp_error = $cust_pkg->replace; + if($cp_error) { + warn 'Unable to set setup time on cust_pkg for cc surcharge'; + # but keep going... + } + + my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum }); + unless ( $cust_bill ) { + warn "race condition + invoice deletion just happened"; + return ''; + } + + my $grand_error = + $cust_bill->add_cc_surcharge($cust_pkg->pkgnum,$options{'cc_surcharge'}); + + warn "cannot add CC surcharge to invoice #$invnum: $grand_error" + if $grand_error; + } + return ''; #no error } @@ -862,7 +986,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"; @@ -1016,9 +1140,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 +1205,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, }