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