FS RT #30362 - added configuration option to disable package modification in selfservice
[freeside.git] / FS / FS / ClientAPI / MyAccount.pm
index d17131f..0131d89 100644 (file)
@@ -555,7 +555,9 @@ sub customer_info_short {
                       1, ##nobalance
                     );
 
-    $return{name} = $cust_main->first. ' '. $cust_main->get('last');
+    $return{first}  = $cust_main->first;
+    $return{'last'} = $cust_main->get('last');
+    $return{name}   = $cust_main->first. ' '. $cust_main->get('last');
 
     $return{payby} = $cust_main->payby;
 
@@ -565,8 +567,10 @@ sub customer_info_short {
     }
     #maybe a little more expensive, but it should be cached by now
     for (@location_editable_fields) {
-      $return{$_} = $cust_main->bill_location->get($_);
-      $return{'ship_'.$_} = $cust_main->ship_location->get($_);
+      $return{$_} = $cust_main->bill_location->get($_)
+        if $cust_main->bill_locationnum;
+      $return{'ship_'.$_} = $cust_main->ship_location->get($_)
+        if $cust_main->ship_locationnum;
     }
  
     if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
@@ -747,6 +751,8 @@ sub edit_info {
     $payby = $1;
   }
 
+  my $conf = new FS::Conf;
+
   if ( $payby =~ /^(CARD|DCRD)$/ ) {
 
     $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
@@ -759,6 +765,10 @@ sub edit_info {
 
     $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
 
+    if ( $conf->exists('selfservice-onfile_require_cvv') ){
+      return { 'error' => 'CVV2 is required' } unless $p->{'paycvv'};
+    }
+
   } elsif ( $payby =~ /^(CHEK|DCHK)$/ ) {
 
     my $payinfo;
@@ -835,6 +845,10 @@ sub payment_info {
 
       'card_types' => card_types(),
 
+      'withcvv'            => $conf->exists('selfservice-require_cvv'), #or enable optional cvv?
+      'require_cvv'        => $conf->exists('selfservice-require_cvv'),
+      'onfile_require_cvv' => $conf->exists('selfservice-onfile_require_cvv'),
+
       'paytypes' => [ @FS::cust_main::paytypes ],
 
       'paybys' => [ $conf->config('signup_server-payby') ],
@@ -907,7 +921,8 @@ sub payment_info {
 
   #doubleclick protection
   my $_date = time;
-  $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
+  $return{payunique} = "webui-MyAccount-$_date-$$-". rand() * 2**32; #new
+  $return{paybatch} = $return{payunique};  #back compat
 
   return { 'error' => '',
            %return,
@@ -957,10 +972,16 @@ sub validate_payment {
     or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
   my $payname = $1;
 
+  $p->{'payunique'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+    or return { 'error' => gettext('illegal_text'). " payunique: ". $p->{'payunique'} };
+  my $payunique = $1;
+
   $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
     or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
   my $paybatch = $1;
 
+  $payunique = $paybatch if ! length($payunique) && length($paybatch);
+
   $p->{'payby'} ||= 'CARD';
   $p->{'payby'} =~ /^([A-Z]{4})$/
     or return { 'error' => "illegal_payby " . $p->{'payby'} };
@@ -986,10 +1007,14 @@ sub validate_payment {
    
     $payinfo = $p->{'payinfo'};
 
+    my $onfile = 0;
+
     #more intelligent matching will be needed here if you change
     #card_masking_method and don't remove existing paymasks
-    $payinfo = $cust_main->payinfo
-      if $cust_main->paymask eq $payinfo;
+    if ( $cust_main->paymask eq $payinfo ) {
+      $payinfo = $cust_main->payinfo;
+      $onfile = 1;
+    }
 
     $payinfo =~ s/\D//g;
     $payinfo =~ /^(\d{13,16}|\d{8,9})$/
@@ -1011,6 +1036,10 @@ sub validate_payment {
           or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
         $paycvv = $1;
       }
+    } elsif ( $conf->exists('selfservice-onfile_require_cvv') ) {
+      return { 'error' => 'CVV2 is required' };
+    } elsif ( !$onfile && $conf->exists('selfservice-require_cvv') ) {
+      return { 'error' => 'CVV2 is required' };
     }
   
   } else {
@@ -1038,7 +1067,8 @@ sub validate_payment {
     'month'          => $p->{'month'},
     'year'           => $p->{'year'},
     'payname'        => $payname,
-    'paybatch'       => $paybatch, #this doesn't actually do anything
+    'payunique'      => $payunique,
+    'paybatch'       => $paybatch,
     'paycvv'         => $paycvv,
     'payname'        => $payname,
     'discount_term'  => $discount_term,
@@ -1212,16 +1242,14 @@ sub do_process_payment {
 
   if ( $cust_pay ) {
 
-    my($gw, $auth, $order) = split(':', $cust_pay->paybatch);
-
     return {
       'error'        => '',
       'amount'       => sprintf('%.2f', $cust_pay->paid),
       'date'         => $cust_pay->_date,
       'date_pretty'  => time2str('%Y-%m-%d', $cust_pay->_date),
       'time_pretty'  => time2str('%T', $cust_pay->_date),
-      'auth_num'     => $auth,
-      'order_num'    => $order,
+      'auth_num'     => $cust_pay->auth,
+      'order_num'    => $cust_pay->order_number,
       'receipt_html' => $receipt_html,
     };
 
@@ -1589,6 +1617,7 @@ sub list_pkgs {
     or return { 'error' => "unknown custnum $custnum" };
 
   my $conf = new FS::Conf;
+  my $immutable = $conf->exists('selfservice_immutable-package');
   
 # the duplication below is necessary:
 # 1. to maintain the current buggy behaviour wrt the cust_pkg and part_pkg
@@ -1601,6 +1630,7 @@ sub list_pkgs {
            'custnum'  => $custnum,
            'cust_pkg' => [ map {
                           { $_->hash,
+                           immutable => $immutable,
                             part_pkg => [ map $_->hashref, $_->part_pkg ],
                             part_svc =>
                               [ map $_->hashref, $_->available_part_svc ],
@@ -1633,6 +1663,7 @@ sub list_pkgs {
                           my $primary_cust_svc = $_->primary_cust_svc;
                           +{ $_->hash,
                             $_->part_pkg->hash,
+                           immutable   => $immutable,
                             pkg_label   => $_->pkg_locale,
                             status      => $_->status,
                             statuscolor => $_->statuscolor,
@@ -1711,7 +1742,7 @@ sub list_svcs {
     my $tag = $part->description . ($part->shared ? 1 : 0);
     my $row = $usage_pools{$tag} 
           ||= [ $part->description, 0, 0, $part->shared ? 1 : 0 ];
-    $row->[1] += $_->minutes; # minutes remaining
+    $row->[1] += sprintf('%.1f', $_->minutes); # minutes remaining
     $row->[2] += $part->minutes; # minutes total
   }
 
@@ -1807,6 +1838,7 @@ sub list_svcs {
                     'inbound' => ( $_ eq 'inbound' ? 1 : 0 ),
                     'begin'   => ($cust_pkg->last_bill || 0),
                     'nonzero' => 1,
+                    'disable_charged_party' => 1,
                   );
                   $hash{$_} = $sum_cdr->hashref;
                 }
@@ -2090,7 +2122,12 @@ sub _list_cdr_usage {
   # we have to return the results all at once...
   my($svc_phone, $begin, $end, %opt) = @_;
   map [ $_->downstream_csv(%opt, 'keeparray' => 1) ],
-    $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, %opt );
+    $svc_phone->get_cdrs(
+      'begin'=>$begin,
+      'end'=>$end,
+      'disable_charged_party' => 1,
+      %opt
+    );
 }
 
 sub list_cdr_usage {
@@ -2102,6 +2139,7 @@ sub list_cdr_usage {
 
 sub _usage_details {
   my($callback, $p, %opt) = @_;
+  my $conf = FS::Conf->new;
 
   my($context, $session, $custnum) = _custoragent_session_custnum($p);
   return { 'error' => $session } if $context eq 'error';
@@ -2120,7 +2158,6 @@ sub _usage_details {
   my %callback_opt;
   my $header = [];
   if ( $svcdb eq 'svc_phone' ) {
-    my $conf = FS::Conf->new;
     my $format = '';
     if ( $p->{inbound} ) {
       $format = $cust_pkg->part_pkg->option('selfservice_inbound_format') 
@@ -2154,6 +2191,14 @@ sub _usage_details {
     %callback_opt
   );
 
+  if ( $conf->exists('selfservice-hide_cdr_price') ) {
+    # ugly kludge, I know
+    my ($delete_col) = grep { $header->[$_] eq 'Price' } (0..scalar(@$header));
+    if (defined $delete_col) {
+      delete($_->[$delete_col]) foreach ($header, @usage);
+    }
+  }
+
   #kinda false laziness with FS::cust_main::bill, but perhaps
   #we should really change this bit to DateTime and DateTime::Duration
   #
@@ -2311,6 +2356,10 @@ sub change_pkg {
   my($context, $session, $custnum) = _custoragent_session_custnum($p);
   return { 'error' => $session } if $context eq 'error';
 
+  my $conf = new FS::Conf;
+  my $immutable = $conf->exists('selfservice_immutable-package');
+  return { 'error' => "Package modification disabled" } if $immutable;
+
   my $search = { 'custnum' => $custnum };
   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
   my $cust_main = qsearchs('cust_main', $search )
@@ -2332,7 +2381,6 @@ sub change_pkg {
                                    \@newpkg,
                                  );
 
-  my $conf = new FS::Conf;
   if ( $conf->exists('signup_server-realtime') ) {
 
     my $bill_error = _do_bop_realtime( $cust_main, $status, 'no_credit'=>1 );
@@ -2833,6 +2881,13 @@ sub myaccount_passwd {
   my $error = '';
 
   my $conf = new FS::Conf;
+
+  return { 'error' => 'Incorrect current password.' }
+    if  ( exists($p->{'old_password'})
+          || $conf->exists('selfservice-password_change_oldpass')
+        )
+    && ! $svc_acct->check_password($p->{'old_password'});
+
   $error = 'Password too short.'
     if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6);
   $error = 'Password too long.'
@@ -2841,6 +2896,16 @@ sub myaccount_passwd {
   $svc_acct->set_password($p->{'new_password'});
   $error ||= $svc_acct->replace();
 
+  #regular pw change in self-service should change contact pw too, otherwise its
+  #way too confusing.  hell its confusing they're separate at all, but alas.
+  #need to support the "ISP provides email that's used as a contact email" case
+  #as well as we can.
+  my $contact = FS::contact->by_selfservice_email($svc_acct->email);
+  if ( $contact && $contact->custnum == $custnum ) {
+    #svc_acct was successful but this one returns an error?  "shouldn't happen"
+    $error ||= $contact->change_password($p->{'new_password'});
+  }
+
   my($label, $value) = $svc_acct->cust_svc->label;
 
   return { 'error' => $error,
@@ -2850,7 +2915,6 @@ sub myaccount_passwd {
 
 }
 
-#regular pw change in self-service should change contact pw too, otherwise its way too confusing.  hell its confusing they're separate at all, but alas.  need to support the "ISP provides email that's used as a contact email" case as well as we can.
 #  sub contact_passwd {
 #    my $p = shift;
 #    my($context, $session, $custnum) = _custoragent_session_custnum($p);
@@ -3220,8 +3284,8 @@ sub create_ticket {
   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();
+  warn "$me create_ticket: initializing ticket system\n" if $DEBUG;
+  FS::TicketSystem->init();
 
   my $conf = new FS::Conf;
   my $queue = $p->{'queue'}