mutex the bill and collect functions per-customer
[freeside.git] / FS / FS / cust_main.pm
index a9fcb2b..74015de 100644 (file)
@@ -1098,6 +1098,8 @@ sub bill {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  $self->select_for_update; #mutex
+
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
  
@@ -1499,6 +1501,8 @@ sub collect {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  $self->select_for_update; #mutex
+
   my $balance = $self->balance;
   warn "collect customer". $self->custnum. ": balance $balance" if $DEBUG;
   unless ( $balance > 0 ) { #redundant?????
@@ -1701,7 +1705,7 @@ sub realtime_bop {
   #overrides
   $self->set( $_ => $options{$_} )
     foreach grep { exists($options{$_}) }
-            qw( payname address1 address2 city state zip payinfo paydate );
+            qw( payname address1 address2 city state zip payinfo paydate paycvv);
 
   #load up config
   my $bop_config = 'business-onlinepayment';
@@ -1711,6 +1715,9 @@ sub realtime_bop {
     $conf->config($bop_config);
   $action ||= 'normal authorization';
   pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+  die "No real-time processor is enabled - ".
+      "did you set the business-onlinepayment configuration value?\n"
+    unless $processor;
 
   #massage data
 
@@ -1837,11 +1844,12 @@ sub realtime_bop {
   }
 
   #remove paycvv after initial transaction
-  #make this disable-able via a config option if anyone insists?  
-  # (though that probably violates cardholder agreements)
+  #false laziness w/misc/process/payment.cgi - check both to make sure working
+  # correctly
   if ( defined $self->dbdef_table->column('paycvv')
        && length($self->paycvv)
        && ! grep { $_ eq cardtype($self->payinfo) } $conf->config('cvv-save')
+       && ! length($options{'paycvv'})
   ) {
     my $new = new FS::cust_main { $self->hash };
     $new->paycvv('');
@@ -1871,15 +1879,19 @@ sub realtime_bop {
     } );
     my $error = $cust_pay->insert;
     if ( $error ) {
-      # gah, even with transactions.
-      my $e = 'WARNING: Card/ACH debited but database not updated - '.
-              'error applying payment, invnum #' . $self->invnum.
-              " ($processor): $error";
-      warn $e;
-      return $e;
-    } else {
-      return '';
+      $cust_pay->invnum(''); #try again with no specific invnum
+      my $error2 = $cust_pay->insert;
+      if ( $error2 ) {
+        # gah, even with transactions.
+        my $e = 'WARNING: Card/ACH debited but database not updated - '.
+                "error inserting payment ($processor): $error2".
+                " (previously tried insert with invnum #$options{'invnum'}" .
+                ": $error )";
+        warn $e;
+        return $e;
+      }
     }
+    return ''; #no error
 
   } else {
 
@@ -1888,7 +1900,7 @@ sub realtime_bop {
     if ( !$options{'quiet'} && !$realtime_bop_decline_quiet
          && $conf->exists('emaildecline')
          && grep { $_ ne 'POST' } $self->invoicing_list
-         && ! grep { $_ eq $transaction->error_message }
+         && ! grep { $transaction->error_message =~ /$_/ }
                    $conf->config('emaildecline-exclude')
     ) {
       my @templ = $conf->config('declinetemplate');
@@ -2121,6 +2133,24 @@ sub balance_date {
   );
 }
 
+=item paydate_monthyear
+
+Returns a two-element list consisting of the month and year of this customer's
+paydate (credit card expiration date for CARD customers)
+
+=cut
+
+sub paydate_monthyear {
+  my $self = shift;
+  if ( $self->paydate  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #Pg date format
+    ( $2, $1 );
+  } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+    ( $1, $3 );
+  } else {
+    ('', '');
+  }
+}
+
 =item invoicing_list [ ARRAYREF ]
 
 If an arguement is given, sets these email addresses as invoice recipients
@@ -2451,6 +2481,18 @@ sub cust_refund {
     qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
 }
 
+=item select_for_update
+
+Selects this record with the SQL "FOR UPDATE" command.  This can be useful as
+a mutex.
+
+=cut
+
+sub select_for_update {
+  my $self = shift;
+  qsearch('cust_main', { 'custnum' => $self->custnum }, '*', 'FOR UPDATE' );
+}
+
 =back
 
 =head1 SUBROUTINES