5569dfbde94fd8e46a4646e464cca619b32c7b7d
[freeside.git] / FS / FS / ClientAPI / Signup.pm
1 package FS::ClientAPI::Signup;
2
3 use strict;
4 use vars qw($DEBUG $me);
5 use Data::Dumper;
6 use Tie::RefHash;
7 use FS::Conf;
8 use FS::Record qw(qsearch qsearchs dbdef);
9 use FS::Msgcat qw(gettext);
10 use FS::Misc qw(card_types);
11 use FS::ClientAPI_SessionCache;
12 use FS::agent;
13 use FS::cust_main_county;
14 use FS::part_pkg;
15 use FS::svc_acct_pop;
16 use FS::cust_main;
17 use FS::cust_pkg;
18 use FS::svc_acct;
19 use FS::svc_phone;
20 use FS::acct_snarf;
21 use FS::queue;
22 use FS::reg_code;
23
24 $DEBUG = 0;
25 $me = '[FS::ClientAPI::Signup]';
26
27 sub signup_info {
28   my $packet = shift;
29
30   warn "$me signup_info called on $packet\n" if $DEBUG;
31
32   my $conf = new FS::Conf;
33   my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
34
35   my $cache = new FS::ClientAPI_SessionCache( {
36     'namespace' => 'FS::ClientAPI::Signup',
37   } );
38   my $signup_info_cache = $cache->get('signup_info_cache');
39
40   if ( $signup_info_cache ) {
41
42     warn "$me loading cached signup info\n" if $DEBUG > 1;
43
44   } else {
45
46     warn "$me populating signup info cache\n" if $DEBUG > 1;
47
48     my $agentnum2part_pkg = 
49       {
50         map {
51           my $agent = $_;
52           my $href = $agent->pkgpart_hashref;
53           $agent->agentnum =>
54             [
55               map { { 'payby'       => [ $_->payby ],
56                       'freq_pretty' => $_->freq_pretty,
57                       'options'     => { $_->options },
58                       %{$_->hashref}
59                   } }
60                 grep { $_->svcpart($svc_x)
61                        && ( $href->{ $_->pkgpart }
62                             || $_->agentnum == $agent->agentnum
63                           )
64                      }
65                   qsearch( 'part_pkg', { 'disabled' => '' } )
66             ];
67         } qsearch('agent', { 'disabled' => '' })
68       };
69
70     my $msgcat = { map { $_=>gettext($_) }
71                        qw( passwords_dont_match invalid_card unknown_card_type
72                            not_a empty_password illegal_or_empty_text )
73                  };
74     warn "msgcat: ". Dumper($msgcat). "\n" if $DEBUG > 2;
75
76     my $label = { map { $_ => FS::Msgcat::_gettext($_) }
77                       qw( stateid stateid_state )
78                 };
79     warn "label: ". Dumper($label). "\n" if $DEBUG > 2;
80
81     my @agent_fields = qw( agentnum agent );
82
83     $signup_info_cache = {
84       'cust_main_county' => [ map $_->hashref,
85                                   qsearch('cust_main_county', {} )
86                             ],
87
88       'agent' => [ map { my $agent = $_;
89                          map { $_ => $agent->get($_) } @agent_fields;
90                        }
91                        qsearch('agent', { 'disabled' => '' } )
92                  ],
93
94       'part_referral' => [ map $_->hashref,
95                                qsearch('part_referral', { 'disabled' => '' } )
96                          ],
97
98       'agentnum2part_pkg' => $agentnum2part_pkg,
99
100       'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ],
101
102       'emailinvoiceonly' => $conf->exists('emailinvoiceonly'),
103
104       'security_phrase' => $conf->exists('security_phrase'),
105
106       'payby' => [ $conf->config('signup_server-payby') ],
107
108       'card_types' => card_types(),
109
110       'paytypes' => [ @FS::cust_main::paytypes ],
111
112       'cvv_enabled' => 1,
113
114       'stateid_enabled' => $conf->exists('show_stateid'),
115
116       'paystate_enabled' => $conf->exists('show_bankstate'),
117
118       'ship_enabled' => 1,
119
120       'msgcat' => $msgcat,
121
122       'label' => $label,
123
124       'statedefault' => scalar($conf->config('statedefault')) || 'CA',
125
126       'countrydefault' => scalar($conf->config('countrydefault')) || 'US',
127
128       'refnum' => scalar($conf->config('signup_server-default_refnum')),
129
130       'default_pkgpart' => scalar($conf->config('signup_server-default_pkgpart')),
131
132       'signup_service' => $svc_x,
133       'default_svcpart' => scalar($conf->config('signup_server-default_svcpart')),
134
135       'head'         => join("\n", $conf->config('selfservice-head') ),
136       'body_header'  => join("\n", $conf->config('selfservice-body_header') ),
137       'body_footer'  => join("\n", $conf->config('selfservice-body_footer') ),
138       'body_bgcolor' => scalar( $conf->config('selfservice-body_bgcolor') ),
139       'box_bgcolor'  => scalar( $conf->config('selfservice-box_bgcolor')  ),
140
141       'company_name'   => scalar($conf->config('company_name')),
142
143       #per-agent?
144       'agent_ship_address' => scalar($conf->exists('agent-ship_address')),
145
146       'no_company'        => scalar($conf->exists('signup-no_company')),
147       'require_phone'     => scalar($conf->exists('cust_main-require_phone')),
148       'recommend_daytime' => scalar($conf->exists('signup-recommend_daytime')),
149       'recommend_email'   => scalar($conf->exists('signup-recommend_email')),
150
151     };
152
153     $cache->set('signup_info_cache', $signup_info_cache);
154
155   }
156
157   my $signup_info = { %$signup_info_cache };
158   warn "$me signup info loaded\n" if $DEBUG > 1;
159   warn Dumper($signup_info). "\n" if $DEBUG > 2;
160
161   my @addl = qw( signup_server-classnum2 signup_server-classnum3 );
162
163   if ( grep { $conf->exists($_) } @addl ) {
164   
165     $signup_info->{optional_packages} = [];
166
167     foreach my $addl ( @addl ) {
168
169       warn "$me adding optional package info\n" if $DEBUG > 1;
170
171       my $classnum = $conf->config($addl) or next;
172
173       my @pkgs = map { {
174                          'freq_pretty' => $_->freq_pretty,
175                          'options'     => { $_->options },
176                          %{ $_->hashref }
177                        };
178                      }
179                      qsearch( 'part_pkg', { classnum => $classnum } );
180
181       push @{$signup_info->{optional_packages}}, \@pkgs;
182
183       warn "$me done adding opt. package info for $classnum\n" if $DEBUG > 1;
184
185     }
186
187   }
188
189   my $agentnum = $packet->{'agentnum'}
190                  || $conf->config('signup_server-default_agentnum');
191   $agentnum =~ /^(\d*)$/ or die "illegal agentnum";
192   $agentnum = $1;
193
194   my $session = '';
195   if ( exists $packet->{'session_id'} ) {
196
197     warn "$me loading agent session\n" if $DEBUG > 1;
198     my $cache = new FS::ClientAPI_SessionCache( {
199       'namespace' => 'FS::ClientAPI::Agent',
200     } );
201     $session = $cache->get($packet->{'session_id'});
202     if ( $session ) {
203       $agentnum = $session->{'agentnum'};
204     } else {
205       return { 'error' => "Can't resume session" }; #better error message
206     }
207     warn "$me done loading agent session\n" if $DEBUG > 1;
208
209   } elsif ( exists $packet->{'customer_session_id'} ) {
210
211     warn "$me loading customer session\n" if $DEBUG > 1;
212     my $cache = new FS::ClientAPI_SessionCache( {
213       'namespace' => 'FS::ClientAPI::MyAccount',
214     } );
215     $session = $cache->get($packet->{'customer_session_id'});
216     if ( $session ) {
217       my $custnum = $session->{'custnum'};
218       my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum });
219       return { 'error' => "Can't find your customer record" } unless $cust_main;
220       $agentnum = $cust_main->agentnum;
221     } else {
222       return { 'error' => "Can't resume session" }; #better error message
223     }
224     warn "$me done loading customer session\n" if $DEBUG > 1;
225
226   }
227
228   $signup_info->{'part_pkg'} = [];
229
230   if ( $packet->{'reg_code'} ) {
231
232     warn "$me setting package list via reg_code\n" if $DEBUG > 1;
233
234     $signup_info->{'part_pkg'} = 
235       [ map { { 'payby'       => [ $_->payby ],
236                 'freq_pretty' => $_->freq_pretty,
237                 'options'     => { $_->options },
238                 %{$_->hashref}
239               };
240             }
241           grep { $_->svcpart($svc_x) }
242           map { $_->part_pkg }
243             qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
244                                     'agentnum' => $agentnum,              } )
245
246       ];
247
248     $signup_info->{'error'} = 'Unknown registration code'
249       unless @{ $signup_info->{'part_pkg'} };
250
251     warn "$me done setting package list via reg_code\n" if $DEBUG > 1;
252
253   } elsif ( $packet->{'promo_code'} ) {
254
255     warn "$me setting package list via promo_code\n" if $DEBUG > 1;
256
257     $signup_info->{'part_pkg'} =
258       [ map { { 'payby'   => [ $_->payby ],
259                 'freq_pretty' => $_->freq_pretty,
260                 'options'     => { $_->options },
261                 %{$_->hashref}
262             } }
263           grep { $_->svcpart($svc_x) }
264             qsearch( 'part_pkg', { 'promo_code' => {
265                                      op=>'ILIKE',
266                                      value=>$packet->{'promo_code'}
267                                    },
268                                    'disabled'   => '',                  } )
269       ];
270
271     $signup_info->{'error'} = 'Unknown promotional code'
272       unless @{ $signup_info->{'part_pkg'} };
273
274     warn "$me done setting package list via promo_code\n" if $DEBUG > 1;
275   }
276
277   if ( $agentnum ) {
278
279     warn "$me setting agent-specific package list\n" if $DEBUG > 1;
280     $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
281       unless @{ $signup_info->{'part_pkg'} };
282     warn "$me done setting agent-specific package list\n" if $DEBUG > 1;
283
284     warn "$me setting agent-specific adv. source list\n" if $DEBUG > 1;
285     $signup_info->{'part_referral'} =
286       [
287         map { $_->hashref }
288           qsearch( {
289                      'table'     => 'part_referral',
290                      'hashref'   => { 'disabled' => '' },
291                      'extra_sql' => "AND (    agentnum = $agentnum  ".
292                                     "      OR agentnum IS NULL    ) ",
293                    },
294                  )
295       ];
296     warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
297
298     my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
299                            
300     $signup_info->{'agent_name'} = $agent->agent;
301
302     $signup_info->{'company_name'} = $conf->config('company_name', $agentnum);
303
304     if ( $signup_info->{'agent_ship_address'} && $agent->agent_custnum ) {
305       my $cust_main = $agent->agent_cust_main;
306       my $prefix = length($cust_main->ship_last) ? 'ship_' : '';
307       $signup_info->{"ship_$_"} = $cust_main->get("$prefix$_")
308         foreach qw( address1 city county state zip country );
309     }
310
311   }
312   # else {
313   # delete $signup_info->{'part_pkg'};
314   #}
315
316   warn "$me sorting package list\n" if $DEBUG > 1;
317   $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} }  # case?
318                                       @{ $signup_info->{'part_pkg'} }
319                                ];
320   warn "$me done sorting package list\n" if $DEBUG > 1;
321
322   if ( exists $packet->{'session_id'} ) {
323     my $agent_signup_info = { %$signup_info };
324     delete $agent_signup_info->{agentnum2part_pkg};
325     $agent_signup_info->{'agent'} = $session->{'agent'};
326     $agent_signup_info;
327   } else {
328     $signup_info;
329   }
330
331 }
332
333 sub domain_select_hash {
334   my $packet = shift;
335
336   my $response = {};
337
338   if ($packet->{pkgpart}) {
339     my $part_pkg = qsearchs('part_pkg' => { 'pkgpart' => $packet->{pkgpart} } );
340     #$packet->{svcpart} = $part_pkg->svcpart('svc_acct')
341     $packet->{svcpart} = $part_pkg->svcpart
342       if $part_pkg;
343   }
344
345   if ($packet->{svcpart}) {
346     my $part_svc = qsearchs('part_svc' => { 'svcpart' => $packet->{svcpart} } );
347     $response->{'domsvc'} = $part_svc->part_svc_column('domsvc')->columnvalue
348       if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag  eq 'D');
349   }
350
351   $response->{'domains'}
352     = { domain_select_hash FS::svc_acct( map { $_ => $packet->{$_} }
353                                                  qw(svcpart pkgnum)
354                                        ) };
355
356   $response;
357 }
358
359 sub new_customer {
360   my $packet = shift;
361
362   my $conf = new FS::Conf;
363   my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
364
365   if ( $svc_x eq 'svc_acct' ) {
366   
367     #things that aren't necessary in base class, but are for signup server
368       #return "Passwords don't match"
369       #  if $hashref->{'_password'} ne $hashref->{'_password2'}
370     return { 'error' => gettext('empty_password') }
371       unless length($packet->{'_password'});
372     # a bit inefficient for large numbers of pops
373     return { 'error' => gettext('no_access_number_selected') }
374       unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
375
376   }
377
378   my $agentnum;
379   if ( exists $packet->{'session_id'} ) {
380     my $cache = new FS::ClientAPI_SessionCache( {
381       'namespace' => 'FS::ClientAPI::Agent',
382     } );
383     my $session = $cache->get($packet->{'session_id'});
384     if ( $session ) {
385       $agentnum = $session->{'agentnum'};
386     } else {
387       return { 'error' => "Can't resume session" }; #better error message
388     }
389   } else {
390     $agentnum = $packet->{agentnum}
391                 || $conf->config('signup_server-default_agentnum');
392   }
393
394   #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
395   # common that are still here and library them.
396   my $cust_main = new FS::cust_main ( {
397     #'custnum'          => '',
398     'agentnum'      => $agentnum,
399     'refnum'        => $packet->{refnum}
400                        || $conf->config('signup_server-default_refnum'),
401
402     map { $_ => $packet->{$_} } qw(
403
404       last first ss company address1 address2
405       city county state zip country
406       daytime night fax stateid stateid_state
407
408       ship_last ship_first ship_ss ship_company ship_address1 ship_address2
409       ship_city ship_county ship_state ship_zip ship_country
410       ship_daytime ship_night ship_fax
411
412       payby
413       payinfo paycvv paydate payname paystate paytype
414       paystart_month paystart_year payissue
415       payip
416
417       referral_custnum comments
418     )
419
420   } );
421
422   my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
423   if ( $conf->exists('agent_ship_address') && $agent->agent_custnum ) {
424     my $agent_cust_main = $agent->agent_cust_main;
425     my $prefix = length($agent_cust_main->ship_last) ? 'ship_' : '';
426     $cust_main->set("ship_$_", $agent_cust_main->get("$prefix$_") )
427       foreach qw( address1 city county state zip country );
428
429     $cust_main->set("ship_$_", $cust_main->get($_))
430       foreach qw( last first );
431
432   }
433
434
435   return { 'error' => "Illegal payment type" }
436     unless grep { $_ eq $packet->{'payby'} }
437                 $conf->config('signup_server-payby');
438
439   $cust_main->payinfo($cust_main->daytime)
440     if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
441
442   my @invoicing_list = $packet->{'invoicing_list'}
443                          ? split( /\s*\,\s*/, $packet->{'invoicing_list'} )
444                          : ();
445
446   $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
447   my $pkgpart = $1;
448   return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
449
450   my $part_pkg =
451     qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
452       or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
453   my $svcpart = $part_pkg->svcpart($svc_x);
454
455   my $reg_code = '';
456   if ( $packet->{'reg_code'} ) {
457     $reg_code = qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
458                                         'agentnum' => $agentnum,             } )
459       or return { 'error' => 'Unknown registration code' };
460   }
461
462   my $cust_pkg = new FS::cust_pkg ( {
463     #later#'custnum' => $custnum,
464     'pkgpart'    => $packet->{'pkgpart'},
465     'promo_code' => $packet->{'promo_code'},
466     'reg_code'   => $packet->{'reg_code'},
467   } );
468   #my $error = $cust_pkg->check;
469   #return { 'error' => $error } if $error;
470
471   #should be all auto-magic and shit
472   my $svc;
473   if ( $svc_x eq 'svc_acct' ) {
474
475     $svc = new FS::svc_acct ( {
476       'svcpart'   => $svcpart,
477       map { $_ => $packet->{$_} }
478         qw( username _password sec_phrase popnum ),
479     } );
480
481     my @acct_snarf;
482     my $snarfnum = 1;
483     while (    exists($packet->{"snarf_machine$snarfnum"})
484             && length($packet->{"snarf_machine$snarfnum"}) ) {
485       my $acct_snarf = new FS::acct_snarf ( {
486         'machine'   => $packet->{"snarf_machine$snarfnum"},
487         'protocol'  => $packet->{"snarf_protocol$snarfnum"},
488         'username'  => $packet->{"snarf_username$snarfnum"},
489         '_password' => $packet->{"snarf_password$snarfnum"},
490       } );
491       $snarfnum++;
492       push @acct_snarf, $acct_snarf;
493     }
494     $svc->child_objects( \@acct_snarf );
495
496   } elsif ( $svc_x eq 'svc_phone' ) {
497
498     $svc = new FS::svc_phone ( {
499       'svcpart' => $svcpart,
500        map { $_ => $packet->{$_} }
501          qw( countrycode phonenum sip_password pin ),
502     } );
503
504   } else {
505     die "unknown signup service $svc_x";
506   }
507
508   my $y = $svc->setdefault; # arguably should be in new method
509   return { 'error' => $y } if $y && !ref($y);
510
511   #$error = $svc->check;
512   #return { 'error' => $error } if $error;
513
514   #setup a job dependancy to delay provisioning
515   my $placeholder = new FS::queue ( {
516     'job'    => 'FS::ClientAPI::Signup::__placeholder',
517     'status' => 'locked',
518   } );
519   my $error = $placeholder->insert;
520   return { 'error' => $error } if $error;
521
522   use Tie::RefHash;
523   tie my %hash, 'Tie::RefHash';
524   %hash = ( $cust_pkg => [ $svc ] );
525   #msgcat
526   $error = $cust_main->insert(
527     \%hash,
528     \@invoicing_list,
529     'depend_jobnum' => $placeholder->jobnum,
530   );
531   if ( $error ) {
532     my $perror = $placeholder->delete;
533     $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
534     return { 'error' => $error };
535   }
536
537   if ( $conf->exists('signup_server-realtime') ) {
538
539     #warn "[fs_signup_server] Billing customer...\n" if $Debug;
540
541     my $bill_error = $cust_main->bill;
542     #warn "[fs_signup_server] error billing new customer: $bill_error"
543     #  if $bill_error;
544
545     $bill_error = $cust_main->apply_payments_and_credits;
546     #warn "[fs_signup_server] error applying payments and credits for".
547     #     " new customer: $bill_error"
548     #  if $bill_error;
549
550     $bill_error = $cust_main->collect('realtime' => 1);
551     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
552     #  if $bill_error;
553
554     if ( $cust_main->balance > 0 ) {
555
556       #this makes sense.  credit is "un-doing" the invoice
557       $cust_main->credit( $cust_main->balance, 'signup server decline',
558                           'reason_type' => $conf->config('signup_credit_type'),
559                         );
560       $cust_main->apply_credits;
561
562       #should check list for errors...
563       #$cust_main->suspend;
564       local $FS::svc_Common::noexport_hack = 1;
565       $cust_main->cancel('quiet'=>1);
566
567       my $perror = $placeholder->depended_delete;
568       warn "error removing provisioning jobs after decline: $perror" if $perror;
569       unless ( $perror ) {
570         $perror = $placeholder->delete;
571         warn "error removing placeholder after decline: $perror" if $perror;
572       }
573
574       return { 'error' => '_decline' };
575     }
576
577   }
578
579   if ( $reg_code ) {
580     $error = $reg_code->delete;
581     return { 'error' => $error } if $error;
582   }
583
584   $error = $placeholder->delete;
585   return { 'error' => $error } if $error;
586
587   my %return = ( 'error'          => '',
588                  'signup_service' => $svc_x,
589                );
590
591   if ( $svc_x eq 'svc_acct' ) {
592     $return{$_} = $svc->$_() for qw( username _password );
593   } elsif ( $svc_x eq 'svc_phone' ) {
594     $return{$_} = $svc->$_() for qw( countrycode phonenum sip_password pin );
595   } else {
596     die "unknown signup service $svc_x";
597   }
598
599   return \%return;
600
601 }
602
603 1;