self-service interface: add proper password changer and prevent "Setup my services...
[freeside.git] / FS / FS / ClientAPI / MyAccount.pm
index fe2e1c2..54b8a99 100644 (file)
@@ -2,14 +2,16 @@ package FS::ClientAPI::MyAccount;
 
 use strict;
 use vars qw($cache);
+use subs qw(_cache);
 use Digest::MD5 qw(md5_hex);
 use Date::Format;
 use Business::CreditCard;
-use Cache::SharedMemoryCache; #store in db?
+use Time::Duration;
 use FS::CGI qw(small_custview); #doh
 use FS::Conf;
 use FS::Record qw(qsearch qsearchs);
 use FS::Msgcat qw(gettext);
+use FS::ClientAPI_SessionCache;
 use FS::svc_acct;
 use FS::svc_domain;
 use FS::svc_external;
@@ -19,42 +21,22 @@ use FS::cust_bill;
 use FS::cust_main_county;
 use FS::cust_pkg;
 
-use FS::ClientAPI; #hmm
-FS::ClientAPI->register_handlers(
-  'MyAccount/login'              => \&login,
-  'MyAccount/logout'             => \&logout,
-  'MyAccount/customer_info'      => \&customer_info,
-  'MyAccount/edit_info'          => \&edit_info,
-  'MyAccount/invoice'            => \&invoice,
-  'MyAccount/list_invoices'      => \&list_invoices,
-  'MyAccount/cancel'             => \&cancel,
-  'MyAccount/payment_info'       => \&payment_info,
-  'MyAccount/process_payment'    => \&process_payment,
-  'MyAccount/list_pkgs'          => \&list_pkgs,
-  'MyAccount/order_pkg'          => \&order_pkg,
-  'MyAccount/cancel_pkg'         => \&cancel_pkg,
-  'MyAccount/charge'             => \&charge,
-  'MyAccount/part_svc_info'      => \&part_svc_info,
-  'MyAccount/provision_acct'     => \&provision_acct,
-  'MyAccount/provision_external' => \&provision_external,
-  'MyAccount/unprovision_svc'    => \&unprovision_svc,
-);
-
 use vars qw( @cust_main_editable_fields );
 @cust_main_editable_fields = qw(
   first last company address1 address2 city
     county state zip country daytime night fax
   ship_first ship_last ship_company ship_address1 ship_address2 ship_city
     ship_state ship_zip ship_country ship_daytime ship_night ship_fax
-  payby payinfo payname
+  payby payinfo payname paystart_month paystart_year payissue payip
 );
 
 use subs qw(_provision);
 
-#store in db?
-my $cache = new Cache::SharedMemoryCache( {
-   'namespace' => 'FS::ClientAPI::MyAccount',
-} );
+sub _cache {
+  $cache ||= new FS::ClientAPI_SessionCache( {
+               'namespace' => 'FS::ClientAPI::MyAccount',
+             } );
+}
 
 #false laziness w/FS::ClientAPI::passwd::passwd
 sub login {
@@ -90,9 +72,9 @@ sub login {
   my $session_id;
   do {
     $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
-  } until ( ! defined $cache->get($session_id) ); #just in case
+  } until ( ! defined _cache->get($session_id) ); #just in case
 
-  $cache->set( $session_id, $session, '1 hour' );
+  _cache->set( $session_id, $session, '1 hour' );
 
   return { 'error'      => '',
            'session_id' => $session_id,
@@ -102,7 +84,7 @@ sub login {
 sub logout {
   my $p = shift;
   if ( $p->{'session_id'} ) {
-    $cache->remove($p->{'session_id'});
+    _cache->remove($p->{'session_id'});
     return { 'error' => '' };
   } else {
     return { 'error' => "Can't resume session" }; #better error message
@@ -136,7 +118,7 @@ sub customer_info {
 
     my $conf = new FS::Conf;
     $return{small_custview} =
-      small_custview( $cust_main, $conf->config('defaultcountry') );
+      small_custview( $cust_main, $conf->config('countrydefault') );
 
     $return{name} = $cust_main->first. ' '. $cust_main->get('last');
 
@@ -150,16 +132,20 @@ sub customer_info {
     }
 
     $return{'invoicing_list'} =
-      join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list );
+      join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
     $return{'postal_invoicing'} =
       0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
 
-  } else { #no customer record
+  } elsif ( $session->{'svcnum'} ) { #no customer record
 
     my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
       or die "unknown svcnum";
     $return{name} = $svc_acct->email;
 
+  } else {
+
+    return { 'error' => 'Expired session' }; #XXX redirect to login w/this err!
+
   }
 
   return { 'error'          => '',
@@ -171,7 +157,7 @@ sub customer_info {
 
 sub edit_info {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my $custnum = $session->{'custnum'}
@@ -211,7 +197,7 @@ sub edit_info {
 
 sub payment_info {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   ##
@@ -221,7 +207,7 @@ sub payment_info {
   my $conf = new FS::Conf;
   my %states = map { $_->state => 1 }
                  qsearch('cust_main_county', {
-                   'country' => $conf->config('defaultcountry') || 'US'
+                   'country' => $conf->config('countrydefault') || 'US'
                  } );
 
   use vars qw($payment_info); #cache for performance
@@ -240,6 +226,8 @@ sub payment_info {
       'MasterCard' => 'MasterCard',
       'Discover' => 'Discover card',
       'American Express' => 'American Express card',
+      'Switch' => 'Switch',
+      'Solo' => 'Solo',
     },
 
   };
@@ -288,7 +276,7 @@ sub process_payment {
 
   my $p = shift;
 
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my %return;
@@ -355,7 +343,8 @@ sub process_payment {
     'payname'  => $payname,
     'paybatch' => $paybatch,
     'paycvv'   => $paycvv,
-    map { $_ => $p->{$_} } qw( address1 address2 city state zip )
+    map { $_ => $p->{$_} } qw( paystart_month paystart_year payissue payip
+                               address1 address2 city state zip )
   );
   return { 'error' => $error } if $error;
 
@@ -364,7 +353,8 @@ sub process_payment {
   if ( $p->{'save'} ) {
     my $new = new FS::cust_main { $cust_main->hash };
     $new->set( $_ => $p->{$_} )
-      foreach qw( payname address1 address2 city state zip payinfo );
+      foreach qw( payname paystart_month paystart_year payissue payip
+                  address1 address2 city state zip payinfo );
     $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
     $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
     my $error = $new->replace($cust_main);
@@ -376,9 +366,39 @@ sub process_payment {
 
 }
 
+sub process_prepay {
+
+  my $p = shift;
+
+  my $session = _cache->get($p->{'session_id'})
+    or return { 'error' => "Can't resume session" }; #better error message
+
+  my %return;
+
+  my $custnum = $session->{'custnum'};
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my( $amount, $seconds ) = ( 0, 0 );
+  my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
+                                           \$amount,
+                                           \$seconds
+                                         );
+
+  return { 'error' => $error } if $error;
+
+  return { 'error'    => '',
+           'amount'   => $amount,
+           'seconds'  => $seconds,
+           'duration' => duration_exact($seconds),
+         };
+
+}
+
 sub invoice {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my $custnum = $session->{'custnum'};
@@ -394,13 +414,40 @@ sub invoice {
   return { 'error'        => '',
            'invnum'       => $invnum,
            'invoice_text' => join('', $cust_bill->print_text ),
+           'invoice_html' => $cust_bill->print_html,
          };
 
 }
 
+sub invoice_logo {
+  my $p = shift;
+
+  #sessioning for this?  how do we get the session id to the backend invoice
+  # template so it can add it to the link, blah
+
+  my $templatename = $p->{'templatename'};
+
+  #false laziness-ish w/view/cust_bill-logo.cgi
+
+  my $conf = new FS::Conf;
+  if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) {
+    $templatename = "_$1";
+  } else {
+    $templatename = '';
+  }
+
+  my $filename = "logo$templatename.png";
+
+  return { 'error'        => '',
+           'logo'         => $conf->config_binary($filename),
+           'content_type' => 'image/png', #should allow gif, jpg too
+         };
+}
+
+
 sub list_invoices {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my $custnum = $session->{'custnum'};
@@ -421,7 +468,7 @@ sub list_invoices {
 
 sub cancel {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my $custnum = $session->{'custnum'};
@@ -474,7 +521,52 @@ sub list_pkgs {
                         } $cust_main->ncancelled_pkgs
                   ],
     'small_custview' =>
-      small_custview( $cust_main, $conf->config('defaultcountry') ),
+      small_custview( $cust_main, $conf->config('countrydefault') ),
+  };
+
+}
+
+sub list_svcs {
+  my $p = shift;
+
+  use Data::Dumper;
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  my $search = { 'custnum' => $custnum };
+  $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+  my $cust_main = qsearchs('cust_main', $search )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my @cust_svc = ();
+  #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
+  foreach my $cust_pkg ( $cust_main->unsuspended_pkgs ) {
+    push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
+  }
+  @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
+    if $p->{'svcdb'};
+
+  #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
+  #              @svc_x;
+
+  { 
+    #no#'svcnum'   => $session->{'svcnum'},
+    'custnum'  => $custnum,
+    'svcs'     => [ map { 
+                          my $svc_x = $_->svc_x;
+                          my($label, $value) = $_->label;
+
+                          { 'svcnum'   => $_->svcnum,
+                            'label'    => $label,
+                            'value'    => $value,
+                            'username' => $svc_x->username,
+                            'email'    => $svc_x->email,
+                            # more...
+                          };
+                        }
+                        @cust_svc
+                  ],
   };
 
 }
@@ -592,7 +684,7 @@ sub order_pkg {
 
 sub cancel_pkg {
   my $p = shift;
-  my $session = $cache->get($p->{'session_id'})
+  my $session = _cache->get($p->{'session_id'})
     or return { 'error' => "Can't resume session" }; #better error message
 
   my $custnum = $session->{'custnum'};
@@ -717,7 +809,7 @@ sub part_svc_info {
     'acstate'         => '',
 
     'small_custview' =>
-      small_custview( $cust_main, $conf->config('defaultcountry') ),
+      small_custview( $cust_main, $conf->config('countrydefault') ),
 
   };
 
@@ -747,7 +839,46 @@ sub unprovision_svc {
   return { 'svc'   => $cust_svc->part_svc->svc,
            'error' => $cust_svc->cancel,
            'small_custview' =>
-             small_custview( $cust_main, $conf->config('defaultcountry') ),
+             small_custview( $cust_main, $conf->config('countrydefault') ),
+         };
+
+}
+
+sub myaccount_passwd {
+  my $p = shift;
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  return { 'error' => "New passwords don't match." }
+    if $p->{'new_password'} ne $p->{'new_password2'};
+
+  return { 'error' => 'Enter new password' }
+    unless length($p->{'new_password'});
+
+  #my $search = { 'custnum' => $custnum };
+  #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+  $custnum =~ /^(\d+)$/ or die "illegal custnum";
+  my $search = " AND custnum = $1";
+  $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
+
+  my $svc_acct = qsearchs( {
+    'table'     => 'svc_acct',
+    'addl_from' => 'LEFT JOIN cust_svc  USING ( svcnum  ) '.
+                   'LEFT JOIN cust_pkg  USING ( pkgnum  ) '.
+                   'LEFT JOIN cust_main USING ( custnum ) ',
+    'hashref'   => { 'svcnum' => $p->{'svcnum'}, },
+    'extra_sql' => $search, #important
+  } )
+    or return { 'error' => "Service not found" };
+
+  $svc_acct->_password($p->{'new_password'});
+  my $error = $svc_acct->replace();
+
+  my($label, $value) = $svc_acct->cust_svc->label;
+
+  return { 'error' => $error,
+           'label' => $label,
+           'value' => $value,
          };
 
 }
@@ -761,28 +892,27 @@ sub _custoragent_session_custnum {
   if ( $p->{'session_id'} ) {
 
     $context = 'customer';
-    $session = $cache->get($p->{'session_id'})
-      or return { 'error' => "Can't resume session" }; #better error message
+    $session = _cache->get($p->{'session_id'})
+      or return ( 'error' => "Can't resume session" ); #better error message
     $custnum = $session->{'custnum'};
 
   } elsif ( $p->{'agent_session_id'} ) {
 
     $context = 'agent';
-    my $agent_cache = new Cache::SharedMemoryCache( {
+    my $agent_cache = new FS::ClientAPI_SessionCache( {
       'namespace' => 'FS::ClientAPI::Agent',
     } );
     $session = $agent_cache->get($p->{'agent_session_id'})
-      or return { 'error' => "Can't resume session" }; #better error message
+      or return ( 'error' => "Can't resume session" ); #better error message
     $custnum = $p->{'custnum'};
 
   } else {
-    return { 'error' => "Can't resume session" }; #better error message
+    return ( 'error' => "Can't resume session" ); #better error message
   }
 
   ($context, $session, $custnum);
 
 }
 
-
 1;