add signup-duplicate_cc-warn_hours to warn about duplicate signups in a time span...
[freeside.git] / fs_selfservice / FS-SelfService / cgi / signup.cgi
1 #!/usr/bin/perl -T
2 #!/usr/bin/perl -Tw
3
4 use strict;
5 use vars qw( @payby $cgi $init_data
6              $self_url $error $agentnum
7
8              $ieak_file $ieak_template
9              $signup_html $signup_template
10              $success_html $success_template
11              $collect_html $collect_template
12              $decline_html $decline_template
13            );
14
15 use subs qw( print_form print_okay print_decline
16              success_default collect_default decline_default
17            );
18 use CGI;
19 #use CGI::Carp qw(fatalsToBrowser);
20 use Tie::IxHash;
21 use Text::Template;
22 use Business::CreditCard;
23 use HTTP::BrowserDetect;
24 use HTML::Widgets::SelectLayers;
25 use FS::SelfService qw( signup_info new_customer );
26
27 #acceptable payment methods
28 #
29 #@payby = qw( CARD BILL COMP );
30 #@payby = qw( CARD BILL );
31 #@payby = qw( CARD );
32 @payby = qw( CARD PREPAY );
33
34 $ieak_file = '/usr/local/freeside/ieak.template';
35 $signup_html = -e 'signup.html'
36                  ? 'signup.html'
37                  : '/usr/local/freeside/signup.html';
38 $success_html = -e 'success.html'
39                   ? 'success.html'
40                   : '/usr/local/freeside/success.html';
41 $collect_html = -e 'collect.html'
42                   ? 'collect.html'
43                   : '/usr/local/freeside/collect.html';
44 $decline_html = -e 'decline.html'
45                   ? 'decline.html'
46                   : '/usr/local/freeside/decline.html';
47
48
49 if ( -e $ieak_file ) {
50   my $ieak_txt = Text::Template::_load_text($ieak_file)
51     or die $Text::Template::ERROR;
52   $ieak_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
53   $ieak_txt = $1;
54   $ieak_txt =~ s/\r//g; # don't double \r on old templates
55   $ieak_txt =~ s/\n/\r\n/g;
56   $ieak_template = new Text::Template ( TYPE => 'STRING', SOURCE => $ieak_txt )
57     or die $Text::Template::ERROR;
58 } else {
59   $ieak_template = '';
60 }
61
62 $agentnum = '';
63 if ( -e $signup_html ) {
64   my $signup_txt = Text::Template::_load_text($signup_html)
65     or die $Text::Template::ERROR;
66   $signup_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
67   $signup_txt = $1;
68   $signup_template = new Text::Template ( TYPE => 'STRING',
69                                           SOURCE => $signup_txt,
70                                           DELIMITERS => [ '<%=', '%>' ]
71                                         )
72     or die $Text::Template::ERROR;
73   if ( $signup_txt =~
74          /<\s*INPUT TYPE="?hidden"?\s+NAME="?agentnum"?\s+VALUE="?(\d+)"?\s*\/?\s*>/si
75   ) {
76     $agentnum = $1;
77   }
78 } else {
79   #too much maintenance hassle to keep in this file
80   die "can't find ./signup.html or /usr/local/freeside/signup.html";
81   #$signup_template = new Text::Template ( TYPE => 'STRING',
82   #                                        SOURCE => &signup_default,
83   #                                        DELIMITERS => [ '<%=', '%>' ]
84   #                                      )
85   #  or die $Text::Template::ERROR;
86 }
87
88 if ( -e $success_html ) {
89   my $success_txt = Text::Template::_load_text($success_html)
90     or die $Text::Template::ERROR;
91   $success_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
92   $success_txt = $1;
93   $success_template = new Text::Template ( TYPE => 'STRING',
94                                            SOURCE => $success_txt,
95                                            DELIMITERS => [ '<%=', '%>' ],
96                                          )
97     or die $Text::Template::ERROR;
98 } else {
99   $success_template = new Text::Template ( TYPE => 'STRING',
100                                            SOURCE => &success_default,
101                                            DELIMITERS => [ '<%=', '%>' ],
102                                          )
103     or die $Text::Template::ERROR;
104 }
105
106 if ( -e $collect_html ) {
107   my $collect_txt = Text::Template::_load_text($collect_html)
108     or die $Text::Template::ERROR;
109   $collect_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
110   $collect_txt = $1;
111   $collect_template = new Text::Template ( TYPE => 'STRING',
112                                            SOURCE => $collect_txt,
113                                            DELIMITERS => [ '<%=', '%>' ],
114                                          )
115     or die $Text::Template::ERROR;
116 } else {
117   $collect_template = new Text::Template ( TYPE => 'STRING',
118                                            SOURCE => &collect_default,
119                                            DELIMITERS => [ '<%=', '%>' ],
120                                          )
121     or die $Text::Template::ERROR;
122 }
123
124 if ( -e $decline_html ) {
125   my $decline_txt = Text::Template::_load_text($decline_html)
126     or die $Text::Template::ERROR;
127   $decline_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
128   $decline_txt = $1;
129   $decline_template = new Text::Template ( TYPE => 'STRING',
130                                            SOURCE => $decline_txt,
131                                            DELIMITERS => [ '<%=', '%>' ],
132                                          )
133     or die $Text::Template::ERROR;
134 } else {
135   $decline_template = new Text::Template ( TYPE => 'STRING',
136                                            SOURCE => &decline_default,
137                                            DELIMITERS => [ '<%=', '%>' ],
138                                          )
139     or die $Text::Template::ERROR;
140 }
141
142 $cgi = new CGI;
143
144 $init_data = signup_info( 'agentnum'   => $agentnum || scalar($cgi->param('agentnum')),
145                           'promo_code' => scalar($cgi->param('promo_code')),
146                           'reg_code'   => uc(scalar($cgi->param('reg_code'))),
147                         );
148
149 my $magic  = $cgi->param('magic') || '';
150 my $action = $cgi->param('action') || '';
151
152 if ( $magic eq 'process' || $action eq 'process_signup' ) {
153
154     $error = '';
155
156     $cgi->param('agentnum', $agentnum) if $agentnum;
157     $cgi->param('reg_code', uc(scalar($cgi->param('reg_code'))) );
158
159     #false laziness w/agent.cgi, identical except for agentnum
160     my $payby = $cgi->param('payby');
161     if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
162       #$payinfo = join('@', map { $cgi->param( $payby. "_payinfo$_" ) } (1,2) );
163       $cgi->param('payinfo' => $cgi->param($payby. '_payinfo1'). '@'. 
164                                $cgi->param($payby. '_payinfo2')
165                  );
166     } else {
167       $cgi->param('payinfo' => $cgi->param( $payby. '_payinfo' ) );
168     }
169     $cgi->param('paydate' => $cgi->param( $payby. '_month' ). '-'.
170                              $cgi->param( $payby. '_year' )
171                );
172     $cgi->param('payname' => $cgi->param( $payby. '_payname' ) );
173     $cgi->param('paycvv' => defined $cgi->param( $payby. '_paycvv' )
174                               ? $cgi->param( $payby. '_paycvv' )
175                               : ''
176                );
177     $cgi->param('paytype' => defined $cgi->param( $payby. '_paytype' )
178                               ? $cgi->param( $payby. '_paytype' )
179                               : ''
180                );
181     $cgi->param('paystate' => defined $cgi->param( $payby. '_paystate' )
182                               ? $cgi->param( $payby. '_paystate' )
183                               : ''
184                );
185
186     if ( $cgi->param('invoicing_list') ) {
187       $cgi->param('invoicing_list' => $cgi->param('invoicing_list'). ', POST')
188         if $cgi->param('invoicing_list_POST');
189     } else {
190       $cgi->param('invoicing_list' => 'POST' );
191     }
192
193     #if ( $svc_x eq 'svc_acct' ) {
194     if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
195       $error = $init_data->{msgcat}{passwords_dont_match}; #msgcat
196       $cgi->param('_password', '');
197       $cgi->param('_password2', '');
198     }
199
200     if ( $payby =~ /^(CARD|DCRD)$/ && $cgi->param('CARD_type') ) {
201       my $payinfo = $cgi->param('payinfo');
202       $payinfo =~ s/\D//g;
203
204       $payinfo =~ /^(\d{13,16})$/
205         or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
206       $payinfo = $1;
207       validate($payinfo)
208         or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
209       cardtype($payinfo) eq $cgi->param('CARD_type')
210         or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
211     }
212
213     if ($init_data->{emailinvoiceonly} && (length $cgi->param('invoicing_list') < 1)) {
214         $error ||= $init_data->{msgcat}{illegal_or_empty_text};
215     }
216
217     my $rv = '';
218     unless ( $error ) {
219       $rv = new_customer( {
220         ( map { $_ => scalar($cgi->param($_)) }
221             qw( last first ss company
222                 address1 address2 city county state zip country
223                 daytime night fax stateid stateid_state
224
225                 ship_last ship_first ship_company
226                 ship_address1 ship_address2 ship_city ship_county ship_state
227                   ship_zip ship_country
228                 ship_daytime ship_night ship_fax
229
230                 payby payinfo paycvv paydate payname paystate paytype
231                 invoicing_list referral_custnum promo_code reg_code
232                 override_ban_warn
233                 pkgpart refnum agentnum
234                 username sec_phrase _password popnum
235                 mac_addr
236                 countrycode phonenum sip_password pin prepaid_shortform
237               ),
238             grep { /^snarf_/ } $cgi->param
239         ),
240         'payip' => $cgi->remote_host(),
241       } );
242       $error = $rv->{'error'};
243     }
244     #eslaf
245     
246     if ( $error eq '_decline' ) {
247       print_decline();
248     } elsif ( $error eq '_collect' ) {
249       map { $cgi->param($_, $rv->{$_}) }
250         qw( popup_url reference amount );
251       print_collect($rv);
252     } elsif ( $error ) {
253
254       #fudge the snarf info
255       no strict 'refs';
256       ${$_} = $cgi->param($_) foreach grep { /^snarf_/ } $cgi->param;
257
258       if ( $error =~ /^_duplicate_(card|ach)$/ ) {
259         my $what = ($1 eq 'card') ? 'Credit card' : 'Electronic check';
260         $error = "Warning: $what already used to sign up recently";
261         $init_data->{'override_ban_warn'} = 1;
262       }
263
264       print_form();
265
266     } else {
267       print_okay(
268         'pkgpart' => scalar($cgi->param('pkgpart')),
269         %$rv,
270       );
271     }
272
273 } elsif ( $magic eq 'success' || $action eq 'success' ) {
274
275   $cgi->param('username', 'username');  #hmmm temp kludge
276   $cgi->param('_password', 'password');
277   print_okay( map { /^([\w ]+)$/ ? ( $_ => $1 ) : () } $cgi->param ); #hmmm
278
279 } elsif ( $magic eq 'decline' || $action eq 'decline' ) {
280
281   print_decline();
282
283 } else {
284   $error = '';
285   print_form;
286 }
287
288 sub print_form {
289
290   $error = "Error: $error" if $error && $error !~ /^Warning:/i;
291
292   my $r = {
293     $cgi->Vars,
294     %{$init_data},
295     'error' => $error,
296   };
297
298   $r->{pkgpart} ||= $r->{default_pkgpart};
299
300   $r->{referral_custnum} = $r->{'ref'};
301   #$cgi->delete('ref');
302   #$cgi->delete('init_popstate');
303   $r->{self_url} = $cgi->self_url;
304
305   $r->{prepaid_shortform} = $cgi->param('prepaid_shortform');
306
307   print $cgi->header( '-expires' => 'now' ),
308         $signup_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
309                                    HASH    => $r
310                                  );
311 }
312
313 sub print_collect {
314
315   $error = "Error: $error" if $error;
316
317   my $rv = shift || {};
318   my $r = {
319     $cgi->Vars,
320     %{$init_data},
321     %$rv,
322     'error' => $error,
323   };
324
325   $r->{pkgpart} ||= $r->{default_pkgpart};
326
327   $r->{referral_custnum} = $r->{'ref'};
328   $r->{self_url} = $cgi->self_url;
329
330   print $cgi->header( '-expires' => 'now' ),
331
332         $collect_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
333                                     HASH    => $r
334                                   );
335 }
336
337 sub print_decline {
338   my $r = {
339     %{$init_data},
340   };
341
342   print $cgi->header( '-expires' => 'now' ),
343         $decline_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
344                                     HASH    => $r
345                                   );
346 }
347
348 sub print_okay {
349   my %param = @_;
350   my $user_agent = new HTTP::BrowserDetect $ENV{HTTP_USER_AGENT};
351
352   my( $username, $password ) = ( '', '' );
353   my( $countrycode, $phonenum, $sip_password, $pin ) = ( '', '', '', '' );
354
355   my $svc_x = $param{signup_service} || 'svc_acct'; #just in case
356   if ( $svc_x eq 'svc_acct' ) {
357
358     $cgi->param('username') =~ /^(.+)$/
359       or die "fatal: invalid username got past FS::SelfService::new_customer";
360     $username = $1;
361     $cgi->param('_password') =~ /^(.+)$/
362       or die "fatal: invalid password got past FS::SelfService::new_customer";
363     $password = $1;
364
365   } elsif ( $svc_x eq 'svc_phone' ) {
366
367     $countrycode  = $param{countrycode};
368     $phonenum     = $param{phonenum};
369     $sip_password = $param{sip_password};
370     $pin          = $param{pin};
371
372   } else {
373     die "unknown signup service $svc_x";
374   }
375
376   ( $cgi->param('first'). ' '. $cgi->param('last') ) =~ /^(.*)$/
377     or die "fatal: invalid email_name got past FS::SelfService::new_customer";
378   my $email_name = $1; #global for template
379
380   #my %pop = ();
381   my %popnum2pop = ();
382   foreach ( @{ $init_data->{'svc_acct_pop'} } ) {
383     #push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
384     $popnum2pop{$_->{popnum}} = $_;
385   }
386
387   my( $ac, $exch, $loc);
388   my $pop = $popnum2pop{$cgi->param('popnum')};
389     #or die "fatal: invalid popnum got past FS::SelfService::new_customer";
390   if ( $pop ) {
391     ( $ac, $exch, $loc ) = ( $pop->{'ac'}, $pop->{'exch'}, $pop->{'loc'} );
392   } else {
393     ( $ac, $exch, $loc ) = ( '', '', ''); #presumably you're not using them.
394   }
395
396   #global for template
397   my $part_pkg = ( grep { $_->{'pkgpart'} eq $param{'pkgpart'} }
398                         @{ $init_data->{'part_pkg'} }
399                  )[0];
400   my $pkg =  $part_pkg->{'pkg'};
401
402   if ( $ieak_template && $user_agent->windows && $user_agent->ie ) {
403
404     #send an IEAK config
405     print $cgi->header('application/x-Internet-signup'),
406           $ieak_template->fill_in();
407
408   } else { #send a simple confirmation
409
410     print $cgi->header( '-expires' => 'now' ),
411           $success_template->fill_in( HASH => {
412
413             %{$init_data},
414
415             email_name     => $email_name,
416             pkg            => $pkg,
417             part_pkg       => \$part_pkg,
418
419             signup_service => $svc_x,
420
421             #for svc_acct
422             username       => $username,
423             password       => $password,
424             _password      => $password,
425             ac             => $ac,   #for dialup POP
426             exch           => $exch, #
427             loc            => $loc,  #
428
429             #for svc_phone
430             countrycode    => $countrycode,
431             phonenum       => $phonenum,
432             sip_password   => $sip_password,
433             pin            => $pin,
434
435           });
436   }
437
438 }
439
440 sub success_default { #html to use if you don't specify a success file
441   <<'END';
442 <HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
443 <BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
444 Thanks for signing up!
445 <BR><BR>
446 Signup information for <%= $email_name %>:
447 <BR><BR>
448 Username: <%= $username %><BR>
449 Password: <%= $password %><BR>
450 Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR>
451 Package: <%= $pkg %><BR>
452 </BODY></HTML>
453 END
454 }
455
456 sub collect_default { #html to use if there is a collect phase
457   <<'END';
458 <HTML><HEAD><TITLE>Pay now</TITLE></HEAD>
459 <BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Pay now</FONT><BR><BR>
460 <%=
461 #<SCRIPT TYPE="text/javascript">
462 #  function popcollect() {
463 #    overlib( OLiframeContent('<%= $popup_url %>', 336, 550, 'Secure Payment Area', 0, 'auto' ), CAPTION, 'Pay now', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close' );
464 #    return false;
465 #  }
466 #</SCRIPT>
467 #<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
468 #<SCRIPT TYPE="text/javascript" SRC="overlibmws_iframe.js"></SCRIPT>
469 #<SCRIPT TYPE="text/javascript" SRC="overlibmws_draggable.js"></SCRIPT>
470 #<SCRIPT TYPE="text/javascript" SRC="overlibmws_crossframe.js"></SCRIPT>
471 #<SCRIPT TYPE="text/javascript" SRC="iframecontentmws.js"></SCRIPT>
472 %>
473 You are about to contact our payment processor to pay <%= $amount %> for
474 <%= $pkg %>.<BR><BR>
475 Your transaction reference number is <%= $reference %><BR><BR>
476 <FORM NAME="collect_popper" method="post" action="<%= $popup_url %>">
477 <%=
478   my %itemhash = @collectitems ;
479   foreach my $input (keys %itemhash) {
480     $OUT .= qq!<INPUT NAME="$input" TYPE="hidden" VALUE="$itemhash{$input}">!;
481   }
482 %>
483 <INPUT NAME="submit" type="submit" value="Pay now">
484 </FORM>
485 </BODY></HTML>
486 END
487 }
488
489 sub decline_default { #html to use if there is a decline
490   <<'END';
491 <HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
492 <BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
493 There has been an error processing your account.  Please contact customer
494 support.
495 </BODY></HTML>
496 END
497 }
498
499 # subs for the templates...
500
501 package FS::SelfService::_signupcgi;
502 use HTML::Entities;
503 use FS::SelfService qw(regionselector expselect popselector didselector);
504