fix up the info passing for optional packages
[freeside.git] / FS / FS / ClientAPI / Signup.pm
1 package FS::ClientAPI::Signup;
2
3 use strict;
4 use Tie::RefHash;
5 use FS::Conf;
6 use FS::Record qw(qsearch qsearchs dbdef);
7 use FS::Msgcat qw(gettext);
8 use FS::Misc qw(card_types);
9 use FS::ClientAPI_SessionCache;
10 use FS::agent;
11 use FS::cust_main_county;
12 use FS::part_pkg;
13 use FS::svc_acct_pop;
14 use FS::cust_main;
15 use FS::cust_pkg;
16 use FS::svc_acct;
17 use FS::acct_snarf;
18 use FS::queue;
19 use FS::reg_code;
20
21 sub signup_info {
22   my $packet = shift;
23
24   my $conf = new FS::Conf;
25
26   use vars qw($signup_info); #cache for performance;
27   $signup_info ||= {
28     'cust_main_county' =>
29       [ map { $_->hashref } qsearch('cust_main_county', {}) ],
30
31     'agent' =>
32       [
33         map { $_->hashref }
34           qsearch('agent', dbdef->table('agent')->column('disabled')
35                              ? { 'disabled' => '' }
36                              : {}
37                  )
38       ],
39
40     'part_referral' =>
41       [
42         map { $_->hashref }
43           qsearch('part_referral',
44                     dbdef->table('part_referral')->column('disabled')
45                       ? { 'disabled' => '' }
46                       : {}
47                  )
48       ],
49
50     'agentnum2part_pkg' =>
51       {
52         map {
53           my $href = $_->pkgpart_hashref;
54           $_->agentnum =>
55             [
56               map { { 'payby'       => [ $_->payby ],
57                       'freq_pretty' => $_->freq_pretty,
58                       'options'     => { $_->options },
59                       %{$_->hashref}
60                   } }
61                 grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } }
62                   qsearch( 'part_pkg', { 'disabled' => '' } )
63             ];
64         } qsearch('agent', dbdef->table('agent')->column('disabled')
65                              ? { 'disabled' => '' }
66                              : {}
67                  )
68       },
69
70     'svc_acct_pop' => [ map { $_->hashref } qsearch('svc_acct_pop',{} ) ],
71
72     'emailinvoiceonly' => $conf->exists('emailinvoiceonly'),
73
74     'security_phrase' => $conf->exists('security_phrase'),
75
76     'payby' => [ $conf->config('signup_server-payby') ],
77
78     'card_types' => card_types(),
79
80     'cvv_enabled' => defined dbdef->table('cust_main')->column('paycvv'),
81
82     'ship_enabled' => defined dbdef->table('cust_main')->column('ship_last'),
83
84     'msgcat' => { map { $_=>gettext($_) } qw(
85       passwords_dont_match invalid_card unknown_card_type not_a empty_password illegal_or_empty_text
86     ) },
87
88     'statedefault' => $conf->config('statedefault') || 'CA',
89
90     'countrydefault' => $conf->config('countrydefault') || 'US',
91
92     'refnum' => $conf->config('signup_server-default_refnum'),
93
94     'default_pkgpart' => $conf->config('signup_server-default_pkgpart'),
95
96   };
97
98   my @addl = qw( signup_server-classnum2 signup_server-classnum3 );
99
100   if ( grep { $conf->exists($_) } @addl ) {
101   
102     $signup_info->{optional_packages} = [];
103
104     foreach my $addl ( @addl ) {
105       my $classnum = $conf->config($addl) or next;
106
107       my @pkgs = map { {
108                          'freq_pretty' => $_->freq_pretty,
109                          'options'     => { $_->options },
110                          %{ $_->hashref }
111                        };
112                      }
113                      qsearch( 'part_pkg', { classnum => $classnum } );
114
115       push @{$signup_info->{optional_packages}}, \@pkgs;
116
117     }
118
119   }
120
121   my $agentnum = $packet->{'agentnum'}
122                  || $conf->config('signup_server-default_agentnum');
123
124   my $session = '';
125   if ( exists $packet->{'session_id'} ) {
126     my $cache = new FS::ClientAPI_SessionCache( {
127       'namespace' => 'FS::ClientAPI::Agent',
128     } );
129     $session = $cache->get($packet->{'session_id'});
130     if ( $session ) {
131       $agentnum = $session->{'agentnum'};
132     } else {
133       return { 'error' => "Can't resume session" }; #better error message
134     }
135   }elsif( exists $packet->{'customer_session_id'} ) {
136     my $cache = new FS::ClientAPI_SessionCache( {
137       'namespace' => 'FS::ClientAPI::MyAccount',
138     } );
139     $session = $cache->get($packet->{'customer_session_id'});
140     if ( $session ) {
141       my $custnum = $session->{'custnum'};
142       my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum });
143       return { 'error' => "Can't find your customer record" } unless $cust_main;
144       $agentnum = $cust_main->agentnum;
145     } else {
146       return { 'error' => "Can't resume session" }; #better error message
147     }
148   }
149
150   $signup_info->{'part_pkg'} = [];
151
152   if ( $packet->{'reg_code'} ) {
153     $signup_info->{'part_pkg'} = 
154       [ map { { 'payby'       => [ $_->payby ],
155                 'freq_pretty' => $_->freq_pretty,
156                 'options'     => { $_->options },
157                 %{$_->hashref}
158               };
159             }
160           grep { $_->svcpart('svc_acct') }
161           map { $_->part_pkg }
162             qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
163                                     'agentnum' => $agentnum,              } )
164
165       ];
166
167     $signup_info->{'error'} = 'Unknown registration code'
168       unless @{ $signup_info->{'part_pkg'} };
169
170   } elsif ( $packet->{'promo_code'} ) {
171
172     $signup_info->{'part_pkg'} =
173       [ map { { 'payby'   => [ $_->payby ],
174                 'freq_pretty' => $_->freq_pretty,
175                 'options'     => { $_->options },
176                 %{$_->hashref}
177             } }
178           grep { $_->svcpart('svc_acct') }
179             qsearch( 'part_pkg', { 'promo_code' => {
180                                      op=>'ILIKE',
181                                      value=>$packet->{'promo_code'}
182                                    },
183                                    'disabled'   => '',                  } )
184       ];
185
186     $signup_info->{'error'} = 'Unknown promotional code'
187       unless @{ $signup_info->{'part_pkg'} };
188   }
189
190   if ( $agentnum && ! @{ $signup_info->{'part_pkg'} } ) {
191     $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum};
192   }
193   # else {
194   # delete $signup_info->{'part_pkg'};
195   #}
196
197   if ( exists $packet->{'session_id'} ) {
198     my $agent_signup_info = { %$signup_info };
199     delete $agent_signup_info->{agentnum2part_pkg};
200     $agent_signup_info->{'agent'} = $session->{'agent'};
201     $agent_signup_info;
202   } else {
203     $signup_info;
204   }
205
206 }
207
208 sub new_customer {
209   my $packet = shift;
210
211   my $conf = new FS::Conf;
212   
213   #things that aren't necessary in base class, but are for signup server
214     #return "Passwords don't match"
215     #  if $hashref->{'_password'} ne $hashref->{'_password2'}
216   return { 'error' => gettext('empty_password') }
217     unless length($packet->{'_password'});
218   # a bit inefficient for large numbers of pops
219   return { 'error' => gettext('no_access_number_selected') }
220     unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
221
222   my $agentnum;
223   if ( exists $packet->{'session_id'} ) {
224     my $cache = new FS::ClientAPI_SessionCache( {
225       'namespace' => 'FS::ClientAPI::Agent',
226     } );
227     my $session = $cache->get($packet->{'session_id'});
228     if ( $session ) {
229       $agentnum = $session->{'agentnum'};
230     } else {
231       return { 'error' => "Can't resume session" }; #better error message
232     }
233   } else {
234     $agentnum = $packet->{agentnum}
235                 || $conf->config('signup_server-default_agentnum');
236   }
237
238   #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
239   # common that are still here and library them.
240   my $cust_main = new FS::cust_main ( {
241     #'custnum'          => '',
242     'agentnum'      => $agentnum,
243     'refnum'        => $packet->{refnum}
244                        || $conf->config('signup_server-default_refnum'),
245
246     map { $_ => $packet->{$_} } qw(
247
248       last first ss company address1 address2
249       city county state zip country
250       daytime night fax
251
252       ship_last ship_first ship_ss ship_company ship_address1 ship_address2
253       ship_city ship_county ship_state ship_zip ship_country
254       ship_daytime ship_night ship_fax
255
256       payby
257       payinfo paycvv paydate payname
258       paystart_month paystart_year payissue
259       payip
260
261       referral_custnum comments
262     )
263
264   } );
265
266   return { 'error' => "Illegal payment type" }
267     unless grep { $_ eq $packet->{'payby'} }
268                 $conf->config('signup_server-payby');
269
270   $cust_main->payinfo($cust_main->daytime)
271     if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
272
273   my @invoicing_list = split( /\s*\,\s*/, $packet->{'invoicing_list'} );
274
275   $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
276   my $pkgpart = $1;
277   return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
278
279   my $part_pkg =
280     qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
281       or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
282   my $svcpart = $part_pkg->svcpart('svc_acct');
283
284   my $reg_code = '';
285   if ( $packet->{'reg_code'} ) {
286     $reg_code = qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
287                                         'agentnum' => $agentnum,             } )
288       or return { 'error' => 'Unknown registration code' };
289   }
290
291   my $cust_pkg = new FS::cust_pkg ( {
292     #later#'custnum' => $custnum,
293     'pkgpart'    => $packet->{'pkgpart'},
294     'promo_code' => $packet->{'promo_code'},
295     'reg_code'   => $packet->{'reg_code'},
296   } );
297   #my $error = $cust_pkg->check;
298   #return { 'error' => $error } if $error;
299
300   my $svc_acct = new FS::svc_acct ( {
301     'svcpart'   => $svcpart,
302     map { $_ => $packet->{$_} }
303       qw( username _password sec_phrase popnum ),
304   } );
305
306   my @acct_snarf;
307   my $snarfnum = 1;
308   while (    exists($packet->{"snarf_machine$snarfnum"})
309           && length($packet->{"snarf_machine$snarfnum"}) ) {
310     my $acct_snarf = new FS::acct_snarf ( {
311       'machine'   => $packet->{"snarf_machine$snarfnum"},
312       'protocol'  => $packet->{"snarf_protocol$snarfnum"},
313       'username'  => $packet->{"snarf_username$snarfnum"},
314       '_password' => $packet->{"snarf_password$snarfnum"},
315     } );
316     $snarfnum++;
317     push @acct_snarf, $acct_snarf;
318   }
319   $svc_acct->child_objects( \@acct_snarf );
320
321   my $y = $svc_acct->setdefault; # arguably should be in new method
322   return { 'error' => $y } if $y && !ref($y);
323
324   #$error = $svc_acct->check;
325   #return { 'error' => $error } if $error;
326
327   #setup a job dependancy to delay provisioning
328   my $placeholder = new FS::queue ( {
329     'job'    => 'FS::ClientAPI::Signup::__placeholder',
330     'status' => 'locked',
331   } );
332   my $error = $placeholder->insert;
333   return { 'error' => $error } if $error;
334
335   use Tie::RefHash;
336   tie my %hash, 'Tie::RefHash';
337   %hash = ( $cust_pkg => [ $svc_acct ] );
338   #msgcat
339   $error = $cust_main->insert(
340     \%hash,
341     \@invoicing_list,
342     'depend_jobnum' => $placeholder->jobnum,
343   );
344   if ( $error ) {
345     my $perror = $placeholder->delete;
346     $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
347     return { 'error' => $error };
348   }
349
350   if ( $conf->exists('signup_server-realtime') ) {
351
352     #warn "[fs_signup_server] Billing customer...\n" if $Debug;
353
354     my $bill_error = $cust_main->bill;
355     #warn "[fs_signup_server] error billing new customer: $bill_error"
356     #  if $bill_error;
357
358     $cust_main->apply_payments_and_credits;
359
360     $bill_error = $cust_main->collect('realtime' => 1);
361     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
362     #  if $bill_error;
363
364     if ( $cust_main->balance > 0 ) {
365
366       #this makes sense.  credit is "un-doing" the invoice
367       $cust_main->credit( $cust_main->balance, 'signup server decline' );
368       $cust_main->apply_credits;
369
370       #should check list for errors...
371       #$cust_main->suspend;
372       local $FS::svc_Common::noexport_hack = 1;
373       $cust_main->cancel('quiet'=>1);
374
375       my $perror = $placeholder->depended_delete;
376       warn "error removing provisioning jobs after decline: $perror" if $perror;
377       unless ( $perror ) {
378         $perror = $placeholder->delete;
379         warn "error removing placeholder after decline: $perror" if $perror;
380       }
381
382       return { 'error' => '_decline' };
383     }
384
385   }
386
387   if ( $reg_code ) {
388     $error = $reg_code->delete;
389     return { 'error' => $error } if $error;
390   }
391
392   $error = $placeholder->delete;
393   return { 'error' => $error } if $error;
394
395   return { error => '' };
396
397 }
398
399 1;