changes to support eWay third-party payment, #10208
[freeside.git] / FS / FS / agent.pm
index a9e41a6..3794d3f 100644 (file)
@@ -2,11 +2,17 @@ package FS::agent;
 
 use strict;
 use vars qw( @ISA );
+#use Crypt::YAPassGen;
+use Business::CreditCard 0.28;
 use FS::Record qw( dbh qsearch qsearchs );
 use FS::cust_main;
+use FS::cust_pkg;
 use FS::agent_type;
+use FS::reg_code;
+use FS::TicketSystem;
+use FS::Conf;
 
-@ISA = qw( FS::Record );
+@ISA = qw( FS::m2m_Common FS::Record );
 
 =head1 NAME
 
@@ -44,17 +50,23 @@ from FS::Record.  The following fields are currently supported:
 
 =item agent - Text name of this agent
 
-=item typenum - Agent type.  See L<FS::agent_type>
+=item typenum - Agent type (see L<FS::agent_type>)
 
-=item prog - For future use.
+=item ticketing_queueid - Ticketing Queue
 
-=item freq - For future use.
+=item invoice_template - Invoice template name
+
+=item agent_custnum - Optional agent customer (see L<FS::cust_main>)
 
 =item disabled - Disabled flag, empty or 'Y'
 
-=item username - Username for the Agent interface
+=item prog - Deprecated (never used)
+
+=item freq - Deprecated (never used)
 
-=item _password - Password for the Agent interface
+=item username - (Deprecated) Username for the Agent interface
+
+=item _password - (Deprecated) Password for the Agent interface
 
 =back
 
@@ -113,6 +125,8 @@ sub check {
       || $self->ut_number('typenum')
       || $self->ut_numbern('freq')
       || $self->ut_textn('prog')
+      || $self->ut_textn('invoice_template')
+      || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
   ;
   return $error if $error;
 
@@ -151,6 +165,18 @@ sub agent_type {
   qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
 }
 
+=item agent_cust_main
+
+Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
+agent.
+
+=cut
+
+sub agent_cust_main {
+  my $self = shift;
+  qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
+}
+
 =item pkgpart_hashref
 
 Returns a hash reference.  The keys of the hash are pkgparts.  The value is
@@ -164,6 +190,144 @@ sub pkgpart_hashref {
   $self->agent_type->pkgpart_hashref;
 }
 
+=item ticketing_queue
+
+Returns the queue name corresponding with the id from the I<ticketing_queueid>
+field, or the empty string.
+
+=cut
+
+sub ticketing_queue {
+  my $self = shift;
+  FS::TicketSystem->queue($self->ticketing_queueid);
+};
+
+=item payment_gateway [ OPTION => VALUE, ... ]
+
+Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
+
+Currently available options are I<nofatal>, I<invnum>, I<method>, and I<payinfo>.
+
+If I<nofatal> is set, and no gateway is available, then the empty string
+will be returned instead of throwing a fatal exception.
+
+If I<invnum> is set to the number of an invoice (see L<FS::cust_bill>) then
+an attempt will be made to select a gateway suited for the taxes paid on 
+the invoice.
+
+The I<method> and I<payinfo> options can be used to influence the choice
+as well.  Presently only 'CC' and 'ECHECK' methods are meaningful.
+
+When the I<method> is 'CC' then the card number in I<payinfo> can direct
+this routine to route to a gateway suited for that type of card.
+
+=cut
+
+sub payment_gateway {
+  my ( $self, %options ) = @_;
+
+  my $taxclass = '';
+  if ( $options{invnum} ) {
+
+    my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } );
+    die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
+
+    my @part_pkg =
+      map  { $_->part_pkg }
+      grep { $_ }
+      map  { $_->cust_pkg }
+      $cust_bill->cust_bill_pkg;
+
+    my @taxclasses = map $_->taxclass, @part_pkg;
+
+    $taxclass = $taxclasses[0]
+      unless grep { $taxclasses[0] ne $_ } @taxclasses; #unless there are
+                                                        #different taxclasses
+  }
+
+  #look for an agent gateway override first
+  my $cardtype;
+  if ( $options{method} && $options{method} eq 'CC' && $options{payinfo} ) {
+    $cardtype = cardtype($options{payinfo});
+  } elsif ( $options{method} && $options{method} eq 'ECHECK' ) {
+    $cardtype = 'ACH';
+  } else {
+    $cardtype = $options{method} || '';
+  }
+
+  my $override =
+       qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => $cardtype,
+                                           taxclass => $taxclass,       } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => '',
+                                           taxclass => $taxclass,       } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => $cardtype,
+                                           taxclass => '',              } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => '',
+                                           taxclass => '',              } );
+
+  my $payment_gateway;
+  my $conf = new FS::Conf;
+  if ( $override ) { #use a payment gateway override
+
+    $payment_gateway = $override->payment_gateway;
+
+    $payment_gateway->gateway_namespace('Business::OnlinePayment')
+      unless $payment_gateway->gateway_namespace;
+
+  } else { #use the standard settings from the config
+
+    # the standard settings from the config could be moved to a null agent
+    # agent_payment_gateway referenced payment_gateway
+
+    unless ( $conf->exists('business-onlinepayment') ) {
+      if ( $options{'nofatal'} ) {
+        return '';
+      } else {
+        die "Real-time processing not enabled\n";
+      }
+    }
+
+    #load up config
+    my $bop_config = 'business-onlinepayment';
+    $bop_config .= '-ach'
+      if ( $options{method}
+           && $options{method} =~ /^(ECHECK|CHEK)$/
+           && $conf->exists($bop_config. '-ach')
+         );
+    my ( $processor, $login, $password, $action, @bop_options ) =
+      $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;
+
+    $payment_gateway = new FS::payment_gateway;
+
+    $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
+                                 'Business::OnlinePayment');
+    $payment_gateway->gateway_module($processor);
+    $payment_gateway->gateway_username($login);
+    $payment_gateway->gateway_password($password);
+    $payment_gateway->gateway_action($action);
+    $payment_gateway->set('options', [ @bop_options ]);
+
+  }
+
+  unless ( $payment_gateway->gateway_namespace ) {
+    $payment_gateway->gateway_namespace(
+      scalar($conf->config('business-onlinepayment-namespace'))
+      || 'Business::OnlinePayment'
+    );
+  }
+
+  $payment_gateway;
+}
+
 =item num_prospect_cust_main
 
 Returns the number of prospects (customers with no packages ever ordered) for
@@ -177,10 +341,9 @@ sub num_prospect_cust_main {
 
 sub num_sql {
   my( $self, $sql ) = @_;
-  my $sth = dbh->prepare(
-    "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql"
-  ) or die dbh->errstr;
-  $sth->execute($self->agentnum) or die $sth->errstr;
+  my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
+  my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+  $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
   $sth->fetchrow_arrayref->[0];
 }
 
@@ -206,7 +369,8 @@ sub cust_main_sql {
 
 =item num_active_cust_main
 
-Returns the number of active customers for this agent.
+Returns the number of active customers for this agent (customers with active
+recurring packages).
 
 =cut
 
@@ -224,6 +388,28 @@ sub active_cust_main {
   shift->cust_main_sql(FS::cust_main->active_sql);
 }
 
+=item num_inactive_cust_main
+
+Returns the number of inactive customers for this agent (customers with no
+active recurring packages, but otherwise unsuspended/uncancelled).
+
+=cut
+
+sub num_inactive_cust_main {
+  shift->num_sql(FS::cust_main->inactive_sql);
+}
+
+=item inactive_cust_main
+
+Returns the inactive customers for this agent, as cust_main objects.
+
+=cut
+
+sub inactive_cust_main {
+  shift->cust_main_sql(FS::cust_main->inactive_sql);
+}
+
+
 =item num_susp_cust_main
 
 Returns the number of suspended customers for this agent.
@@ -264,6 +450,133 @@ sub cancel_cust_main {
   shift->cust_main_sql(FS::cust_main->cancel_sql);
 }
 
+=item num_active_cust_pkg
+
+Returns the number of active customer packages for this agent.
+
+=cut
+
+sub num_active_cust_pkg {
+  shift->num_pkg_sql(FS::cust_pkg->active_sql);
+}
+
+sub num_pkg_sql {
+  my( $self, $sql ) = @_;
+  my $statement = 
+    "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
+    " WHERE agentnum = ? AND $sql";
+  my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+  $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
+  $sth->fetchrow_arrayref->[0];
+}
+
+=item num_inactive_cust_pkg
+
+Returns the number of inactive customer packages (one-time packages otherwise
+unsuspended/uncancelled) for this agent.
+
+=cut
+
+sub num_inactive_cust_pkg {
+  shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
+}
+
+=item num_susp_cust_pkg
+
+Returns the number of suspended customer packages for this agent.
+
+=cut
+
+sub num_susp_cust_pkg {
+  shift->num_pkg_sql(FS::cust_pkg->susp_sql);
+}
+
+=item num_cancel_cust_pkg
+
+Returns the number of cancelled customer packages for this agent.
+
+=cut
+
+sub num_cancel_cust_pkg {
+  shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
+}
+
+=item generate_reg_codes NUM PKGPART_ARRAYREF
+
+Generates the specified number of registration codes, allowing purchase of the
+specified package definitions.  Returns an array reference of the newly
+generated codes, or a scalar error message.
+
+=cut
+
+#false laziness w/prepay_credit::generate
+sub generate_reg_codes {
+  my( $self, $num, $pkgparts ) = @_;
+
+  my @codeset = ( 'A'..'Z' );
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my @codes = ();
+  for ( 1 ... $num ) {
+    my $reg_code = new FS::reg_code {
+      'agentnum' => $self->agentnum,
+      'code'     => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
+    };
+    my $error = $reg_code->insert($pkgparts);
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    push @codes, $reg_code->code;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  \@codes;
+
+}
+
+=item num_reg_code
+
+Returns the number of unused registration codes for this agent.
+
+=cut
+
+sub num_reg_code {
+  my $self = shift;
+  my $sth = dbh->prepare(
+    "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
+  ) or die dbh->errstr;
+  $sth->execute($self->agentnum) or die $sth->errstr;
+  $sth->fetchrow_arrayref->[0];
+}
+
+=item num_prepay_credit
+
+Returns the number of unused prepaid cards for this agent.
+
+=cut
+
+sub num_prepay_credit {
+  my $self = shift;
+  my $sth = dbh->prepare(
+    "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
+  ) or die dbh->errstr;
+  $sth->execute($self->agentnum) or die $sth->errstr;
+  $sth->fetchrow_arrayref->[0];
+}
+
+
 =back
 
 =head1 BUGS