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