1 package FS::ClientAPI::MyAccount;
6 use Digest::MD5 qw(md5_hex);
8 use Business::CreditCard;
10 use FS::CGI qw(small_custview); #doh
12 use FS::Record qw(qsearch qsearchs);
13 use FS::Msgcat qw(gettext);
14 use FS::ClientAPI_SessionCache;
21 use FS::cust_main_county;
24 use vars qw( @cust_main_editable_fields );
25 @cust_main_editable_fields = qw(
26 first last company address1 address2 city
27 county state zip country daytime night fax
28 ship_first ship_last ship_company ship_address1 ship_address2 ship_city
29 ship_state ship_zip ship_country ship_daytime ship_night ship_fax
30 payby payinfo payname paystart_month paystart_year payissue payip
33 use subs qw(_provision);
36 $cache ||= new FS::ClientAPI_SessionCache( {
37 'namespace' => 'FS::ClientAPI::MyAccount',
41 #false laziness w/FS::ClientAPI::passwd::passwd
45 my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
46 or return { error => 'Domain '. $p->{'domain'}. ' not found' };
48 my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{'username'},
49 'domsvc' => $svc_domain->svcnum, }
51 return { error => 'User not found.' } unless $svc_acct;
53 my $conf = new FS::Conf;
54 my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
55 return { error => 'Only primary user may log in.' }
56 if $conf->exists('selfservice_server-primary_only')
57 && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
59 return { error => 'Incorrect password.' }
60 unless $svc_acct->check_password($p->{'password'});
63 'svcnum' => $svc_acct->svcnum,
66 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
68 my $cust_main = $cust_pkg->cust_main;
69 $session->{'custnum'} = $cust_main->custnum;
74 $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
75 } until ( ! defined _cache->get($session_id) ); #just in case
77 _cache->set( $session_id, $session, '1 hour' );
79 return { 'error' => '',
80 'session_id' => $session_id,
86 if ( $p->{'session_id'} ) {
87 _cache->remove($p->{'session_id'});
88 return { 'error' => '' };
90 return { 'error' => "Can't resume session" }; #better error message
97 my($context, $session, $custnum) = _custoragent_session_custnum($p);
98 return { 'error' => $session } if $context eq 'error';
101 if ( $custnum ) { #customer record
103 my $search = { 'custnum' => $custnum };
104 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
105 my $cust_main = qsearchs('cust_main', $search )
106 or return { 'error' => "unknown custnum $custnum" };
108 $return{balance} = $cust_main->balance;
112 invnum => $_->invnum,
113 date => time2str("%b %o, %Y", $_->_date),
116 } $cust_main->open_cust_bill;
117 $return{open_invoices} = \@open;
119 my $conf = new FS::Conf;
120 $return{small_custview} =
121 small_custview( $cust_main, $conf->config('countrydefault') );
123 $return{name} = $cust_main->first. ' '. $cust_main->get('last');
125 for (@cust_main_editable_fields) {
126 $return{$_} = $cust_main->get($_);
129 if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
130 $return{payinfo} = $cust_main->payinfo_masked;
131 @return{'month', 'year'} = $cust_main->paydate_monthyear;
134 $return{'invoicing_list'} =
135 join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
136 $return{'postal_invoicing'} =
137 0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
139 } elsif ( $session->{'svcnum'} ) { #no customer record
141 my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
142 or die "unknown svcnum";
143 $return{name} = $svc_acct->email;
147 return { 'error' => 'Expired session' }; #XXX redirect to login w/this err!
151 return { 'error' => '',
152 'custnum' => $custnum,
160 my $session = _cache->get($p->{'session_id'})
161 or return { 'error' => "Can't resume session" }; #better error message
163 my $custnum = $session->{'custnum'}
164 or return { 'error' => "no customer record" };
166 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
167 or return { 'error' => "unknown custnum $custnum" };
169 my $new = new FS::cust_main { $cust_main->hash };
170 $new->set( $_ => $p->{$_} )
171 foreach grep { exists $p->{$_} } @cust_main_editable_fields;
173 if ( $p->{'payby'} =~ /^(CARD|DCRD)$/ ) {
174 $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
175 if ( $new->payinfo eq $cust_main->payinfo_masked ) {
176 $new->payinfo($cust_main->payinfo);
178 $new->paycvv($p->{'paycvv'});
183 if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
184 #false laziness with httemplate/edit/process/cust_main.cgi
185 @invoicing_list = split( /\s*\,\s*/, $p->{'invoicing_list'} );
186 push @invoicing_list, 'POST' if $p->{'postal_invoicing'};
188 @invoicing_list = $cust_main->invoicing_list;
191 my $error = $new->replace($cust_main, \@invoicing_list);
192 return { 'error' => $error } if $error;
195 return { 'error' => '' };
200 my $session = _cache->get($p->{'session_id'})
201 or return { 'error' => "Can't resume session" }; #better error message
207 use vars qw($payment_info); #cache for performance
208 unless ( $payment_info ) {
210 my $conf = new FS::Conf;
211 my %states = map { $_->state => 1 }
212 qsearch('cust_main_county', {
213 'country' => $conf->config('countrydefault') || 'US'
217 #displayname #value (Business::CreditCard)
218 "VISA" => "VISA card",
219 "MasterCard" => "MasterCard",
220 "Discover" => "Discover card",
221 "American Express" => "American Express card",
222 "Diner's Club/Carte Blanche" => "Diner's Club/Carte Blanche",
223 "enRoute" => "enRoute",
225 "BankCard" => "BankCard",
226 "Switch" => "Switch",
229 my @conf_card_types = grep { ! /^\s*$/ } $conf->config('card-types');
230 if ( @conf_card_types ) {
231 #perhaps the hash is backwards for this, but this way works better for
232 #usage in selfservice
233 %card_types = map { $_ => $card_types{$_} }
236 grep { $card_types{$d} eq $_ } @conf_card_types
243 #list all counties/states/countries
244 'cust_main_county' =>
245 [ map { $_->hashref } qsearch('cust_main_county', {}) ],
247 #shortcut for one-country folks
249 [ sort { $a cmp $b } keys %states ],
252 'VISA' => 'VISA card',
253 'MasterCard' => 'MasterCard',
254 'Discover' => 'Discover card',
255 'American Express' => 'American Express card',
256 'Switch' => 'Switch',
268 my %return = %$payment_info;
270 my $custnum = $session->{'custnum'};
272 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
273 or return { 'error' => "unknown custnum $custnum" };
275 $return{balance} = $cust_main->balance;
277 $return{payname} = $cust_main->payname
278 || ( $cust_main->first. ' '. $cust_main->get('last') );
280 $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
282 $return{payby} = $cust_main->payby;
284 if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
285 $return{card_type} = cardtype($cust_main->payinfo);
286 $return{payinfo} = $cust_main->payinfo;
288 @return{'month', 'year'} = $cust_main->paydate_monthyear;
292 #doubleclick protection
294 $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
296 return { 'error' => '',
302 #some false laziness with httemplate/process/payment.cgi - look there for
303 #ACH and CVV support stuff
304 sub process_payment {
308 my $session = _cache->get($p->{'session_id'})
309 or return { 'error' => "Can't resume session" }; #better error message
313 my $custnum = $session->{'custnum'};
315 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
316 or return { 'error' => "unknown custnum $custnum" };
318 $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
319 or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
322 $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
323 or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
328 #if ( $payby eq 'CHEK' ) {
330 # $p->{'payinfo1'} =~ /^(\d+)$/
331 # or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
333 # $p->{'payinfo2'} =~ /^(\d+)$/
334 # or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
336 # $payinfo = $payinfo1. '@'. $payinfo2;
338 #} elsif ( $payby eq 'CARD' ) {
340 $payinfo = $p->{'payinfo'};
342 $payinfo =~ /^(\d{13,16})$/
343 or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
346 or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
347 return { 'error' => gettext('unknown_card_type') }
348 if cardtype($payinfo) eq "Unknown";
350 if ( defined $cust_main->dbdef_table->column('paycvv') ) {
351 if ( length($p->{'paycvv'} ) ) {
352 if ( cardtype($payinfo) eq 'American Express card' ) {
353 $p->{'paycvv'} =~ /^(\d{4})$/
354 or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
357 $p->{'paycvv'} =~ /^(\d{3})$/
358 or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
365 # die "unknown payby $payby";
368 my $error = $cust_main->realtime_bop( 'CC', $p->{'amount'},
370 'payinfo' => $payinfo,
371 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01',
372 'payname' => $payname,
373 'paybatch' => $paybatch,
375 map { $_ => $p->{$_} } qw( paystart_month paystart_year payissue payip
376 address1 address2 city state zip )
378 return { 'error' => $error } if $error;
380 $cust_main->apply_payments;
382 if ( $p->{'save'} ) {
383 my $new = new FS::cust_main { $cust_main->hash };
384 $new->set( $_ => $p->{$_} )
385 foreach qw( payname paystart_month paystart_year payissue payip
386 address1 address2 city state zip payinfo );
387 $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
388 $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
389 my $error = $new->replace($cust_main);
390 return { 'error' => $error } if $error;
394 return { 'error' => '' };
402 my $session = _cache->get($p->{'session_id'})
403 or return { 'error' => "Can't resume session" }; #better error message
407 my $custnum = $session->{'custnum'};
409 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
410 or return { 'error' => "unknown custnum $custnum" };
412 my( $amount, $seconds ) = ( 0, 0 );
413 my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
418 return { 'error' => $error } if $error;
420 return { 'error' => '',
422 'seconds' => $seconds,
423 'duration' => duration_exact($seconds),
430 my $session = _cache->get($p->{'session_id'})
431 or return { 'error' => "Can't resume session" }; #better error message
433 my $custnum = $session->{'custnum'};
435 my $invnum = $p->{'invnum'};
437 my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
438 'custnum' => $custnum } )
439 or return { 'error' => "Can't find invnum" };
443 return { 'error' => '',
445 'invoice_text' => join('', $cust_bill->print_text ),
446 'invoice_html' => $cust_bill->print_html,
454 #sessioning for this? how do we get the session id to the backend invoice
455 # template so it can add it to the link, blah
457 my $templatename = $p->{'templatename'};
459 #false laziness-ish w/view/cust_bill-logo.cgi
461 my $conf = new FS::Conf;
462 if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) {
463 $templatename = "_$1";
468 my $filename = "logo$templatename.png";
470 return { 'error' => '',
471 'logo' => $conf->config_binary($filename),
472 'content_type' => 'image/png', #should allow gif, jpg too
479 my $session = _cache->get($p->{'session_id'})
480 or return { 'error' => "Can't resume session" }; #better error message
482 my $custnum = $session->{'custnum'};
484 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
485 or return { 'error' => "unknown custnum $custnum" };
487 my @cust_bill = $cust_main->cust_bill;
489 return { 'error' => '',
490 'invoices' => [ map { { 'invnum' => $_->invnum,
491 '_date' => $_->_date,
500 my $session = _cache->get($p->{'session_id'})
501 or return { 'error' => "Can't resume session" }; #better error message
503 my $custnum = $session->{'custnum'};
505 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
506 or return { 'error' => "unknown custnum $custnum" };
508 my @errors = $cust_main->cancel( 'quiet'=>1 );
510 my $error = scalar(@errors) ? join(' / ', @errors) : '';
512 return { 'error' => $error };
519 my($context, $session, $custnum) = _custoragent_session_custnum($p);
520 return { 'error' => $session } if $context eq 'error';
522 my $search = { 'custnum' => $custnum };
523 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
524 my $cust_main = qsearchs('cust_main', $search )
525 or return { 'error' => "unknown custnum $custnum" };
527 #return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
529 my $conf = new FS::Conf;
531 { 'svcnum' => $session->{'svcnum'},
532 'custnum' => $custnum,
533 'cust_pkg' => [ map {
537 [ map $_->hashref, $_->available_part_svc ],
539 [ map { my $ref = { $_->hash,
540 label => [ $_->label ],
542 $ref->{_password} = $_->svc_x->_password
543 if $context eq 'agent'
544 && $conf->exists('agent-showpasswords')
545 && $_->part_svc->svcdb eq 'svc_acct';
550 } $cust_main->ncancelled_pkgs
553 small_custview( $cust_main, $conf->config('countrydefault') ),
563 my($context, $session, $custnum) = _custoragent_session_custnum($p);
564 return { 'error' => $session } if $context eq 'error';
566 my $search = { 'custnum' => $custnum };
567 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
568 my $cust_main = qsearchs('cust_main', $search )
569 or return { 'error' => "unknown custnum $custnum" };
572 #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
573 foreach my $cust_pkg ( $cust_main->unsuspended_pkgs ) {
574 push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
576 @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
579 #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
583 #no#'svcnum' => $session->{'svcnum'},
584 'custnum' => $custnum,
586 my $svc_x = $_->svc_x;
587 my($label, $value) = $_->label;
589 { 'svcnum' => $_->svcnum,
592 'username' => $svc_x->username,
593 'email' => $svc_x->email,
606 my($context, $session, $custnum) = _custoragent_session_custnum($p);
607 return { 'error' => $session } if $context eq 'error';
609 my $search = { 'custnum' => $custnum };
610 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
611 my $cust_main = qsearchs('cust_main', $search )
612 or return { 'error' => "unknown custnum $custnum" };
614 #false laziness w/ClientAPI/Signup.pm
616 my $cust_pkg = new FS::cust_pkg ( {
617 'custnum' => $custnum,
618 'pkgpart' => $p->{'pkgpart'},
620 my $error = $cust_pkg->check;
621 return { 'error' => $error } if $error;
624 unless ( $p->{'svcpart'} eq 'none' ) {
628 if ( $p->{'svcpart'} =~ /^(\d+)$/ ) {
630 my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
631 return { 'error' => "Unknown svcpart $svcpart" } unless $part_svc;
632 $svcdb = $part_svc->svcdb;
636 $svcpart ||= $cust_pkg->part_pkg->svcpart($svcdb);
639 'svc_acct' => [ qw( username _password sec_phrase popnum ) ],
640 'svc_domain' => [ qw( domain ) ],
641 'svc_external' => [ qw( id title ) ],
644 my $svc_x = "FS::$svcdb"->new( {
645 'svcpart' => $svcpart,
646 map { $_ => $p->{$_} } @{$fields{$svcdb}}
649 if ( $svcdb eq 'svc_acct' ) {
652 while ( length($p->{"snarf_machine$snarfnum"}) ) {
653 my $acct_snarf = new FS::acct_snarf ( {
654 'machine' => $p->{"snarf_machine$snarfnum"},
655 'protocol' => $p->{"snarf_protocol$snarfnum"},
656 'username' => $p->{"snarf_username$snarfnum"},
657 '_password' => $p->{"snarf_password$snarfnum"},
660 push @acct_snarf, $acct_snarf;
662 $svc_x->child_objects( \@acct_snarf );
665 my $y = $svc_x->setdefault; # arguably should be in new method
666 return { 'error' => $y } if $y && !ref($y);
668 $error = $svc_x->check;
669 return { 'error' => $error } if $error;
676 tie my %hash, 'Tie::RefHash';
677 %hash = ( $cust_pkg => \@svc );
679 $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
680 return { 'error' => $error } if $error;
682 my $conf = new FS::Conf;
683 if ( $conf->exists('signup_server-realtime') ) {
685 my $old_balance = $cust_main->balance;
687 my $bill_error = $cust_main->bill;
688 $cust_main->apply_payments;
689 $cust_main->apply_credits;
690 $bill_error = $cust_main->collect('realtime' => 1);
692 if ( $cust_main->balance > $old_balance
693 && $cust_main->balance > 0
694 && $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/ ) {
695 #this makes sense. credit is "un-doing" the invoice
696 $cust_main->credit( sprintf("%.2f", $cust_main->balance - $old_balance ),
697 'self-service decline' );
698 $cust_main->apply_credits( 'order' => 'newest' );
700 $cust_pkg->cancel('quiet'=>1);
701 return { 'error' => '_decline', 'bill_error' => $bill_error };
710 return { error => '', pkgnum => $cust_pkg->pkgnum };
716 my $session = _cache->get($p->{'session_id'})
717 or return { 'error' => "Can't resume session" }; #better error message
719 my $custnum = $session->{'custnum'};
721 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
722 or return { 'error' => "unknown custnum $custnum" };
724 my $pkgnum = $p->{'pkgnum'};
726 my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
727 'pkgnum' => $pkgnum, } )
728 or return { 'error' => "unknown pkgnum $pkgnum" };
730 my $error = $cust_pkg->cancel( 'quiet'=>1 );
731 return { 'error' => $error };
738 return { 'error' => gettext('passwords_dont_match') }
739 if $p->{'_password'} ne $p->{'_password2'};
740 return { 'error' => gettext('empty_password') }
741 unless length($p->{'_password'});
743 _provision( 'FS::svc_acct',
744 [qw(username _password)],
745 [qw(username _password)],
751 sub provision_external {
753 #_provision( 'FS::svc_external', [qw(id title)], [qw(id title)], $p, @_ );
754 _provision( 'FS::svc_external',
763 my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4);
765 my($context, $session, $custnum) = _custoragent_session_custnum($p);
766 return { 'error' => $session } if $context eq 'error';
768 my $search = { 'custnum' => $custnum };
769 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
770 my $cust_main = qsearchs('cust_main', $search )
771 or return { 'error' => "unknown custnum $custnum" };
773 my $pkgnum = $p->{'pkgnum'};
775 my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
778 or return { 'error' => "unknown pkgnum $pkgnum" };
780 my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
781 or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
783 my $svc_x = $class->new( {
784 'pkgnum' => $p->{'pkgnum'},
785 'svcpart' => $p->{'svcpart'},
786 map { $_ => $p->{$_} } @$fields
788 my $error = $svc_x->insert;
789 $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum })
792 return { 'svc' => $part_svc->svc,
794 map { $_ => $svc_x->get($_) } @$return_fields
802 my($context, $session, $custnum) = _custoragent_session_custnum($p);
803 return { 'error' => $session } if $context eq 'error';
805 my $search = { 'custnum' => $custnum };
806 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
807 my $cust_main = qsearchs('cust_main', $search )
808 or return { 'error' => "unknown custnum $custnum" };
810 my $pkgnum = $p->{'pkgnum'};
812 my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
815 or return { 'error' => "unknown pkgnum $pkgnum" };
817 my $svcpart = $p->{'svcpart'};
819 my $pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $cust_pkg->pkgpart,
820 'svcpart' => $svcpart, } )
821 or return { 'error' => "unknown svcpart $svcpart for pkgnum $pkgnum" };
822 my $part_svc = $pkg_svc->part_svc;
824 my $conf = new FS::Conf;
827 'svc' => $part_svc->svc,
828 'svcdb' => $part_svc->svcdb,
830 'svcpart' => $svcpart,
831 'custnum' => $custnum,
833 'security_phrase' => 0, #XXX !
834 'svc_acct_pop' => [], #XXX !
836 'init_popstate' => '',
841 small_custview( $cust_main, $conf->config('countrydefault') ),
847 sub unprovision_svc {
850 my($context, $session, $custnum) = _custoragent_session_custnum($p);
851 return { 'error' => $session } if $context eq 'error';
853 my $search = { 'custnum' => $custnum };
854 $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
855 my $cust_main = qsearchs('cust_main', $search )
856 or return { 'error' => "unknown custnum $custnum" };
858 my $svcnum = $p->{'svcnum'};
860 my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svcnum, } )
861 or return { 'error' => "unknown svcnum $svcnum" };
863 return { 'error' => "Service $svcnum does not belong to customer $custnum" }
864 unless $cust_svc->cust_pkg->custnum == $custnum;
866 my $conf = new FS::Conf;
868 return { 'svc' => $cust_svc->part_svc->svc,
869 'error' => $cust_svc->cancel,
871 small_custview( $cust_main, $conf->config('countrydefault') ),
876 sub myaccount_passwd {
878 my($context, $session, $custnum) = _custoragent_session_custnum($p);
879 return { 'error' => $session } if $context eq 'error';
881 return { 'error' => "New passwords don't match." }
882 if $p->{'new_password'} ne $p->{'new_password2'};
884 return { 'error' => 'Enter new password' }
885 unless length($p->{'new_password'});
887 #my $search = { 'custnum' => $custnum };
888 #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
889 $custnum =~ /^(\d+)$/ or die "illegal custnum";
890 my $search = " AND custnum = $1";
891 $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
893 my $svc_acct = qsearchs( {
894 'table' => 'svc_acct',
895 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '.
896 'LEFT JOIN cust_pkg USING ( pkgnum ) '.
897 'LEFT JOIN cust_main USING ( custnum ) ',
898 'hashref' => { 'svcnum' => $p->{'svcnum'}, },
899 'extra_sql' => $search, #important
901 or return { 'error' => "Service not found" };
903 $svc_acct->_password($p->{'new_password'});
904 my $error = $svc_acct->replace();
906 my($label, $value) = $svc_acct->cust_svc->label;
908 return { 'error' => $error,
917 sub _custoragent_session_custnum {
920 my($context, $session, $custnum);
921 if ( $p->{'session_id'} ) {
923 $context = 'customer';
924 $session = _cache->get($p->{'session_id'})
925 or return ( 'error' => "Can't resume session" ); #better error message
926 $custnum = $session->{'custnum'};
928 } elsif ( $p->{'agent_session_id'} ) {
931 my $agent_cache = new FS::ClientAPI_SessionCache( {
932 'namespace' => 'FS::ClientAPI::Agent',
934 $session = $agent_cache->get($p->{'agent_session_id'})
935 or return ( 'error' => "Can't resume session" ); #better error message
936 $custnum = $p->{'custnum'};
939 return ( 'error' => "Can't resume session" ); #better error message
942 ($context, $session, $custnum);