X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=4e22a904e1d42efb1ca6fb559aba9bfcf6370de4;hb=7ed55804735f4f687cd64139db7bae9746282a89;hp=ff40cfe83e3bda5924ebdd2592da580cd6d404e5;hpb=490dabbf104bcb716f26dd21dbff49c2738eedb8;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index ff40cfe83..4e22a904e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -28,6 +28,7 @@ use FS::cust_svc; use FS::cust_bill; use FS::cust_bill_pkg; use FS::cust_pay; +use FS::cust_pay_pending; use FS::cust_pay_void; use FS::cust_pay_batch; use FS::cust_credit; @@ -2860,7 +2861,7 @@ 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, I The additional options I, I, I, I, I, I, I and I are also available. Any of these options, @@ -2878,6 +2879,8 @@ I can be set true to surpress email decline notices. I can be set to a scalar reference. It will be filled in with the resulting paynum, if any. +I is a unique identifier for this payment. + (moved from cust_bill) (probably should get realtime_{card,ach,lec} here too) =cut @@ -3026,6 +3029,10 @@ sub realtime_bop { $content{invoice_number} = $options{'invnum'} if exists($options{'invnum'}) && length($options{'invnum'}); + $content{email_customer} = + ( $conf->exists('business-onlinepayment-email_customer') + || $conf->exists('business-onlinepayment-email-override') ); + my $paydate = ''; if ( $method eq 'CC' ) { @@ -3098,6 +3105,49 @@ sub realtime_bop { # run transaction(s) ### + my $balance = exists( $options{'balance'} ) + ? $options{'balance'} + : $self->balance; + + $self->select_for_update; #mutex ... just until we get our pending record in + + #the checks here are intended to catch concurrent payments + #double-form-submission prevention is taken care of in cust_pay_pending::check + + #check the balance + return "The customer's balance has changed; $method transaction aborted." + if $self->balance < $balance; + #&& $self->balance < $amount; #might as well anyway? + + #also check and make sure there aren't *other* pending payments for this cust + + my @pending = qsearch('cust_pay_pending', { + 'custnum' => $self->custnum, + 'status' => { op=>'!=', value=>'done' } + }); + return "A payment is already being processed for this customer (". + join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ). + "); $method transaction aborted." + if scalar(@pending); + + #okay, good to go, if we're a duplicate, cust_pay_pending will kick us out + + my $cust_pay_pending = new FS::cust_pay_pending { + 'custnum' => $self->custnum, + #'invnum' => $options{'invnum'}, + 'paid' => $amount, + '_date' => '', + 'payby' => $method2payby{$method}, + 'payinfo' => $payinfo, + 'paydate' => $paydate, + 'status' => 'new', + 'gatewaynum' => ( $payment_gateway ? $payment_gateway->gatewaynum : '' ), + }; + $cust_pay_pending->payunique( $options{payunique} ) + if length($options{payunique}); + my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted + return $cpp_new_err if $cpp_new_err; + my( $action1, $action2 ) = split(/\s*\,\s*/, $action ); my $transaction = new Business::OnlinePayment( $processor, @bop_options ); @@ -3131,9 +3181,19 @@ sub realtime_bop { 'phone' => $self->daytime || $self->night, %content, #after ); + + $cust_pay_pending->status('pending'); + my $cpp_pending_err = $cust_pay_pending->replace; + return $cpp_pending_err if $cpp_pending_err; + $transaction->submit(); if ( $transaction->is_success() && $action2 ) { + + $cust_pay_pending->status('authorized'); + my $cpp_authorized_err = $cust_pay_pending->replace; + return $cpp_authorized_err if $cpp_authorized_err; + my $auth = $transaction->authorization; my $ordernum = $transaction->can('order_number') ? $transaction->order_number @@ -3175,6 +3235,10 @@ sub realtime_bop { } + $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined'); + my $cpp_captured_err = $cust_pay_pending->replace; + return $cpp_captured_err if $cpp_captured_err; + ### # remove paycvv after initial transaction ### @@ -3224,8 +3288,15 @@ sub realtime_bop { 'paybatch' => $paybatch, 'paydate' => $paydate, } ); + #doesn't hurt to know, even though the dup check is in cust_pay_pending now $cust_pay->payunique( $options{payunique} ) if length($options{payunique}); + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction + my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () ); if ( $error ) { @@ -3234,11 +3305,13 @@ sub realtime_bop { ( 'manual' => 1 ) : () ); if ( $error2 ) { - # gah, even with transactions. - my $e = 'WARNING: Card/ACH debited but database not updated - '. + # gah. but at least we have a record of the state we had to abort in + # from cust_pay_pending now. + my $e = "WARNING: $method captured but payment not recorded - ". "error inserting payment ($processor): $error2". " (previously tried insert with invnum #$options{'invnum'}" . - ": $error )"; + ": $error ) - pending payment saved as paypendingnum ". + $cust_pay_pending->paypendingnum. "\n"; warn $e; return $e; } @@ -3248,7 +3321,25 @@ sub realtime_bop { ${ $options{'paynum_ref'} } = $cust_pay->paynum; } - return ''; #no error + $cust_pay_pending->status('done'); + $cust_pay_pending->statustext('captured'); + my $cpp_done_err = $cust_pay_pending->replace; + + if ( $cpp_done_err ) { + + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + my $e = "WARNING: $method captured but payment not recorded - ". + "error updating status for paypendingnum ". + $cust_pay_pending->paypendingnum. ": $cpp_done_err \n"; + warn $e; + return $e; + + } else { + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return ''; #no error + + } } else { @@ -3309,7 +3400,18 @@ sub realtime_bop { if $error; } - + + $cust_pay_pending->status('done'); + $cust_pay_pending->statustext("declined: $perror"); + my $cpp_done_err = $cust_pay_pending->replace; + if ( $cpp_done_err ) { + my $e = "WARNING: $method declined but pending payment not resolved - ". + "error updating status for paypendingnum ". + $cust_pay_pending->paypendingnum. ": $cpp_done_err \n"; + warn $e; + $perror = "$e ($perror)"; + } + return $perror; } @@ -3779,6 +3881,8 @@ sub batch_card { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + #this needs to handle mysql as well as Pg, like svc_acct.pm + #(make it into a common function if folks need to do batching with mysql) $dbh->do("LOCK TABLE pay_batch IN SHARE ROW EXCLUSIVE MODE") or return "Cannot lock pay_batch: " . $dbh->errstr;