change service, billing, and payment info in selfservice
[freeside.git] / fs_selfservice / FS-SelfService / cgi / selfservice.cgi
1 #!/usr/bin/perl -Tw
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 Text::Template;
9 use HTML::Entities;
10 use Date::Format;
11 use Number::Format 1.50;
12 use FS::SelfService qw( login customer_info edit_info invoice
13                         payment_info process_payment 
14                         process_prepay
15                         list_pkgs order_pkg signup_info order_recharge
16                         part_svc_info provision_acct provision_external
17                         unprovision_svc change_pkg domainselector
18                         list_svcs list_svc_usage list_support_usage
19                         myaccount_passwd
20                       );
21
22 $template_dir = '.';
23
24 $DEBUG = 1;
25
26 $form_max = 255;
27
28 $cgi = new CGI;
29
30 unless ( defined $cgi->param('session') ) {
31   do_template('login',{});
32   exit;
33 }
34
35 if ( $cgi->param('session') eq 'login' ) {
36
37   $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i
38     or die "illegal username";
39   my $username = $1;
40
41   $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/
42     or die "illegal domain";
43   my $domain = $1;
44
45   $cgi->param('password') =~ /^(.{0,$form_max})$/
46     or die "illegal password";
47   my $password = $1;
48
49   my $rv = login(
50     'username' => $username,
51     'domain'   => $domain,
52     'password' => $password,
53   );
54   if ( $rv->{error} ) {
55     do_template('login', {
56       'error'    => $rv->{error},
57       'username' => $username,
58       'domain'   => $domain,
59     } );
60     exit;
61   } else {
62     $cgi->param('session' => $rv->{session_id} );
63     $cgi->param('action'  => 'myaccount' );
64   }
65 }
66
67 $session_id = $cgi->param('session');
68
69 #order|pw_list XXX ???
70 $cgi->param('action') =~
71     /^(myaccount|view_invoice|make_payment|make_ach_payment|payment_results|ach_payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|change_pay|process_change_bill|process_change_ship|process_change_pay|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|view_support_details|change_password|process_change_password)$/
72   or die "unknown action ". $cgi->param('action');
73 my $action = $1;
74
75 warn "calling $action sub\n"
76   if $DEBUG;
77 $FS::SelfService::DEBUG = $DEBUG;
78 my $result = eval "&$action();";
79 die $@ if $@;
80
81 if ( $result->{error} eq "Can't resume session"
82   || $result->{error} eq "Expired session" ) { #ick
83
84   do_template('login',{});
85   exit;
86 }
87
88 #warn $result->{'open_invoices'};
89 #warn scalar(@{$result->{'open_invoices'}});
90
91 warn "processing template $action\n"
92   if $DEBUG;
93 do_template($action, {
94   'session_id' => $session_id,
95   'action'     => $action, #so the menu knows what tab we're on...
96   %{$result}
97 });
98
99 #--
100
101 sub myaccount { customer_info( 'session_id' => $session_id ); }
102
103 sub change_bill { my $payment_info =
104                     payment_info( 'session_id' => $session_id );
105                   return $payment_info if ( $payment_info->{'error'} );
106                   my $customer_info =
107                     customer_info( 'session_id' => $session_id );
108                   return { 
109                     %$payment_info,
110                     %$customer_info,
111                   };
112                 }
113 sub change_ship { change_bill(@_); }
114 sub change_pay { change_bill(@_); }
115
116 sub _process_change_info { 
117   my ($erroraction, @fields) = @_;
118
119   my $results = '';
120
121   $results ||= edit_info (
122     'session_id' => $session_id,
123     map { ($_ => $cgi->param($_)) } grep { defined($cgi->param($_)) } @fields,
124   );
125
126
127   if ( $results->{'error'} ) {
128     no strict 'refs';
129     $action = $erroraction;
130     return {
131       $cgi->Vars,
132       %{&$action()},
133       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
134     };
135   } else {
136     return $results;
137   }
138 }
139
140 sub process_change_bill {
141         _process_change_info( 'change_bill', 
142           qw( first last company address1 address2 city state
143               county state zip country daytime night fax )
144         );
145 }
146
147 sub process_change_ship {
148         my @list = map { "ship_$_" }
149                      qw( first last company address1 address2 city state
150                          county zip country daytime night fax 
151                        );
152         if ($cgi->param('same') eq 'Y') {
153           foreach (@list) { $cgi->param($_, '') }
154         }
155
156         _process_change_info( 'change_ship', @list );
157 }
158
159 sub process_change_pay {
160         _process_change_info( 'change_pay', 
161           qw( payby payinfo payinfo1 payinfo2 month year payname
162               address1 address2 city county state zip country auto paytype
163               paystate ss stateid stateid_state )
164         );
165 }
166
167 sub view_invoice {
168
169   $cgi->param('invnum') =~ /^(\d+)$/ or die "illegal invnum";
170   my $invnum = $1;
171
172   invoice( 'session_id' => $session_id,
173            'invnum'     => $invnum,
174          );
175
176 }
177
178 sub customer_order_pkg {
179   my $init_data = signup_info( 'customer_session_id' => $session_id );
180   return $init_data if ( $init_data->{'error'} );
181
182   my $customer_info = customer_info( 'session_id' => $session_id );
183   return $customer_info if ( $customer_info->{'error'} );
184
185   return {
186     ( map { $_ => $init_data->{$_} }
187           qw( part_pkg security_phrase svc_acct_pop ),
188     ),
189     %$customer_info,
190   };
191 }
192
193 sub customer_change_pkg {
194   my $init_data = signup_info( 'customer_session_id' => $session_id );
195   return $init_data if ( $init_data->{'error'} );
196
197   my $customer_info = customer_info( 'session_id' => $session_id );
198   return $customer_info if ( $customer_info->{'error'} );
199
200   return {
201     ( map { $_ => $init_data->{$_} }
202           qw( part_pkg security_phrase svc_acct_pop ),
203     ),
204     ( map { $_ => $cgi->param($_) }
205         qw( pkgnum pkg )
206     ),
207     %$customer_info,
208   };
209 }
210
211 sub process_order_pkg {
212
213   my $results = '';
214
215   unless ( length($cgi->param('_password')) ) {
216     my $init_data = signup_info( 'customer_session_id' => $session_id );
217     $results = { 'error' => $init_data->{msgcat}{empty_password} };
218     $results = { 'error' => $init_data->{error} } if($init_data->{error});
219   }
220   if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
221     my $init_data = signup_info( 'customer_session_id' => $session_id );
222     $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
223     $results = { 'error' => $init_data->{error} } if($init_data->{error});
224     $cgi->param('_password', '');
225     $cgi->param('_password2', '');
226   }
227
228   $results ||= order_pkg (
229     'session_id' => $session_id,
230     map { $_ => $cgi->param($_) }
231         qw( custnum pkgpart username _password _password2 sec_phrase popnum )
232   );
233
234
235   if ( $results->{'error'} ) {
236     $action = 'customer_order_pkg';
237     return {
238       $cgi->Vars,
239       %{customer_order_pkg()},
240       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
241     };
242   } else {
243     return $results;
244   }
245
246 }
247
248 sub process_change_pkg {
249
250   my $results = '';
251
252   $results ||= change_pkg (
253     'session_id' => $session_id,
254     map { $_ => $cgi->param($_) }
255         qw( pkgpart pkgnum )
256   );
257
258
259   if ( $results->{'error'} ) {
260     $action = 'customer_change_pkg';
261     return {
262       $cgi->Vars,
263       %{customer_change_pkg()},
264       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
265     };
266   } else {
267     return $results;
268   }
269
270 }
271
272 sub process_order_recharge {
273
274   my $results = '';
275
276   $results ||= order_recharge (
277     'session_id' => $session_id,
278     map { $_ => $cgi->param($_) }
279         qw( svcnum )
280   );
281
282
283   if ( $results->{'error'} ) {
284     $action = 'view_usage';
285     if ($results->{'error'} eq '_decline') {
286       $results->{'error'} = "There has been an error processing your account.  Please contact customer support."
287     }
288     return {
289       $cgi->Vars,
290       %{view_usage()},
291       'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
292     };
293   } else {
294     return $results;
295   }
296
297 }
298
299 sub make_payment {
300   payment_info( 'session_id' => $session_id );
301 }
302
303 sub payment_results {
304
305   use Business::CreditCard;
306
307   #we should only do basic checking here for DoS attacks and things
308   #that couldn't be constructed by the web form...  let process_payment() do
309   #the rest, it gives better error messages
310
311   $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
312     or die "Illegal amount: ". $cgi->param('amount'); #!!!
313   my $amount = $1;
314
315   my $payinfo = $cgi->param('payinfo');
316   $payinfo =~ s/\D//g;
317   $payinfo =~ /^(\d{13,16})$/
318     #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
319     or die "illegal card"; #!!!
320   $payinfo = $1;
321   validate($payinfo)
322     #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
323     or die "invalid card"; #!!!
324
325   if ( $cgi->param('card_type') ) {
326     cardtype($payinfo) eq $cgi->param('card_type')
327       #or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
328       or die "not a ". $cgi->param('card_type');
329   }
330
331   $cgi->param('paycvv') =~ /^\s*(.{0,4})\s*$/ or die "illegal CVV2";
332   my $paycvv = $1;
333
334   $cgi->param('month') =~ /^(\d{2})$/ or die "illegal month";
335   my $month = $1;
336   $cgi->param('year') =~ /^(\d{4})$/ or die "illegal year";
337   my $year = $1;
338
339   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
340   my $payname = $1;
341
342   $cgi->param('address1') =~ /^(.{0,80})$/ or die "illegal address1";
343   my $address1 = $1;
344
345   $cgi->param('address2') =~ /^(.{0,80})$/ or die "illegal address2";
346   my $address2 = $1;
347
348   $cgi->param('city') =~ /^(.{0,80})$/ or die "illegal city";
349   my $city = $1;
350
351   $cgi->param('state') =~ /^(.{2})$/ or die "illegal state";
352   my $state = $1;
353
354   $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
355   my $zip = $1;
356
357   my $save = 0;
358   $save = 1 if $cgi->param('save');
359
360   my $auto = 0;
361   $auto = 1 if $cgi->param('auto');
362
363   $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
364   my $paybatch = $1;
365
366   process_payment(
367     'session_id' => $session_id,
368     'payby'      => 'CARD',
369     'amount'     => $amount,
370     'payinfo'    => $payinfo,
371     'paycvv'     => $paycvv,
372     'month'      => $month,
373     'year'       => $year,
374     'payname'    => $payname,
375     'address1'   => $address1,
376     'address2'   => $address2,
377     'city'       => $city,
378     'state'      => $state,
379     'zip'        => $zip,
380     'save'       => $save,
381     'auto'       => $auto,
382     'paybatch'   => $paybatch,
383   );
384
385 }
386
387 sub make_ach_payment {
388   payment_info( 'session_id' => $session_id );
389 }
390
391 sub ach_payment_results {
392
393   #we should only do basic checking here for DoS attacks and things
394   #that couldn't be constructed by the web form...  let process_payment() do
395   #the rest, it gives better error messages
396
397   $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
398     or die "illegal amount"; #!!!
399   my $amount = $1;
400
401   my $payinfo1 = $cgi->param('payinfo1');
402   $payinfo1=~ /^(\d+)$/
403     or die "illegal account"; #!!!
404   $payinfo1= $1;
405
406   my $payinfo2 = $cgi->param('payinfo2');
407   $payinfo2=~ /^(\d+)$/
408     or die "illegal ABA/routing code"; #!!!
409   $payinfo2= $1;
410
411   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
412   my $payname = $1;
413
414   $cgi->param('paystate') =~ /^(.{0,2})$/ or die "illegal paystate";
415   my $paystate = $1;
416
417   $cgi->param('paytype') =~ /^(.{0,80})$/ or die "illegal paytype";
418   my $paytype = $1;
419
420   $cgi->param('ss') =~ /^(.{0,80})$/ or die "illegal ss";
421   my $ss = $1;
422
423   $cgi->param('stateid') =~ /^(.{0,80})$/ or die "illegal stateid";
424   my $stateid = $1;
425
426   $cgi->param('stateid_state') =~ /^(.{0,2})$/ or die "illegal stateid_state";
427   my $stateid_state = $1;
428
429   my $save = 0;
430   $save = 1 if $cgi->param('save');
431
432   my $auto = 0;
433   $auto = 1 if $cgi->param('auto');
434
435   $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
436   my $paybatch = $1;
437
438   process_payment(
439     'session_id' => $session_id,
440     'payby'      => 'CHEK',
441     'amount'     => $amount,
442     'payinfo1'   => $payinfo1,
443     'payinfo2'   => $payinfo2,
444     'month'      => '12',
445     'year'       => '2037',
446     'payname'    => $payname,
447     'paytype'    => $paytype,
448     'paystate'   => $paystate,
449     'ss'         => $ss,
450     'stateid'    => $stateid,
451     'stateid_state' => $stateid_state,
452     'save'       => $save,
453     'auto'       => $auto,
454     'paybatch'   => $paybatch,
455   );
456
457 }
458
459 sub recharge_prepay {
460   customer_info( 'session_id' => $session_id );
461 }
462
463 sub recharge_results {
464
465   my $prepaid_cardnum = $cgi->param('prepaid_cardnum');
466   $prepaid_cardnum =~ s/\W//g;
467   $prepaid_cardnum =~ /^(\w*)$/ or die "illegal prepaid card number";
468   $prepaid_cardnum = $1;
469
470   process_prepay ( 'session_id'     => $session_id,
471                    'prepaid_cardnum' => $prepaid_cardnum,
472                  );
473 }
474
475 sub logout {
476   FS::SelfService::logout( 'session_id' => $session_id );
477 }
478
479 sub provision {
480   my $result = list_pkgs( 'session_id' => $session_id );
481   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
482   $result;
483 }
484
485 sub provision_svc {
486
487   my $result = part_svc_info(
488     'session_id' => $session_id,
489     map { $_ => $cgi->param($_) } qw( pkgnum svcpart ),
490   );
491   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
492
493   $result->{'svcdb'} =~ /^svc_(.*)$/
494     #or return { 'error' => 'Unknown svcdb '. $result->{'svcdb'} };
495     or die 'Unknown svcdb '. $result->{'svcdb'};
496   $action .= "_$1";
497
498   $result;
499 }
500
501 sub process_svc_acct {
502
503   my $result = provision_acct (
504     'session_id' => $session_id,
505     map { $_ => $cgi->param($_) } qw(
506       pkgnum svcpart username domsvc _password _password2 sec_phrase popnum )
507   );
508
509   if ( exists $result->{'error'} && $result->{'error'} ) { 
510     #warn "$result $result->{'error'}"; 
511     $action = 'provision_svc_acct';
512     return {
513       $cgi->Vars,
514       %{ part_svc_info( 'session_id' => $session_id,
515                         map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
516                       )
517       },
518       'error' => $result->{'error'},
519     };
520   } else {
521     #warn "$result $result->{'error'}"; 
522     return $result;
523   }
524
525 }
526
527 sub process_svc_external {
528   provision_external (
529     'session_id' => $session_id,
530     map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
531   );
532 }
533
534 sub delete_svc {
535   unprovision_svc(
536     'session_id' => $session_id,
537     'svcnum'     => $cgi->param('svcnum'),
538   );
539 }
540
541 sub view_usage {
542   list_svcs(
543     'session_id'  => $session_id,
544     'svcdb'       => 'svc_acct',
545     'ncancelled'  => 1,
546   );
547 }
548
549 sub view_usage_details {
550   list_svc_usage(
551     'session_id'  => $session_id,
552     'svcnum'      => $cgi->param('svcnum'),
553     'beginning'   => $cgi->param('beginning') || '',
554     'ending'      => $cgi->param('ending') || '',
555   );
556 }
557
558 sub view_support_details {
559   list_support_usage(
560     'session_id'  => $session_id,
561     'svcnum'      => $cgi->param('svcnum'),
562     'beginning'   => $cgi->param('beginning') || '',
563     'ending'      => $cgi->param('ending') || '',
564   );
565 }
566
567 sub change_password {
568   list_svcs(
569     'session_id' => $session_id,
570     'svcdb'      => 'svc_acct',
571   );
572 };
573
574 sub process_change_password {
575
576   my $result = myaccount_passwd(
577     'session_id'    => $session_id,
578     map { $_ => $cgi->param($_) } qw( svcnum new_password new_password2 )
579   );
580
581   if ( exists $result->{'error'} && $result->{'error'} ) { 
582
583     $action = 'change_password';
584     return {
585       $cgi->Vars,
586       %{ list_svcs( 'session_id' => $session_id,
587                     'svcdb'      => 'svc_acct',
588                   )
589        },
590       #'svcnum' => $cgi->param('svcnum'),
591       'error'  => $result->{'error'}
592     };
593
594  } else {
595
596    return $result;
597
598  }
599
600 }
601
602 #--
603
604 sub do_template {
605   my $name = shift;
606   my $fill_in = shift;
607
608   $cgi->delete_all();
609   $fill_in->{'selfurl'} = $cgi->self_url;
610   $fill_in->{'cgi'} = \$cgi;
611
612   my $template = new Text::Template( TYPE    => 'FILE',
613                                      SOURCE  => "$template_dir/$name.html",
614                                      DELIMITERS => [ '<%=', '%>' ],
615                                      UNTAINT => 1,                    )
616     or die $Text::Template::ERROR;
617
618   print $cgi->header( '-expires' => 'now' ),
619         $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
620                             HASH    => $fill_in
621                           );
622 }
623
624 #*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
625
626 package FS::SelfService::_selfservicecgi;
627
628 #use FS::SelfService qw(regionselector expselect popselector);
629 use HTML::Entities;
630 use FS::SelfService qw(regionselector popselector domainselector);
631
632 #false laziness w/agent.cgi
633 sub include {
634   my $name = shift;
635   my $template = new Text::Template( TYPE   => 'FILE',
636                                      SOURCE => "$main::template_dir/$name.html",
637                                      DELIMITERS => [ '<%=', '%>' ],
638                                      UNTAINT => 1,                   
639                                    )
640     or die $Text::Template::ERROR;
641
642   $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
643                       #HASH    => $fill_in
644                     );
645
646 }
647