X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling_Realtime.pm;h=e2a02674e9331457ee93cd22d8014fb34dfbacc7;hb=05dee44cdb4e93df6963ae396f916705c4086f86;hp=8285cbfdf1f35b693cdfcc7f1054b4377f0ae3aa;hpb=5583e0b1788d9e307f676f21827e9920f5bf0677;p=freeside.git diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index 8285cbfdf..e2a02674e 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -3,14 +3,15 @@ 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; +use FS::cust_bill_pay; use FS::cust_refund; use FS::banned_pay; @@ -44,6 +45,31 @@ 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 = $self->cust_payby('CARD','CHEK'); + + 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 @@ -128,7 +154,9 @@ 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. +I can be set to true to run B on success. + +I can be set to true to prevent resulting payment from being automatically applied. I can be set true to surpress email decline notices. @@ -159,6 +187,15 @@ A third-party transaction will return a hashref containing: =cut # some helper routines +# +# _bop_recurring_billing: Checks whether this payment should have the +# recurring_billing flag used by some B:OP interfaces (IPPay, PlugnPay, +# vSecure, etc.). This works in two different modes: +# - actual_oncard (default): treat the payment as recurring if the customer +# has made a payment using this card before. +# - transaction_is_recur: treat the payment as recurring if the invoice +# being paid has any recurring package charges. + sub _bop_recurring_billing { my( $self, %opt ) = @_; @@ -236,7 +273,10 @@ sub _bop_defaults { } } - $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} ); + unless ( exists( $options->{'payinfo'} ) ) { + $options->{'payinfo'} = $self->payinfo; + $options->{'paymask'} = $self->paymask; + } # Default invoice number if the customer has exactly one open invoice. if( ! $options->{'invnum'} ) { @@ -319,6 +359,10 @@ my %bop_method2payby = ( 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 = (); @@ -339,8 +383,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; @@ -360,6 +405,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; } @@ -542,7 +589,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 @@ -583,6 +632,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'}, @@ -593,9 +643,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 ); @@ -701,8 +758,7 @@ sub realtime_bop { # remove paycvv after initial transaction ### - #false laziness w/misc/process/payment.cgi - check both to make sure working - # correctly + # compare to FS::cust_main::save_cust_payby - check both to make sure working correctly if ( length($self->paycvv) && ! grep { $_ eq cardtype($options{payinfo}) } $conf->config('cvv-save') ) { @@ -719,8 +775,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; @@ -843,6 +897,7 @@ sub _realtime_bop_result { '_date' => '', 'payby' => $cust_pay_pending->payby, 'payinfo' => $options{'payinfo'}, + 'paymask' => $options{'paymask'} || $cust_pay_pending->paymask, 'paydate' => $cust_pay_pending->paydate, 'pkgnum' => $cust_pay_pending->pkgnum, 'discount_term' => $options{'discount_term'}, @@ -850,7 +905,7 @@ sub _realtime_bop_result { 'processor' => $payment_gateway->gateway_module, 'auth' => $transaction->authorization, 'order_number' => $order_number || '', - + 'no_auto_apply' => $options{'no_auto_apply'} ? 'Y' : '', } ); #doesn't hurt to know, even though the dup check is in cust_pay_pending now $cust_pay->payunique( $options{payunique} ) @@ -909,6 +964,8 @@ sub _realtime_bop_result { return $e; } + $cust_pay_pending->set('jobnum',''); + } if ( $options{'paynum_ref'} ) { @@ -1017,8 +1074,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"; @@ -1074,31 +1132,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; @@ -1291,14 +1325,14 @@ L for supported gateways. Available methods are: I, I and I -Available options are: I, I, I, I +Available options are: I, I, I, I Most gateways require a reference to an original payment transaction to refund, so you probably need to specify a I. I defaults to the original amount of the payment if not specified. -I specifies a reason for the refund. +I specified an existing refund reason for the refund I specifies the expiration date for a credit card overriding the value from the customer record or the payment record. Specified as yyyy-mm-dd @@ -1341,6 +1375,10 @@ sub realtime_refund_bop { warn " $_ => $options{$_}\n" foreach keys %options; } + return "No reason specified" unless $options{'reasonnum'} =~ /^\d+$/; + + my %content = (); + ### # look up the original payment and optionally a gateway for that payment ### @@ -1359,6 +1397,9 @@ sub realtime_refund_bop { or return "Unknown paynum $options{'paynum'}"; $amount ||= $cust_pay->paid; + my @cust_bill_pay = qsearch('cust_bill_pay', { paynum=>$cust_pay->paynum }); + $content{'invoice_number'} = $cust_bill_pay[0]->invnum if @cust_bill_pay; + if ( $cust_pay->get('processor') ) { ($gatewaynum, $processor, $auth, $order_number) = ( @@ -1431,7 +1472,8 @@ sub realtime_refund_bop { eval "use $namespace"; die $@ if $@; - my %content = ( + %content = ( + %content, 'type' => $options{method}, 'login' => $login, 'password' => $password, @@ -1493,7 +1535,12 @@ sub realtime_refund_bop { if $conf->exists('business-onlinepayment-test_transaction'); $void->submit(); if ( $void->is_success ) { - my $error = $cust_pay->void($options{'reason'}); + # specified as a refund reason, but now we want a payment void reason + # extract just the reason text, let cust_pay::void handle new_or_existing + my $reason = qsearchs('reason',{ 'reasonnum' => $options{'reasonnum'} }); + my $error; + $error = 'Reason could not be loaded' unless $reason; + $error = $cust_pay->void($reason->reason) unless $error; if ( $error ) { # gah, even with transactions. my $e = 'WARNING: Card/ACH voided but database not updated - '. @@ -1602,6 +1649,7 @@ sub realtime_refund_bop { $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; @@ -1613,11 +1661,12 @@ sub realtime_refund_bop { my $cust_refund = new FS::cust_refund ( { 'custnum' => $self->custnum, 'paynum' => $options{'paynum'}, + 'source_paynum' => $options{'paynum'}, 'refund' => $amount, '_date' => '', 'payby' => $bop_method2payby{$options{method}}, 'payinfo' => $payinfo, - 'reason' => $options{'reason'} || 'card or ACH refund', + 'reasonnum' => $options{'reasonnum'}, 'gatewaynum' => $gatewaynum, # may be null 'processor' => $processor, 'auth' => $refund->authorization, @@ -1626,6 +1675,7 @@ sub realtime_refund_bop { my $error = $cust_refund->insert; if ( $error ) { $cust_refund->paynum(''); #try again with no specific paynum + $cust_refund->source_paynum(''); my $error2 = $cust_refund->insert; if ( $error2 ) { # gah, even with transactions.