1 package FS::ClientAPI::MyAccount;
5 use Digest::MD5 qw(md5_hex);
7 use Business::CreditCard;
8 use Cache::SharedMemoryCache; #store in db?
9 use FS::CGI qw(small_custview); #doh
11 use FS::Record qw(qsearch qsearchs);
12 use FS::Msgcat qw(gettext);
17 use FS::cust_main_county;
20 use FS::ClientAPI; #hmm
21 FS::ClientAPI->register_handlers(
22 'MyAccount/login' => \&login,
23 'MyAccount/customer_info' => \&customer_info,
24 'MyAccount/edit_info' => \&edit_info,
25 'MyAccount/invoice' => \&invoice,
26 'MyAccount/cancel' => \&cancel,
27 'MyAccount/payment_info' => \&payment_info,
28 'MyAccount/process_payment' => \&process_payment,
29 'MyAccount/list_pkgs' => \&list_pkgs,
30 'MyAccount/order_pkg' => \&order_pkg,
31 'MyAccount/cancel_pkg' => \&cancel_pkg,
32 'MyAccount/charge' => \&charge,
35 use vars qw( @cust_main_editable_fields );
36 @cust_main_editable_fields = qw(
37 first last company address1 address2 city
38 county state zip country daytime night fax
39 ship_first ship_last ship_company ship_address1 ship_address2 ship_city
40 ship_state ship_zip ship_country ship_daytime ship_night ship_fax
44 my $cache = new Cache::SharedMemoryCache( {
45 'namespace' => 'FS::ClientAPI::MyAccount',
48 #false laziness w/FS::ClientAPI::passwd::passwd
52 my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
53 or return { error => 'Domain '. $p->{'domain'}. ' not found' };
55 my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{'username'},
56 'domsvc' => $svc_domain->svcnum, }
58 return { error => 'User not found.' } unless $svc_acct;
59 return { error => 'Incorrect password.' }
60 unless $svc_acct->check_password($p->{'password'});
63 'svcnum' => $svc_acct->svcnum,
66 my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
68 my $cust_main = $cust_pkg->cust_main;
69 $session->{'custnum'} = $cust_main->custnum;
74 $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
75 } until ( ! defined $cache->get($session_id) ); #just in case
77 $cache->set( $session_id, $session, '1 hour' );
79 return { 'error' => '',
80 'session_id' => $session_id,
86 my $session = $cache->get($p->{'session_id'})
87 or return { 'error' => "Can't resume session" }; #better error message
91 my $custnum = $session->{'custnum'};
93 if ( $custnum ) { #customer record
95 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
96 or return { 'error' => "unknown custnum $custnum" };
98 $return{balance} = $cust_main->balance;
102 invnum => $_->invnum,
103 date => time2str("%b %o, %Y", $_->_date),
106 } $cust_main->open_cust_bill;
107 $return{open_invoices} = \@open;
109 my $conf = new FS::Conf;
110 $return{small_custview} =
111 small_custview( $cust_main, $conf->config('defaultcountry') );
113 $return{name} = $cust_main->first. ' '. $cust_main->get('last');
115 for (@cust_main_editable_fields) {
116 $return{$_} = $cust_main->get($_);
119 } else { #no customer record
121 my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
122 or die "unknown svcnum";
123 $return{name} = $svc_acct->email;
127 return { 'error' => '',
128 'custnum' => $custnum,
136 my $session = $cache->get($p->{'session_id'})
137 or return { 'error' => "Can't resume session" }; #better error message
139 my $custnum = $session->{'custnum'}
140 or return { 'error' => "no customer record" };
142 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
143 or return { 'error' => "unknown custnum $custnum" };
145 my $new = new FS::cust_main { $cust_main->hash };
146 $new->set( $_ => $p->{$_} )
147 foreach grep { exists $p->{$_} } @cust_main_editable_fields;
148 my $error = $new->replace($cust_main);
149 return { 'error' => $error } if $error;
152 return { 'error' => '' };
157 my $session = $cache->get($p->{'session_id'})
158 or return { 'error' => "Can't resume session" }; #better error message
162 my $custnum = $session->{'custnum'};
164 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
165 or return { 'error' => "unknown custnum $custnum" };
167 $return{balance} = $cust_main->balance;
169 $return{payname} = $cust_main->payname
170 || ( $cust_main->first. ' '. $cust_main->get('last') );
172 $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
174 $return{payby} = $cust_main->payby;
176 if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
177 warn $return{card_type} = cardtype($cust_main->payinfo);
178 $return{payinfo} = $cust_main->payinfo;
180 @return{'month', 'year'} = $cust_main->paydate_monthyear;
184 #list all counties/states/countries
185 $return{'cust_main_county'} =
186 [ map { $_->hashref } qsearch('cust_main_county', {}) ],
188 #shortcut for one-country folks
189 my $conf = new FS::Conf;
190 my %states = map { $_->state => 1 }
191 qsearch('cust_main_county', {
192 'country' => $conf->config('defaultcountry') || 'US'
194 $return{'states'} = [ sort { $a cmp $b } keys %states ];
196 $return{card_types} = {
197 'VISA' => 'VISA card',
198 'MasterCard' => 'MasterCard',
199 'Discover' => 'Discover card',
200 'American Express' => 'American Express card',
204 $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
206 return { 'error' => '',
212 #some false laziness with httemplate/process/payment.cgi - look there for
213 #ACH and CVV support stuff
214 sub process_payment {
218 my $session = $cache->get($p->{'session_id'})
219 or return { 'error' => "Can't resume session" }; #better error message
223 my $custnum = $session->{'custnum'};
225 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
226 or return { 'error' => "unknown custnum $custnum" };
228 $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
229 or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
232 $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
233 or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
238 #if ( $payby eq 'CHEK' ) {
240 # $p->{'payinfo1'} =~ /^(\d+)$/
241 # or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
243 # $p->{'payinfo2'} =~ /^(\d+)$/
244 # or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
246 # $payinfo = $payinfo1. '@'. $payinfo2;
248 #} elsif ( $payby eq 'CARD' ) {
250 $payinfo = $p->{'payinfo'};
252 $payinfo =~ /^(\d{13,16})$/
253 or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
256 or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
257 return { 'error' => gettext('unknown_card_type') }
258 if cardtype($payinfo) eq "Unknown";
260 if ( defined $cust_main->dbdef_table->column('paycvv') ) {
261 if ( length($p->{'paycvv'} ) ) {
262 if ( cardtype($payinfo) eq 'American Express card' ) {
263 $p->{'paycvv'} =~ /^(\d{4})$/
264 or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
267 $p->{'paycvv'} =~ /^(\d{3})$/
268 or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
275 # die "unknown payby $payby";
278 my $error = $cust_main->realtime_bop( 'CC', $p->{'amount'},
280 'payinfo' => $payinfo,
281 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01',
282 'payname' => $payname,
283 'paybatch' => $paybatch,
285 map { $_ => $p->{$_} } qw( address1 address2 city state zip )
287 return { 'error' => $error } if $error;
289 $cust_main->apply_payments;
291 if ( $p->{'save'} ) {
292 my $new = new FS::cust_main { $cust_main->hash };
293 $new->set( $_ => $p->{$_} )
294 foreach qw( payname address1 address2 city state zip payinfo );
295 $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
296 $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
297 my $error = $new->replace($cust_main);
298 return { 'error' => $error } if $error;
302 return { 'error' => '' };
308 my $session = $cache->get($p->{'session_id'})
309 or return { 'error' => "Can't resume session" }; #better error message
311 my $custnum = $session->{'custnum'};
313 my $invnum = $p->{'invnum'};
315 my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
316 'custnum' => $custnum } )
317 or return { 'error' => "Can't find invnum" };
321 return { 'error' => '',
323 'invoice_text' => join('', $cust_bill->print_text ),
330 my $session = $cache->get($p->{'session_id'})
331 or return { 'error' => "Can't resume session" }; #better error message
333 my $custnum = $session->{'custnum'};
335 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
336 or return { 'error' => "unknown custnum $custnum" };
338 my @errors = $cust_main->cancel( 'quiet'=>1 );
340 my $error = scalar(@errors) ? join(' / ', @errors) : '';
342 return { 'error' => $error };
348 my $session = $cache->get($p->{'session_id'})
349 or return { 'error' => "Can't resume session" }; #better error message
351 my $custnum = $session->{'custnum'};
353 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
354 or return { 'error' => "unknown custnum $custnum" };
356 return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
362 my $session = $cache->get($p->{'session_id'})
363 or return { 'error' => "Can't resume session" }; #better error message
365 my $custnum = $session->{'custnum'};
367 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
368 or return { 'error' => "unknown custnum $custnum" };
370 #false laziness w/ClientAPI/Signup.pm
372 my $cust_pkg = new FS::cust_pkg ( {
373 'custnum' => $custnum,
374 'pkgpart' => $p->{'pkgpart'},
376 my $error = $cust_pkg->check;
377 return { 'error' => $error } if $error;
379 my $svc_acct = new FS::svc_acct ( {
380 'svcpart' => $p->{'svcpart'} || $cust_pkg->part_pkg->svcpart('svc_acct'),
381 map { $_ => $p->{$_} }
382 qw( username _password sec_phrase popnum ),
387 while ( length($p->{"snarf_machine$snarfnum"}) ) {
388 my $acct_snarf = new FS::acct_snarf ( {
389 'machine' => $p->{"snarf_machine$snarfnum"},
390 'protocol' => $p->{"snarf_protocol$snarfnum"},
391 'username' => $p->{"snarf_username$snarfnum"},
392 '_password' => $p->{"snarf_password$snarfnum"},
395 push @acct_snarf, $acct_snarf;
397 $svc_acct->child_objects( \@acct_snarf );
399 my $y = $svc_acct->setdefault; # arguably should be in new method
400 return { 'error' => $y } if $y && !ref($y);
402 $error = $svc_acct->check;
403 return { 'error' => $error } if $error;
406 tie my %hash, 'Tie::RefHash';
407 %hash = ( $cust_pkg => [ $svc_acct ] );
409 $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
410 return { 'error' => $error } if $error;
412 my $conf = new FS::Conf;
413 if ( $conf->exists('signup_server-realtime') ) {
415 my $old_balance = $cust_main->balance;
417 my $bill_error = $cust_main->bill;
418 $cust_main->apply_payments;
419 $cust_main->apply_credits;
420 $bill_error = $cust_main->collect;
422 if ( $cust_main->balance > $old_balance ) {
423 $cust_pkg->cancel('quiet'=>1);
424 return { 'error' => '_decline' };
433 return { error => '' };
439 my $session = $cache->get($p->{'session_id'})
440 or return { 'error' => "Can't resume session" }; #better error message
442 my $custnum = $session->{'custnum'};
444 my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
445 or return { 'error' => "unknown custnum $custnum" };
447 my $pkgnum = $session->{'pkgnum'};
449 my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
450 'pkgnum' => $pkgnum, } )
451 or return { 'error' => "unknown pkgnum $pkgnum" };
453 my $error = $cust_main->cancel( 'quiet'=>1 );
454 return { 'error' => $error };