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