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