RT# 76093 - Added ability to charge a processing fee when taking a payment on the...
[freeside.git] / FS / FS / cust_main / Billing_Realtime.pm
index dc0eea6..ed5d094 100644 (file)
@@ -5,6 +5,7 @@ use vars qw( $conf $DEBUG $me );
 use vars qw( $realtime_bop_decline_quiet ); #ugh
 use Data::Dumper;
 use Business::CreditCard 0.35;
+use Business::OnlinePayment;
 use FS::UID qw( dbh myconnect );
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( send_email );
@@ -353,20 +354,28 @@ 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')
+  $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage', $self->agentnum) 
+    if $conf->config('credit-card-surcharge-percentage', $self->agentnum)
+    && $options{method} eq 'CC';
+
+  my $cc_surcharge_flat = 0;
+  $cc_surcharge_flat = $conf->config('credit-card-surcharge-flatfee', $self->agentnum)
+    if $conf->config('credit-card-surcharge-flatfee', $self->agentnum)
     && $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;
+  if($options{'cc_surcharge_from_event'} && ($cc_surcharge_pct > 0 || $cc_surcharge_flat > 0)) {
+    if ($options{'amount'} > 0) {
+      $cc_surcharge = ($options{'amount'} * ($cc_surcharge_pct / 100)) + $cc_surcharge_flat;
       $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 ));
+  elsif($cc_surcharge_pct > 0 || $cc_surcharge_flat > 0) {
+    # we're called not from event (i.e. from a
+    # payment screen), so consider the given
+               # amount as post-surcharge-processing_fee
+    $cc_surcharge = $options{'amount'} - $options{'processing-fee'} - (($options{'amount'} - ($cc_surcharge_flat + $options{'processing-fee'})) / ( 1 + $cc_surcharge_pct/100 )) if $options{'amount'} > 0;
   }
   
   $cc_surcharge = sprintf("%.2f",$cc_surcharge) if $cc_surcharge > 0;
@@ -970,7 +979,7 @@ sub _realtime_bop_result {
       }
 
       # have a CC surcharge portion --> one-time charge
-      if ( $options{'cc_surcharge'} > 0 ) { 
+      if ( $options{'cc_surcharge'} > 0 || $options{'processing-fee'} > 0) {
            # XXX: this whole block needs to be in a transaction?
 
          my $invnum;
@@ -991,42 +1000,83 @@ sub _realtime_bop_result {
          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!';
+               warn 'CC SURCHARGE OR PROCESS FEE AND NO INVOICES PICKED TO APPLY IT!';
                return '';
          }
 
-         my $cust_pkg;
-         my $charge_error = $self->charge({
+    if ($options{'cc_surcharge'} > 0) {
+           my $cust_pkg;
+      my $cc_surcharge_text = 'Credit Card Surcharge';
+      $cc_surcharge_text = $conf->config('credit-card-surcharge-text', $self->agentnum) if $conf->exists('credit-card-surcharge-text', $self->agentnum);
+           my $charge_error = $self->charge({
                                    'amount'    => $options{'cc_surcharge'},
-                                   'pkg'       => 'Credit Card Surcharge',
+                                   'pkg'       => $cc_surcharge_text,
                                    'setuptax'  => 'Y',
                                    'cust_pkg_ref' => \$cust_pkg,
-                               });
-         if($charge_error) {
-               warn 'Unable to add CC surcharge cust_pkg';
-               return '';
-         }
+                       });
+
+           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...
+      }
 
-         $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 $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'});
+      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;
+    } # end if $options{'cc_surcharge'}
+
+    if ($options{'processing-fee'} > 0) {
+      my $pf_cust_pkg;
+      my $processing_fee_text = 'Payment Processing Fee';
+      my $pf_change_error = $self->charge({
+            'amount'  => $options{'processing-fee'},
+            'pkg'   => $processing_fee_text,
+            'setuptax'  => 'Y',
+            'cust_pkg_ref' => \$pf_cust_pkg,
+      });
+
+      if($pf_change_error) {
+        warn 'Unable to add payment processing fee';
+        return '';
+      }
+
+      $pf_cust_pkg->setup(time);
+      my $pf_error = $pf_cust_pkg->replace;
+      if($pf_error) {
+        warn 'Unable to set setup time on cust_pkg for processing fee';
+        # but keep going...
+      }
 
-         warn "cannot add CC surcharge to invoice #$invnum: $grand_error"
-           if $grand_error;
+      my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum });
+      unless ( $cust_bill ) {
+        warn "race condition + invoice deletion just happened";
+        return '';
       }
 
+      my $grand_pf_error =
+        $cust_bill->add_cc_surcharge($pf_cust_pkg->pkgnum,$options{'processing-fee'});
+
+      warn "cannot add Processing fee to invoice #$invnum: $grand_pf_error"
+        if $grand_pf_error;
+    } #end if $options{'processing-fee'}
+
+      } #end if ( $options{'cc_surcharge'} > 0 || $options{'processing-fee'} > 0)
+
       return ''; #no error
 
     }
@@ -1470,7 +1520,7 @@ sub realtime_refund_bop {
       $self->agent->payment_gateway( 'method'  => $options{method},
                                      #'payinfo' => $payinfo,
                                    );
-    my( $processor, $login, $password, $namespace ) =
+    ( $processor, $login, $password, $namespace ) =
       map { my $method = "gateway_$_"; $payment_gateway->$method }
         qw( module username password namespace );
 
@@ -1862,10 +1912,19 @@ sub realtime_verify_bop {
     if defined($options{payunique}) && length($options{payunique});
 
   IMMEDIATE: {
-    # open a separate handle for creating/updating the cust_pay_pending record
+    # open a separate handle for creating/updating the cust_pay_pending
+    # record
     local $FS::UID::dbh = myconnect();
     local $FS::UID::AutoCommit = 1;
 
+    # if this is an existing customer (and we can tell now because
+    # this is a fresh transaction), it's safe to assign their custnum
+    # to the cust_pay_pending record, and then the verification attempt
+    # will remain linked to them even if it fails.
+    if ( FS::cust_main->by_key($self->custnum) ) {
+      $cust_pay_pending->set('custnum', $self->custnum);
+    }
+
     warn "inserting cust_pay_pending record for customer ". $self->custnum. "\n"
       if $DEBUG > 1;
 
@@ -2092,12 +2151,14 @@ sub realtime_verify_bop {
   # the cust_main)
   ###
 
-  $cust_pay_pending->set('custnum', $self->custnum);
-  my $set_custnum_err = $cust_pay_pending->replace;
-  if ($set_custnum_err) {
-    $log->error($set_custnum_err);
-    $error ||= $set_custnum_err;
-    # but if there was a real verification error also, return that one
+  if (!$cust_pay_pending->custnum) {
+    $cust_pay_pending->set('custnum', $self->custnum);
+    my $set_custnum_err = $cust_pay_pending->replace;
+    if ($set_custnum_err) {
+      $log->error($set_custnum_err);
+      $error ||= $set_custnum_err;
+      # but if there was a real verification error also, return that one
+    }
   }
 
   ###