self-service access for contacts, RT#25533
[freeside.git] / fs_selfservice / FS-SelfService / cgi / selfservice.cgi
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw($DEBUG $cgi $session_id $form_max $template_dir);
5 use subs qw(do_template);
6 use CGI;
7 use CGI::Carp qw(fatalsToBrowser);
8 use CGI::Cookie;
9 use Text::Template;
10 use HTML::Entities;
11 use Date::Format;
12 use Date::Parse 'str2time';
13 use Number::Format 1.50;
14 use FS::SelfService qw(
15   access_info login_info login customer_info edit_info invoice
16   payment_info process_payment realtime_collect process_prepay
17   list_pkgs order_pkg signup_info order_recharge
18   part_svc_info provision_acct provision_external provision_phone
19   unprovision_svc change_pkg suspend_pkg domainselector
20   list_svcs list_svc_usage list_cdr_usage list_support_usage
21   myaccount_passwd list_invoices create_ticket get_ticket did_report
22   adjust_ticket_priority
23   mason_comp port_graph
24   start_thirdparty finish_thirdparty
25   reset_passwd check_reset_passwd process_reset_passwd
26 );
27
28 $template_dir = '.';
29
30 $DEBUG = 0;
31
32 $form_max = 255;
33
34 $cgi = new CGI;
35 my %cookies = CGI::Cookie->fetch;
36
37 my $login_rv;
38
39 if ( exists($cookies{'session'}) ) {
40
41   $session_id = $cookies{'session'}->value;
42
43   if ( $session_id eq 'login' ) {
44     # then we've just come back from the login page
45
46     $cgi->param('password') =~ /^(.{0,$form_max})$/;
47     my $password = $1;
48
49     if ( $cgi->param('email') =~ /^\s*([a-z0-9_\-\.\@]{1,$form_max})\s*$/i ) {
50
51       my $email = $1;
52       $login_rv = login(
53         'email'    => $email,
54         'password' => $password
55       );
56       $session_id = $login_rv->{'session_id'};
57
58     } else {
59
60       $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i;
61       my $username = $1;
62
63       $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/;
64       my $domain = $1;
65
66       if ( $username and $domain and $password ) {
67
68         # authenticate
69         $login_rv = login(
70           'username' => $username,
71           'domain'   => $domain,
72           'password' => $password,
73         );
74         $session_id = $login_rv->{'session_id'};
75
76       } elsif ( $username or $domain or $password ) {
77       
78         my $error = 'Illegal '; #XXX localization...
79         my $count = 0;
80         if ( !$username ) {
81           $error .= 'username';
82           $count++;
83         }
84         if ( !$domain )  {
85           $error .= ', ' if $count;
86           $error .= 'domain';
87           $count++;
88         }
89         if ( !$password ) {
90           $error .= ', ' if $count;
91           $error .= 'and ' if $count > 1;
92           $error .= 'password';
93           $count++;
94         }
95         $error .= '.';
96         $login_rv = {
97           'username'  => $username,
98           'domain'    => $domain,
99           'password'  => $password,
100           'error'     => $error,
101         };
102         $session_id = undef; # attempt login again
103
104       }
105
106     } # else there was no input, so show no error message
107
108   } # else session_id ne 'login'
109
110 } else {
111   # there is no session cookie
112   $login_rv = {};
113 }
114
115 if ( !$session_id ) {
116   # XXX why are we getting agentnum from a CGI param? surely it should 
117   # be some kind of configuration option.
118   #
119   # show the login page
120   $session_id = 'login'; # set state
121   my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
122
123   do_template('login', { %$login_rv, %$login_info });
124   exit;
125 }
126
127 # at this point $session_id is a real session
128
129 #order|pw_list XXX ???
130 my @actions = ( qw(
131   myaccount
132   tktcreate
133   tktview
134   ticket_priority
135   didreport
136   invoices
137   view_invoice
138   make_payment
139   make_ach_payment
140   make_term_payment
141   make_thirdparty_payment
142   post_thirdparty_payment
143   finish_thirdparty_payment
144   cancel_thirdparty_payment
145   payment_results
146   ach_payment_results
147   recharge_prepay
148   recharge_results
149   logout
150   change_bill
151   change_ship
152   change_pay
153   process_change_bill
154   process_change_ship
155   process_change_pay
156   customer_order_pkg
157   process_order_pkg
158   customer_change_pkg
159   process_change_pkg
160   process_order_recharge
161   provision
162   provision_svc
163   process_svc_acct
164   process_svc_phone
165   process_svc_external
166   delete_svc
167   view_usage
168   view_usage_details
169   view_cdr_details
170   view_support_details
171   view_port_graph
172   real_port_graph
173   change_password
174   process_change_password
175   forgot_password
176   do_forgot_password
177   process_forgot_password
178   do_process_forgot_password
179   customer_suspend_pkg
180   process_suspend_pkg
181 ));
182
183 my $action = 'myaccount'; # sensible default
184 if ( $cgi->param('action') =~ /^(\w+)$/ ) {
185   if (grep {$_ eq $1} @actions) {
186     $action = $1;
187   } else {
188     warn "WARNING: unrecognized action '$1'\n";
189   }
190 }
191
192 warn "calling $action sub\n"
193   if $DEBUG;
194 $FS::SelfService::DEBUG = $DEBUG;
195 my $result = eval "&$action();";
196 die $@ if $@;
197
198 warn Dumper($result) if $DEBUG;
199
200 if ( $result->{error} && ( $result->{error} eq "Can't resume session"
201   || $result->{error} eq "Expired session") ) { #ick
202
203   $session_id = 'login';
204   my $login_info = login_info();
205   do_template('login', $login_info);
206   exit;
207 }
208
209 #warn $result->{'open_invoices'};
210 #warn scalar(@{$result->{'open_invoices'}});
211
212 warn "processing template $action\n"
213   if $DEBUG;
214 do_template($action, {
215   'session_id' => $session_id,
216   'action'     => $action, #so the menu knows what tab we're on...
217   #%{ payment_info( 'session_id' => $session_id ) },  # cust_paybys for the menu
218   %{$result}
219 });
220
221 #--
222
223 use Data::Dumper;
224 sub myaccount { 
225   customer_info( 'session_id' => $session_id ); 
226 }
227
228 sub change_bill { my $payment_info =
229                     payment_info( 'session_id' => $session_id );
230                   return $payment_info if ( $payment_info->{'error'} );
231                   my $customer_info =
232                     customer_info( 'session_id' => $session_id );
233                   return { 
234                     %$payment_info,
235                     %$customer_info,
236                   };
237                 }
238 sub change_ship { change_bill(@_); }
239 sub change_pay { change_bill(@_); }
240
241 sub _process_change_info { 
242   my ($erroraction, @fields) = @_;
243
244   my $results = '';
245
246   $results ||= edit_info (
247     'session_id' => $session_id,
248     map { ($_ => $cgi->param($_)) } grep { defined($cgi->param($_)) } @fields,
249   );
250
251
252   if ( $results->{'error'} ) {
253     no strict 'refs';
254     $action = $erroraction;
255     return {
256       $cgi->Vars,
257       %{&$action()},
258       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
259     };
260   } else {
261     return $results;
262   }
263 }
264
265 sub process_change_bill {
266         _process_change_info( 'change_bill', 
267           qw( first last company address1 address2 city state
268               county zip country daytime night fax )
269         );
270 }
271
272 sub process_change_ship {
273         my @list = map { "ship_$_" }
274                      qw( first last company address1 address2 city state
275                          county zip country daytime night fax 
276                        );
277         if ($cgi->param('same') eq 'Y') {
278           foreach (@list) { $cgi->param($_, '') }
279         }
280
281         _process_change_info( 'change_ship', @list );
282 }
283
284 sub process_change_pay {
285         my $postal = $cgi->param( 'postal_invoicing' );
286         my $payby  = $cgi->param( 'payby' );
287         my @list =
288           qw( payby payinfo payinfo1 payinfo2 month year payname
289               address1 address2 city county state zip country auto paytype
290               paystate ss stateid stateid_state invoicing_list
291             );
292         push @list, 'postal_invoicing' if $postal;
293         unless (    $payby ne 'BILL'
294                  || $postal
295                  || $cgi->param( 'invoicing_list' )
296                )
297         {
298           $action = 'change_pay';
299           return {
300             %{&change_pay()},
301             $cgi->Vars,
302             'error' => '<FONT COLOR="#FF0000">Postal or email required.</FONT>',
303           };
304         }
305         _process_change_info( 'change_pay', @list );
306 }
307
308 sub view_invoice {
309
310   $cgi->param('invnum') =~ /^(\d+)$/ or die "illegal invnum";
311   my $invnum = $1;
312
313   invoice( 'session_id' => $session_id,
314            'invnum'     => $invnum,
315          );
316
317 }
318
319 sub invoices {
320   list_invoices( 'session_id' => $session_id, );
321 }
322
323 sub tktcreate {
324   my $customer_info = customer_info( 'session_id' => $session_id );
325   return $customer_info if ( $customer_info->{'error'} );
326
327   my $requestor = "";
328   if ( $customer_info->{'invoicing_list'} ) {
329     my @requestor = split( /\s*\,\s*/, $customer_info->{'invoicing_list'} );
330     $requestor = $requestor[0] if scalar(@requestor);
331   }
332
333   return { 'requestor' => $requestor }
334     unless ($cgi->param('subject') && $cgi->param('message') &&
335         length($cgi->param('subject')) && length($cgi->param('message')));
336     
337  create_ticket( 'session_id' => $session_id,
338                         'subject' => $cgi->param('subject'),
339                         'message' => $cgi->param('message'), 
340                         'requestor' => $requestor,
341             );
342 }
343
344 sub tktview {
345  get_ticket(    'session_id' => $session_id,
346                 'ticket_id' => ($cgi->param('ticket_id') || ''),
347                 'subject'   => ($cgi->param('subject') || ''),
348                 'reply'     => ($cgi->param('reply') || ''),
349             );
350 }
351
352 sub ticket_priority {
353   my %values;
354   foreach ( $cgi->param ) {
355     if ( /^ticket(\d+)$/ ) {
356       # a 'ticket1001' param implies the existence of a 'priority1001' param
357       # but if that's empty, we need to send it as empty rather than forget
358       # it.
359       $values{$1} = $cgi->param("priority$1") || '';
360     }
361   }
362   $action = 'myaccount';
363   # this returns an updated customer_info for myaccount
364   adjust_ticket_priority( 'session_id' => $session_id,
365                           'values'     => \%values );
366 }
367
368 sub customer_order_pkg {
369   my $init_data = signup_info( 'customer_session_id' => $session_id );
370   return $init_data if ( $init_data->{'error'} );
371
372   my $customer_info = customer_info( 'session_id' => $session_id );
373   return $customer_info if ( $customer_info->{'error'} );
374
375   my $pkgselect = mason_comp(
376     'session_id' => $session_id,
377     'comp'       => '/edit/cust_main/first_pkg/select-part_pkg.html',
378     'args'       => [ 'password_verify' => 1,
379                       'onchange'        => 'enable_order_pkg()',
380                       'relurls'         => 1,
381                       'empty_label'     => 'Select package',
382                     ],
383   );
384
385   $pkgselect = $pkgselect->{'error'} || $pkgselect->{'output'};
386
387   return {
388     ( map { $_ => $init_data->{$_} }
389           qw( part_pkg security_phrase svc_acct_pop ),
390     ),
391     %$customer_info,
392     'pkg_selector' => $pkgselect,
393   };
394 }
395
396 sub customer_change_pkg {
397   my $init_data = signup_info( 'customer_session_id' => $session_id );
398   return $init_data if ( $init_data->{'error'} );
399
400   my $customer_info = customer_info( 'session_id' => $session_id );
401   return $customer_info if ( $customer_info->{'error'} );
402
403   return {
404     ( map { $_ => $init_data->{$_} }
405           qw( part_pkg security_phrase svc_acct_pop ),
406     ),
407     ( map { $_ => $cgi->param($_) }
408         qw( pkgnum pkg )
409     ),
410     %$customer_info,
411   };
412 }
413
414 sub process_order_pkg {
415
416   my $results = '';
417
418   my @params = (qw( custnum pkgpart ));
419   my $svcdb = '';
420   if ( $cgi->param('pkgpart_svcpart') =~ /^(\d+)_(\d+)$/ ) {
421     $cgi->param('pkgpart', $1);
422     $cgi->param('svcpart', $2);
423     push @params, 'svcpart';
424     $svcdb = $cgi->param('svcdb');
425     push @params, 'domsvc' if $svcdb eq 'svc_acct';
426   } else {
427     $svcdb = 'svc_acct';
428   }
429
430   if ( $svcdb eq 'svc_acct' ) {
431
432     push @params, qw( username _password _password2 sec_phrase popnum );
433
434     unless ( length($cgi->param('_password')) ) {
435       my $init_data = signup_info( 'customer_session_id' => $session_id );
436       $results = { 'error' => $init_data->{msgcat}{empty_password} };
437       $results = { 'error' => $init_data->{error} } if($init_data->{error});
438     }
439     if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
440       my $init_data = signup_info( 'customer_session_id' => $session_id );
441       $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
442       $results = { 'error' => $init_data->{error} } if($init_data->{error});
443       $cgi->param('_password', '');
444       $cgi->param('_password2', '');
445     }
446
447   } elsif ( $svcdb eq 'svc_phone' ) {
448
449     push @params, qw( phonenum sip_password pin phone_name );
450
451   } else {
452     die "$svcdb not handled on process_order_pkg yet";
453   }
454
455   $results ||= order_pkg (
456     'session_id' => $session_id,
457     map { $_ => $cgi->param($_) } @params
458   );
459
460
461   if ( $results->{'error'} ) {
462     $action = 'customer_order_pkg';
463     return {
464       $cgi->Vars,
465       %{customer_order_pkg()},
466       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
467     };
468   } else {
469     return $results;
470   }
471
472 }
473
474 sub process_change_pkg {
475
476   my $results = '';
477
478   $results ||= change_pkg (
479     'session_id' => $session_id,
480     map { $_ => $cgi->param($_) }
481         qw( pkgpart pkgnum )
482   );
483
484
485   if ( $results->{'error'} ) {
486     $action = 'customer_change_pkg';
487     return {
488       $cgi->Vars,
489       %{customer_change_pkg()},
490       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
491     };
492   } else {
493     return $results;
494   }
495
496 }
497
498 sub process_suspend_pkg {
499   my $results = '';
500   $results = suspend_pkg (
501     'session_id' => $session_id,
502     map { $_ => $cgi->param($_) } 
503       qw( pkgnum )
504     );
505   if ( $results->{'error'} ) {
506     $action = 'provision';
507     return {
508       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
509     }
510   }
511   else {
512     return $results;
513   }
514 }
515
516 sub process_order_recharge {
517
518   my $results = '';
519
520   $results ||= order_recharge (
521     'session_id' => $session_id,
522     map { $_ => $cgi->param($_) }
523         qw( svcnum )
524   );
525
526
527   if ( $results->{'error'} ) {
528     $action = 'view_usage';
529     if ($results->{'error'} eq '_decline') {
530       $results->{'error'} = "There has been an error processing your account.  Please contact customer support."
531     }
532     return {
533       $cgi->Vars,
534       %{view_usage()},
535       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
536     };
537   } else {
538     return $results;
539   }
540
541 }
542
543 sub make_payment {
544
545   my $payment_info = payment_info( 'session_id' => $session_id );
546
547   my $tr_amount_fee = mason_comp(
548     'session_id' => $session_id,
549     'comp'       => '/elements/tr-amount_fee.html',
550     'args'       => [ 'amount' => $payment_info->{'balance'},
551                     ],
552   );
553
554   $tr_amount_fee = $tr_amount_fee->{'error'} || $tr_amount_fee->{'output'};
555
556   $payment_info->{'tr_amount_fee'} = $tr_amount_fee;
557
558   $payment_info;
559 }
560
561 sub payment_results {
562
563   use Business::CreditCard 0.30;
564
565   #we should only do basic checking here for DoS attacks and things
566   #that couldn't be constructed by the web form...  let process_payment() do
567   #the rest, it gives better error messages
568
569   $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
570     or die "Illegal amount: ". $cgi->param('amount'); #!!!
571   my $amount = $1;
572
573   my $payinfo = $cgi->param('payinfo');
574   $payinfo =~ s/[^\dx]//g;
575   $payinfo =~ /^([\dx]{13,16}|[\dx]{8,9})$/
576     #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
577     or die "illegal card"; #!!!
578   $payinfo = $1;
579   unless ( $payinfo =~ /x/ ) {
580     validate($payinfo)
581       #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
582       or die "invalid card"; #!!!
583   }
584
585   if ( $cgi->param('card_type') ) {
586     cardtype($payinfo) eq $cgi->param('card_type')
587       #or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
588       or die "not a ". $cgi->param('card_type');
589   }
590
591   $cgi->param('paycvv') =~ /^\s*(.{0,4})\s*$/ or die "illegal CVV2";
592   my $paycvv = $1;
593
594   $cgi->param('month') =~ /^(\d{2})$/ or die "illegal month";
595   my $month = $1;
596   $cgi->param('year') =~ /^(\d{4})$/ or die "illegal year";
597   my $year = $1;
598
599   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
600   my $payname = $1;
601
602   $cgi->param('address1') =~ /^(.{0,80})$/ or die "illegal address1";
603   my $address1 = $1;
604
605   $cgi->param('address2') =~ /^(.{0,80})$/ or die "illegal address2";
606   my $address2 = $1;
607
608   $cgi->param('city') =~ /^(.{0,80})$/ or die "illegal city";
609   my $city = $1;
610
611   $cgi->param('state') =~ /^(.{0,80})$/ or die "illegal state";
612   my $state = $1;
613
614   $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
615   my $zip = $1;
616
617   $cgi->param('country') =~ /^(.{0,2})$/ or die "illegal country";
618   my $country = $1;
619
620   my $save = 0;
621   $save = 1 if $cgi->param('save');
622
623   my $auto = 0;
624   $auto = 1 if $cgi->param('auto');
625
626   $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
627   my $paybatch = $1;
628
629   $cgi->param('discount_term') =~ /^(\d*)$/ or die "illegal discount_term";
630   my $discount_term = $1;
631
632
633   process_payment(
634     'session_id' => $session_id,
635     'payby'      => 'CARD',
636     'amount'     => $amount,
637     'payinfo'    => $payinfo,
638     'paycvv'     => $paycvv,
639     'month'      => $month,
640     'year'       => $year,
641     'payname'    => $payname,
642     'address1'   => $address1,
643     'address2'   => $address2,
644     'city'       => $city,
645     'state'      => $state,
646     'zip'        => $zip,
647     'country'    => $country,
648     'save'       => $save,
649     'auto'       => $auto,
650     'paybatch'   => $paybatch,
651     'discount_term' => $discount_term,
652   );
653
654 }
655
656 sub make_ach_payment {
657   payment_info( 'session_id' => $session_id );
658 }
659
660 sub ach_payment_results {
661
662   #we should only do basic checking here for DoS attacks and things
663   #that couldn't be constructed by the web form...  let process_payment() do
664   #the rest, it gives better error messages
665
666   $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
667     or die "illegal amount"; #!!!
668   my $amount = $1;
669
670   my $payinfo1 = $cgi->param('payinfo1');
671   $payinfo1 =~ s/[^\dx]//g;
672   $payinfo1 =~ /^([\dx]+)$/
673     or die "illegal account"; #!!!
674   $payinfo1 = $1;
675
676   my $payinfo2 = $cgi->param('payinfo2');
677   $payinfo2 =~ s/[^\dx]//g;
678   $payinfo2 =~ /^([\dx]+)$/
679     or die "illegal ABA/routing code"; #!!!
680   $payinfo2 = $1;
681
682   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
683   my $payname = $1;
684
685   $cgi->param('paystate') =~ /^(.{0,2})$/ or die "illegal paystate";
686   my $paystate = $1;
687
688   $cgi->param('paytype') =~ /^(.{0,80})$/ or die "illegal paytype";
689   my $paytype = $1;
690
691   $cgi->param('ss') =~ /^(.{0,80})$/ or die "illegal ss";
692   my $ss = $1;
693
694   $cgi->param('stateid') =~ /^(.{0,80})$/ or die "illegal stateid";
695   my $stateid = $1;
696
697   $cgi->param('stateid_state') =~ /^(.{0,2})$/ or die "illegal stateid_state";
698   my $stateid_state = $1;
699
700   my $save = 0;
701   $save = 1 if $cgi->param('save');
702
703   my $auto = 0;
704   $auto = 1 if $cgi->param('auto');
705
706   $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
707   my $paybatch = $1;
708
709   process_payment(
710     'session_id' => $session_id,
711     'payby'      => 'CHEK',
712     'amount'     => $amount,
713     'payinfo1'   => $payinfo1,
714     'payinfo2'   => $payinfo2,
715     'month'      => '12',
716     'year'       => '2037',
717     'payname'    => $payname,
718     'paytype'    => $paytype,
719     'paystate'   => $paystate,
720     'ss'         => $ss,
721     'stateid'    => $stateid,
722     'stateid_state' => $stateid_state,
723     'save'       => $save,
724     'auto'       => $auto,
725     'paybatch'   => $paybatch,
726   );
727
728 }
729
730 sub make_thirdparty_payment {
731   my $payment_info = payment_info('session_id' => $session_id);
732   $cgi->param('payby_method') =~ /^(CC|ECHECK|PAYPAL)$/
733     or die "illegal payby method";
734   $payment_info->{'payby_method'} = $1;
735   $payment_info->{'error'} = $cgi->param('error');
736
737   $payment_info;
738 }
739
740 sub post_thirdparty_payment {
741   $cgi->param('payby_method') =~ /^(CC|ECHECK|PAYPAL)$/
742     or die "illegal payby method";
743   my $method = $1;
744   $cgi->param('amount') =~ /^(\d+(\.\d*)?)$/
745     or die "illegal amount";
746   my $amount = $1;
747   my $result = start_thirdparty(
748     'session_id' => $session_id,
749     'method' => $method, 
750     'amount' => $amount,
751   );
752   if ( $result->{error} ) {
753     $cgi->param('action', 'make_thirdparty_payment');
754     $cgi->param('error', $result->{error});
755     print $cgi->redirect( $cgi->self_url );
756     exit;
757   }
758
759   $result;
760 }
761
762 sub finish_thirdparty_payment {
763   my %param = $cgi->Vars;
764   finish_thirdparty( 'session_id' => $session_id, %param );
765   # result contains either 'error' => error message, or the payment details
766 }
767
768 sub cancel_thirdparty_payment {
769   $action = 'make_thirdparty_payment';
770   finish_thirdparty( 'session_id' => $session_id, '_cancel' => 1 );
771 }
772
773 sub make_term_payment {
774   $cgi->param('amount') =~ /^(\d+\.\d{2})$/
775     or die "illegal payment amount";
776   my $balance = $1;
777   $cgi->param('discount_term') =~ /^(\d+)$/
778     or die "illegal discount term";
779   my $discount_term = $1;
780   $action = 'make_payment';
781   ({ %{payment_info( 'session_id' => $session_id )},
782     'balance' => $balance,
783     'discount_term' => $discount_term,
784   })
785 }
786
787 sub recharge_prepay {
788   customer_info( 'session_id' => $session_id );
789 }
790
791 sub recharge_results {
792
793   my $prepaid_cardnum = $cgi->param('prepaid_cardnum');
794   $prepaid_cardnum =~ s/\W//g;
795   $prepaid_cardnum =~ /^(\w*)$/ or die "illegal prepaid card number";
796   $prepaid_cardnum = $1;
797
798   process_prepay ( 'session_id'     => $session_id,
799                    'prepaid_cardnum' => $prepaid_cardnum,
800                  );
801 }
802
803 sub logout {
804   FS::SelfService::logout( 'session_id' => $session_id );
805 }
806
807 sub didreport {
808   my $result = did_report( 'session_id' => $session_id, 
809             'format' => $cgi->param('type'),
810             'recentonly' => $cgi->param('recentonly'),
811         );
812   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
813   $result;
814 }
815
816 sub provision {
817   my $result = list_pkgs( 'session_id' => $session_id );
818   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
819   $result->{'pkgpart'} = $cgi->param('pkgpart') if $cgi->param('pkgpart');
820   $result->{'filter'} = $cgi->param('filter') if $cgi->param('filter');
821   $result;
822 }
823
824 sub provision_svc {
825
826   my $result = part_svc_info(
827     'session_id' => $session_id,
828     map { $_ => $cgi->param($_) } qw( pkgnum svcpart svcnum ),
829   );
830   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
831
832   $result->{'svcdb'} =~ /^svc_(.*)$/
833     #or return { 'error' => 'Unknown svcdb '. $result->{'svcdb'} };
834     or die 'Unknown svcdb '. $result->{'svcdb'};
835   $action .= "_$1";
836
837   $result->{'numavail'} = $cgi->param('numavail');
838   $result->{'lnp'} = $cgi->param('lnp');
839
840   $result;
841 }
842
843 sub process_svc_phone {
844     my @bulkdid = $cgi->param('bulkdid');
845     my $phonenum = $cgi->param('phonenum');
846     my $lnp = $cgi->param('lnp');
847
848     my $result;
849     if($lnp) {
850         $result = provision_phone (
851             'session_id' => $session_id,
852             'countrycode' => '1',
853              map { $_ => $cgi->param($_) } qw( pkgnum svcpart phonenum 
854                 lnp_desired_due_date lnp_other_provider 
855                 lnp_other_provider_account )
856         );
857     } else {
858         $result = provision_phone (
859             'session_id' => $session_id,
860             'bulkdid' => [ @bulkdid ],
861             'countrycode' => '1',
862              map { $_ => $cgi->param($_) } qw( pkgnum svcpart phonenum svcnum email forwarddst )
863         );
864     }
865
866     if ( exists $result->{'error'} && $result->{'error'} ) { 
867         $action = 'provision_svc_phone';
868         return {
869           $cgi->Vars,
870           %{ part_svc_info( 'session_id' => $session_id,
871                         map { $_ => $cgi->param($_) } qw( pkgnum svcpart svcnum )
872               )
873           },
874           'error' => $result->{'error'},
875         };
876   }
877
878   $result;
879 }
880
881 sub process_svc_acct {
882
883   my $result = provision_acct (
884     'session_id' => $session_id,
885     map { $_ => $cgi->param($_) } qw(
886       pkgnum svcpart username domsvc _password _password2 sec_phrase popnum )
887   );
888
889   if ( exists $result->{'error'} && $result->{'error'} ) { 
890     #warn "$result $result->{'error'}"; 
891     $action = 'provision_svc_acct';
892     return {
893       $cgi->Vars,
894       %{ part_svc_info( 'session_id' => $session_id,
895                         map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
896                       )
897       },
898       'error' => $result->{'error'},
899     };
900   } else {
901     #warn "$result $result->{'error'}"; 
902     return $result;
903   }
904
905 }
906
907 sub process_svc_external {
908   provision_external (
909     'session_id' => $session_id,
910     map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
911   );
912 }
913
914 sub delete_svc {
915   unprovision_svc(
916     'session_id' => $session_id,
917     'svcnum'     => $cgi->param('svcnum'),
918   );
919 }
920
921 sub view_usage {
922   list_svcs(
923     'session_id'  => $session_id,
924     'svcdb'       => [ 'svc_acct', 'svc_phone', 'svc_port', ],
925     'ncancelled'  => 1,
926   );
927 }
928
929 sub real_port_graph {
930     my $svcnum = $cgi->param('svcnum');
931     my $res = port_graph(
932             'session_id'  => $session_id,
933             'svcnum'      => $svcnum,
934             'beginning'   => str2time($cgi->param('start')." 00:00:00"),
935             'ending'      => str2time($cgi->param('end')  ." 23:59:59"),
936             );
937     my @usage = @{$res->{'usage'}};
938     my $png = $usage[0]->{'png'};
939     { 'content' => $png, 'format' => 'png' };
940 }
941
942 sub view_port_graph {
943     my $svcnum = $cgi->param('svcnum');
944     { 'svcnum' => $svcnum,
945       'start' => $cgi->param($svcnum.'_start'),
946       'end' => $cgi->param($svcnum.'_end'),
947     }
948 }
949
950 sub view_usage_details {
951       list_svc_usage(
952         'session_id'  => $session_id,
953         'svcnum'      => $cgi->param('svcnum'),
954         'beginning'   => $cgi->param('beginning') || '',
955         'ending'      => $cgi->param('ending') || '',
956       );
957 }
958
959 sub view_cdr_details {
960   list_cdr_usage(
961     'session_id'  => $session_id,
962     'svcnum'      => $cgi->param('svcnum'),
963     'beginning'   => $cgi->param('beginning') || '',
964     'ending'      => $cgi->param('ending') || '',
965     'inbound'     => $cgi->param('inbound') || 0,
966   );
967 }
968
969 sub view_support_details {
970   list_support_usage(
971     'session_id'  => $session_id,
972     'svcnum'      => $cgi->param('svcnum'),
973     'beginning'   => $cgi->param('beginning') || '',
974     'ending'      => $cgi->param('ending') || '',
975   );
976 }
977
978 sub change_password {
979   list_svcs(
980     'session_id' => $session_id,
981     'svcdb'      => 'svc_acct',
982   );
983 };
984
985 sub process_change_password {
986
987   my $result = myaccount_passwd(
988     'session_id'    => $session_id,
989     map { $_ => $cgi->param($_) } qw( svcnum new_password new_password2 )
990   );
991
992   if ( exists $result->{'error'} && $result->{'error'} ) { 
993
994     $action = 'change_password';
995     return {
996       $cgi->Vars,
997       %{ list_svcs( 'session_id' => $session_id,
998                     'svcdb'      => 'svc_acct',
999                   )
1000        },
1001       #'svcnum' => $cgi->param('svcnum'),
1002       'error'  => $result->{'error'}
1003     };
1004
1005  } else {
1006
1007    return $result;
1008
1009  }
1010
1011 }
1012
1013 sub forgot_password {
1014   login_info( 'agentnum' => scalar($cgi->param('agentnum')) ); #skin_info
1015 }
1016
1017 sub do_forgot_password {
1018   reset_passwd(
1019     map { $_ => scalar($cgi->param($_)) }
1020       qw( email username domain )
1021   );
1022 }
1023
1024 sub process_forgot_password {
1025   check_reset_passwd(
1026     map { $_ => scalar($cgi->param($_)) }
1027       qw( session_id )
1028   );
1029 }
1030
1031 sub do_process_forgot_password {
1032   process_reset_passwd(
1033     map { $_ => scalar($cgi->param($_)) }
1034       qw( session_id new_password new_password2 )
1035   );
1036 }
1037
1038 #--
1039
1040 sub do_template {
1041   my $name = shift;
1042   my $fill_in = shift;
1043
1044   $cgi->delete_all();
1045   $fill_in->{'selfurl'} = $cgi->self_url;
1046   $fill_in->{'cgi'} = \$cgi;
1047   $fill_in->{'error'} = $cgi->param('error') if $cgi->param('error');
1048
1049   my $access_info = ($session_id and $session_id ne 'login')
1050                       ? access_info( 'session_id' => $session_id )
1051                       : {};
1052   $fill_in->{$_} = $access_info->{$_} foreach keys %$access_info;
1053
1054   # update the user's authentication
1055   my $timeout = $access_info->{'timeout'} || '60';
1056   my $cookie = CGI::Cookie->new('-name'     => 'session',
1057                                 '-value'    => $session_id,
1058                                 '-expires'  => '+'.$timeout,
1059                                 #'-secure'   => 1, # would be a good idea...
1060                                );
1061   if ( $name eq 'logout' ) {
1062     $cookie->expires(0);
1063   }
1064
1065   if ( $fill_in->{'format'} ) {
1066     # then override content-type, and return $fill_in->{'content'} instead
1067     # of filling in a template
1068     if ( $fill_in->{'format'} eq 'csv') {
1069       print $cgi->header('-expires' => 'now',
1070         '-Content-Type' => 'text/csv',
1071         '-Content-Disposition' => "attachment;filename=output.csv",
1072       );
1073     } elsif ( $fill_in->{'format'} eq 'xls' ) {
1074       print $cgi->header('-expires' => 'now',
1075         '-Content-Type' => 'application/vnd.ms-excel',
1076         '-Content-Disposition' => "attachment;filename=output.xls",
1077         '-Content-Length' => length($fill_in->{'content'}),
1078       );
1079     } elsif ( $fill_in->{'format'} eq 'png' ) {
1080       print $cgi->header('-expires' => 'now',
1081         '-Content-Type' => 'image/png',
1082       );
1083     }
1084     print $fill_in->{'content'};
1085   } else { # the usual case
1086     my $source = "$template_dir/$name.html";
1087     my $template = new Text::Template(
1088       TYPE       => 'FILE',
1089       SOURCE     => $source,
1090       DELIMITERS => [ '<%=', '%>' ],
1091       UNTAINT    => 1,
1092     )
1093       or die $Text::Template::ERROR;
1094
1095     my $data = $template->fill_in( 
1096       PACKAGE => 'FS::SelfService::_selfservicecgi',
1097       HASH    => $fill_in,
1098     ) || "Error processing template $source"; # at least print _something_
1099     print $cgi->header( '-cookie' => $cookie,
1100                         '-expires' => 'now' );
1101     print $data;
1102   }
1103 }
1104
1105 #*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
1106
1107 package FS::SelfService::_selfservicecgi;
1108
1109 use HTML::Entities;
1110 use FS::SelfService qw(
1111     regionselector popselector domainselector location_form didselector
1112 );
1113
1114 #false laziness w/agent.cgi
1115 use vars qw(@INCLUDE_ARGS);
1116 sub include {
1117   my $name = shift;
1118
1119   @INCLUDE_ARGS = @_;
1120
1121   my $template = new Text::Template( TYPE   => 'FILE',
1122                                      SOURCE => "$main::template_dir/$name.html",
1123                                      DELIMITERS => [ '<%=', '%>' ],
1124                                      UNTAINT => 1,                   
1125                                    )
1126     or die $Text::Template::ERROR;
1127
1128   $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
1129                       #HASH    => $fill_in
1130                     );
1131
1132 }
1133
1134