selfservice command to suspend packages, RT#9989
[freeside.git] / FS / FS / ClientAPI / MyAccount.pm
index 26cd76f..29e374c 100644 (file)
@@ -1,18 +1,21 @@
 package FS::ClientAPI::MyAccount;
 
+use 5.008; #require 5.8+ for Time::Local 1.05+
 use strict;
-use vars qw( $cache $DEBUG );
+use vars qw( $cache $DEBUG $me );
 use subs qw( _cache _provision );
 use Data::Dumper;
 use Digest::MD5 qw(md5_hex);
 use Date::Format;
 use Business::CreditCard;
 use Time::Duration;
+use Time::Local qw(timelocal_nocheck);
 use FS::UI::Web::small_custview qw(small_custview); #less doh
 use FS::UI::Web;
 use FS::UI::bytecount qw( display_bytecount );
 use FS::Conf;
-use FS::Record qw(qsearch qsearchs);
+#use FS::UID qw(dbh);
+use FS::Record qw(qsearch qsearchs dbh);
 use FS::Msgcat qw(gettext);
 use FS::Misc qw(card_types);
 use FS::ClientAPI_SessionCache;
@@ -28,16 +31,10 @@ use FS::cust_pkg;
 use FS::payby;
 use FS::acct_rt_transaction;
 use HTML::Entities;
+use FS::TicketSystem;
 
 $DEBUG = 0;
-
-#false laziness with FS::cust_main
-BEGIN {
-  eval "use Time::Local;";
-  die "Time::Local minimum version 1.05 required with Perl versions before 5.6"
-    if $] < 5.006 && !defined($Time::Local::VERSION);
-  eval "use Time::Local qw(timelocal_nocheck);";
-}
+$me = '[FS::ClientAPI::MyAccount]';
 
 use vars qw( @cust_main_editable_fields );
 @cust_main_editable_fields = qw(
@@ -56,24 +53,72 @@ sub _cache {
 }
 
 sub skin_info {
-  #my $p = shift;
+  my $p = shift;
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  #return { 'error' => $session } if $context eq 'error';
+
+  my $agentnum = '';
+  if ( $context eq 'customer' ) {
+
+    my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?')
+      or die dbh->errstr;
+
+    $sth->execute($custnum) or die $sth->errstr;
+
+    $agentnum = $sth->fetchrow_arrayref->[0]
+      or die "no agentnum for custnum $custnum";
+
+  #} elsif ( $context eq 'agent' ) {
+  } elsif ( $p->{'agentnum'} =~ /^(\d+)$/ ) {
+    $agentnum = $1;
+  }
 
   my $conf = new FS::Conf;
 
-  use vars qw($skin_info); #cache for performance.
-  #agentnum eventually...?  but if they're not logged in yet.. ?
+  #false laziness w/Signup.pm
 
-  $skin_info ||= {
-    'head'           => join("\n", $conf->config('selfservice-head') ),
-    'body_header'    => join("\n", $conf->config('selfservice-body_header') ),
-    'body_footer'    => join("\n", $conf->config('selfservice-body_footer') ),
-    'body_bgcolor'   => scalar( $conf->config('selfservice-body_bgcolor') ),
-    'box_bgcolor'    => scalar( $conf->config('selfservice-box_bgcolor')  ),
+  my $skin_info_cache_agent = _cache->get("skin_info_cache_agent$agentnum");
 
-    'company_name'   => scalar($conf->config('company_name')),
-  };
+  if ( $skin_info_cache_agent ) {
 
-  $skin_info;
+    warn "$me loading cached skin info for agentnum $agentnum\n"
+      if $DEBUG > 1;
+
+  } else {
+
+    warn "$me populating skin info cache for agentnum $agentnum\n"
+      if $DEBUG > 1;
+
+    $skin_info_cache_agent = {
+      'agentnum' => $agentnum,
+      ( map { $_ => scalar( $conf->config($_, $agentnum) ) }
+        qw( company_name ) ),
+      ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) }
+        qw( body_bgcolor box_bgcolor
+            text_color link_color vlink_color hlink_color alink_color
+            font title_color title_align title_size menu_bgcolor menu_fontsize
+          )
+      ),
+      ( map { $_ => $conf->exists("selfservice-$_", $agentnum ) }
+        qw( menu_skipblanks menu_skipheadings menu_nounderline )
+      ),
+      ( map { $_ => scalar($conf->config_binary("selfservice-$_", $agentnum)) }
+        qw( title_left_image title_right_image
+            menu_top_image menu_body_image menu_bottom_image
+          )
+      ),
+      'logo' => scalar($conf->config_binary('logo.png', $agentnum )),
+      ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) }
+        qw( head body_header body_footer company_address ) ),
+    };
+
+    _cache->set("skin_info_cache_agent$agentnum", $skin_info_cache_agent);
+
+  }
+
+  #{ %$skin_info_cache_agent };
+  $skin_info_cache_agent;
 
 }
 
@@ -83,7 +128,7 @@ sub login_info {
   my $conf = new FS::Conf;
 
   my %info = (
-    %{ skin_info() },
+    %{ skin_info($p) },
     'phone_login'  => $conf->exists('selfservice_server-phone_login'),
     'single_domain'=> scalar($conf->config('selfservice_server-single_domain')),
   );
@@ -176,9 +221,9 @@ sub logout {
   my $p = shift;
   if ( $p->{'session_id'} ) {
     _cache->remove($p->{'session_id'});
-    return { %{ skin_info() }, 'error' => '' };
+    return { %{ skin_info($p) }, 'error' => '' };
   } else {
-    return { %{ skin_info() }, 'error' => "Can't resume session" }; #better error message
+    return { %{ skin_info($p) }, 'error' => "Can't resume session" }; #better error message
   }
 }
 
@@ -221,11 +266,13 @@ sub access_info {
     @{ $info->{cust_paybys} }
   ];
 
+  $info->{'self_suspend_reason'} = 
+      $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum);
+
   return { %$info,
-           'custnum'    => $custnum,
-           'pkgnum'     => $session->{'pkgnum'},
-           'svcnum'     => $session->{'svcnum'},
-           'nonprimary' => $session->{'nonprimary'},
+           'custnum'       => $custnum,
+           'access_pkgnum' => $session->{'pkgnum'},
+           'access_svcnum' => $session->{'svcnum'},
          };
 }
 
@@ -276,8 +323,6 @@ sub customer_info {
                       ( $session->{'pkgnum'} ? 1 : 0 ), #nobalance
                     );
 
-    warn $return{small_custview};
-
     $return{name} = $cust_main->first. ' '. $cust_main->get('last');
 
     for (@cust_main_editable_fields) {
@@ -315,6 +360,11 @@ sub customer_info {
       $return{support_services} = \@support_services;
     }
 
+    if ( $conf->config('prepayment_discounts-credit_type') ) {
+      #need to eval?
+      $return{discount_terms_hash} = { $cust_main->discount_terms_hash };
+    }
+
   } elsif ( $session->{'svcnum'} ) { #no customer record
 
     my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
@@ -417,10 +467,10 @@ sub payment_info {
   #generic
   ##
 
+  my $conf = new FS::Conf;
   use vars qw($payment_info); #cache for performance
   unless ( $payment_info ) {
 
-    my $conf = new FS::Conf;
     my %states = map { $_->state => 1 }
                    qsearch('cust_main_county', {
                      'country' => $conf->config('countrydefault') || 'US'
@@ -455,6 +505,8 @@ sub payment_info {
       'show_ss'  => $conf->exists('show_ss'),
       'show_stateid' => $conf->exists('show_stateid'),
       'show_paystate' => $conf->exists('show_bankstate'),
+
+      'save_unchecked' => $conf->exists('selfservice-save_unchecked'),
     };
 
   }
@@ -511,6 +563,11 @@ sub payment_info {
 
   }
 
+  if ( $conf->config('prepayment_discounts-credit_type') ) {
+    #need to eval?
+    $return{discount_terms_hash} = { $cust_main->discount_terms_hash };
+  }
+
   #doubleclick protection
   my $_date = time;
   $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
@@ -537,6 +594,15 @@ sub process_payment {
   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
     or return { 'error' => "unknown custnum $custnum" };
 
+  $p->{'amount'} =~ /^\s*(\d+(\.\d{2})?)\s*$/
+    or return { 'error' => gettext('illegal_amount') };
+  my $amount = $1;
+  return { error => 'Amount must be greater than 0' } unless $amount > 0;
+
+  $p->{'discount_term'} =~ /^\s*(\d*)\s*$/
+    or return { 'error' => gettext('illegal_discount_term'). ': '. $p->{'discount_term'} };
+  my $discount_term = $1;
+
   $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
     or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
   my $payname = $1;
@@ -583,7 +649,7 @@ sub process_payment {
     validate($payinfo)
       or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
     return { 'error' => gettext('unknown_card_type') }
-      if cardtype($payinfo) eq "Unknown";
+      if $payinfo !~ /^99\d{14}$/ && cardtype($payinfo) eq "Unknown";
 
     if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) {
       if ( cardtype($payinfo) eq 'American Express card' ) {
@@ -607,7 +673,7 @@ sub process_payment {
     'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
   );
 
-  my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $p->{'amount'},
+  my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
     'quiet'    => 1,
     'payinfo'  => $payinfo,
     'paydate'  => $p->{'year'}. '-'. $p->{'month'}. '-01',
@@ -615,6 +681,7 @@ sub process_payment {
     'paybatch' => $paybatch, #this doesn't actually do anything
     'paycvv'   => $paycvv,
     'pkgnum'   => $session->{'pkgnum'},
+    'discount_term' => $discount_term,
     map { $_ => $p->{$_} } @{ $payby2fields{$payby} }
   );
   return { 'error' => $error } if $error;
@@ -634,11 +701,24 @@ sub process_payment {
                     stateid stateid_state );
       $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
     }
-    $new->set( 'payinfo' => $payinfo );
+    $new->set( 'payinfo' => $cust_main->card_token || $payinfo );
     $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
     my $error = $new->replace($cust_main);
-    return { 'error' => $error } if $error;
-    $cust_main = $new;
+    if ( $error ) {
+      #no, this causes customers to process their payments again
+      #return { 'error' => $error };
+      #XXX just warn verosely for now so i can figure out how these happen in
+      # the first place, eventually should redirect them to the "change
+      #address" page but indicate the payment did process??
+      delete($p->{'payinfo'}); #don't want to log this!
+      warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
+           "NEW: ". Dumper($new)."\n".
+           "OLD: ". Dumper($cust_main)."\n".
+           "PACKET: ". Dumper($p)."\n";
+    #} else {
+      #not needed...
+      #$cust_main = $new;
+    }
   }
 
   return { 'error' => '' };
@@ -646,7 +726,6 @@ sub process_payment {
 }
 
 sub realtime_collect {
-
   my $p = shift;
 
   my $session = _cache->get($p->{'session_id'})
@@ -661,6 +740,7 @@ sub realtime_collect {
     'method'     => $p->{'method'},
     'pkgnum'     => $session->{'pkgnum'},
     'session_id' => $p->{'session_id'},
+    'apply'      => 1,
   );
   return { 'error' => $error } unless ref( $error );
 
@@ -897,7 +977,7 @@ sub list_svcs {
   #              @svc_x;
 
   { 
-    #no#'svcnum'   => $session->{'svcnum'},
+    'svcnum'   => $session->{'svcnum'},
     'custnum'  => $custnum,
     'svcs'     => [
       map { 
@@ -990,7 +1070,7 @@ sub list_support_usage {
 sub _list_cdr_usage {
   my($svc_phone, $begin, $end) = @_;
   map [ $_->downstream_csv('format' => 'default') ], #XXX config for format
-      $svc_phone->cust_svc->get_cdrs( 'begin'=>$begin, 'end'=>$end, );
+      $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, );
 }
 
 sub list_cdr_usage {
@@ -1119,6 +1199,7 @@ sub order_pkg {
       'svc_domain'   => [ qw( domain ) ],
       'svc_phone'    => [ qw( phonenum pin sip_password phone_name ) ],
       'svc_external' => [ qw( id title ) ],
+      'svc_pbx'      => [ qw( id name ) ],
     );
   
     my $svc_x = "FS::$svcdb"->new( {
@@ -1175,7 +1256,9 @@ sub order_pkg {
     $cust_pkg->reexport;
   }
 
-  return { error => '', pkgnum => $cust_pkg->pkgnum };
+  my $svcnum = $svc[0] ? $svc[0]->svcnum : '';
+
+  return { error=>'', pkgnum=>$cust_pkg->pkgnum, svcnum=>$svcnum };
 
 }
 
@@ -1282,7 +1365,7 @@ sub _do_bop_realtime {
 
     my $bill_error =    $cust_main->bill
                      || $cust_main->apply_payments_and_credits
-                     || $cust_main->collect('realtime' => 1);
+                     || $cust_main->realtime_collect;
 
     if (    $cust_main->balance > $old_balance
          && $cust_main->balance > 0
@@ -1373,6 +1456,32 @@ sub order_renew {
 
 }
 
+sub suspend_pkg {
+  my $p = shift;
+  my $session = _cache->get($p->{'session_id'})
+    or return { 'error' => "Can't resume session" }; #better error message
+
+  my $custnum = $session->{'custnum'};
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my $conf = new FS::Conf;
+  my $reasonnum = 
+    $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum)
+      or return { 'error' => 'Permission denied' };
+
+  my $pkgnum = $p->{'pkgnum'};
+
+  my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+                                        'pkgnum'  => $pkgnum,   } )
+    or return { 'error' => "unknown pkgnum $pkgnum" };
+
+  my $error = $cust_pkg->suspend(reason => $reasonnum);
+  return { 'error' => $error };
+
+}
+
 sub cancel_pkg {
   my $p = shift;
   my $session = _cache->get($p->{'session_id'})
@@ -1389,7 +1498,7 @@ sub cancel_pkg {
                                         'pkgnum'  => $pkgnum,   } )
     or return { 'error' => "unknown pkgnum $pkgnum" };
 
-  my $error = $cust_pkg->cancel( 'quiet'=>1 );
+  my $error = $cust_pkg->cancel('quiet' => 1);
   return { 'error' => $error };
 
 }
@@ -1602,6 +1711,43 @@ sub myaccount_passwd {
 
 }
 
+sub create_ticket {
+  my $p = shift;
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  warn "$me create_ticket: initializing ticket system\n" if $DEBUG;
+  FS::TicketSystem->init();
+
+  my $conf = new FS::Conf;
+  my $queue = $p->{'queue'}
+              || $conf->config('ticket_system-selfservice_queueid')
+              || $conf->config('ticket_system-default_queueid');
+
+  warn "$me create_ticket: creating ticket\n" if $DEBUG;
+  my $err_or_ticket = FS::TicketSystem->create_ticket(
+    '', #create RT session based on FS CurrentUser (fs_selfservice)
+    'queue'   => $queue,
+    'custnum' => $custnum,
+    'svcnum'  => $session->{'svcnum'},
+    map { $_ => $p->{$_} } qw( requestor cc subject message mime_type )
+  );
+
+  if ( ref($err_or_ticket) ) {
+    warn "$me create_ticket: sucessful: ". $err_or_ticket->id. "\n"
+      if $DEBUG;
+    return { 'error'     => '',
+             'ticket_id' => $err_or_ticket->id,
+           };
+  } else {
+    warn "$me create_ticket: unsucessful: $err_or_ticket\n"
+      if $DEBUG;
+    return { 'error' => $err_or_ticket };
+  }
+
+
+}
+
 #--
 
 sub _custoragent_session_custnum {
@@ -1626,6 +1772,7 @@ sub _custoragent_session_custnum {
     $custnum = $p->{'custnum'};
 
   } else {
+    $context = 'error';
     return ( 'error' => "Can't resume session" ); #better error message
   }