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