34dad3870240d0caa450d2efaa4653916bc2a5aa
[freeside.git] / FS / FS / ClientAPI / MyAccount.pm
1 package FS::ClientAPI::MyAccount;
2
3 use strict;
4 use vars qw($cache);
5 use Digest::MD5 qw(md5_hex);
6 use Date::Format;
7 use Business::CreditCard;
8 use Cache::SharedMemoryCache; #store in db?
9 use FS::CGI qw(small_custview); #doh
10 use FS::Conf;
11 use FS::Record qw(qsearch qsearchs);
12 use FS::Msgcat qw(gettext);
13 use FS::svc_acct;
14 use FS::svc_domain;
15 use FS::svc_external;
16 use FS::part_svc;
17 use FS::cust_main;
18 use FS::cust_bill;
19 use FS::cust_main_county;
20 use FS::cust_pkg;
21
22 use FS::ClientAPI; #hmm
23 FS::ClientAPI->register_handlers(
24   'MyAccount/login'            => \&login,
25   'MyAccount/logout'           => \&logout,
26   'MyAccount/customer_info'    => \&customer_info,
27   'MyAccount/edit_info'        => \&edit_info,
28   'MyAccount/invoice'          => \&invoice,
29   'MyAccount/list_invoices'    => \&list_invoices,
30   'MyAccount/cancel'           => \&cancel,
31   'MyAccount/payment_info'     => \&payment_info,
32   'MyAccount/process_payment'  => \&process_payment,
33   'MyAccount/list_pkgs'        => \&list_pkgs,
34   'MyAccount/order_pkg'        => \&order_pkg,
35   'MyAccount/cancel_pkg'       => \&cancel_pkg,
36   'MyAccount/charge'           => \&charge,
37   'MyAccount/part_svc_info'    => \&part_svc_info,
38   'MyAccount/provision_acct'   => \&provision_acct,
39   'MyAccount/unprovision_svc'  => \&unprovision_svc,
40 );
41
42 use vars qw( @cust_main_editable_fields );
43 @cust_main_editable_fields = qw(
44   first last company address1 address2 city
45     county state zip country daytime night fax
46   ship_first ship_last ship_company ship_address1 ship_address2 ship_city
47     ship_state ship_zip ship_country ship_daytime ship_night ship_fax
48   payby payinfo payname
49 );
50
51 #store in db?
52 my $cache = new Cache::SharedMemoryCache( {
53    'namespace' => 'FS::ClientAPI::MyAccount',
54 } );
55
56 #false laziness w/FS::ClientAPI::passwd::passwd
57 sub login {
58   my $p = shift;
59
60   my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
61     or return { error => 'Domain '. $p->{'domain'}. ' not found' };
62
63   my $svc_acct = qsearchs( 'svc_acct', { 'username'  => $p->{'username'},
64                                          'domsvc'    => $svc_domain->svcnum, }
65                          );
66   return { error => 'User not found.' } unless $svc_acct;
67
68   my $conf = new FS::Conf;
69   my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
70   return { error => 'Only primary user may log in.' } 
71     if $conf->exists('selfservice_server-primary_only')
72        && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
73
74   return { error => 'Incorrect password.' }
75     unless $svc_acct->check_password($p->{'password'});
76
77   my $session = {
78     'svcnum' => $svc_acct->svcnum,
79   };
80
81   my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
82   if ( $cust_pkg ) {
83     my $cust_main = $cust_pkg->cust_main;
84     $session->{'custnum'} = $cust_main->custnum;
85   }
86
87   my $session_id;
88   do {
89     $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
90   } until ( ! defined $cache->get($session_id) ); #just in case
91
92   $cache->set( $session_id, $session, '1 hour' );
93
94   return { 'error'      => '',
95            'session_id' => $session_id,
96          };
97 }
98
99 sub logout {
100   my $p = shift;
101   if ( $p->{'session_id'} ) {
102     $cache->remove($p->{'session_id'});
103     return { 'error' => '' };
104   } else {
105     return { 'error' => "Can't resume session" }; #better error message
106   }
107 }
108
109 sub customer_info {
110   my $p = shift;
111
112   my($context, $session, $custnum) = _custoragent_session_custnum($p);
113   return { 'error' => $session } if $context eq 'error';
114
115   my %return;
116   if ( $custnum ) { #customer record
117
118     my $search = { 'custnum' => $custnum };
119     $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
120     my $cust_main = qsearchs('cust_main', $search )
121       or return { 'error' => "unknown custnum $custnum" };
122
123     $return{balance} = $cust_main->balance;
124
125     my @open = map {
126                      {
127                        invnum => $_->invnum,
128                        date   => time2str("%b %o, %Y", $_->_date),
129                        owed   => $_->owed,
130                      };
131                    } $cust_main->open_cust_bill;
132     $return{open_invoices} = \@open;
133
134     my $conf = new FS::Conf;
135     $return{small_custview} =
136       small_custview( $cust_main, $conf->config('defaultcountry') );
137
138     $return{name} = $cust_main->first. ' '. $cust_main->get('last');
139
140     for (@cust_main_editable_fields) {
141       $return{$_} = $cust_main->get($_);
142     }
143
144     if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
145       $return{payinfo} = $cust_main->payinfo_masked;
146       @return{'month', 'year'} = $cust_main->paydate_monthyear;
147     }
148
149     $return{'invoicing_list'} =
150       join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list );
151     $return{'postal_invoicing'} =
152       0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
153
154   } else { #no customer record
155
156     my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
157       or die "unknown svcnum";
158     $return{name} = $svc_acct->email;
159
160   }
161
162   return { 'error'          => '',
163            'custnum'        => $custnum,
164            %return,
165          };
166
167 }
168
169 sub edit_info {
170   my $p = shift;
171   my $session = $cache->get($p->{'session_id'})
172     or return { 'error' => "Can't resume session" }; #better error message
173
174   my $custnum = $session->{'custnum'}
175     or return { 'error' => "no customer record" };
176
177   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
178     or return { 'error' => "unknown custnum $custnum" };
179
180   my $new = new FS::cust_main { $cust_main->hash };
181   $new->set( $_ => $p->{$_} )
182     foreach grep { exists $p->{$_} } @cust_main_editable_fields;
183
184   if ( $p->{'payby'} =~ /^(CARD|DCRD)$/ ) {
185     $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
186     if ( $new->payinfo eq $cust_main->payinfo_masked ) {
187       $new->payinfo($cust_main->payinfo);
188     } else {
189       $new->paycvv($p->{'paycvv'});
190     }
191   }
192
193   my @invoicing_list;
194   if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
195     #false laziness with httemplate/edit/process/cust_main.cgi
196     @invoicing_list = split( /\s*\,\s*/, $p->{'invoicing_list'} );
197     push @invoicing_list, 'POST' if $p->{'postal_invoicing'};
198   } else {
199     @invoicing_list = $cust_main->invoicing_list;
200   }
201
202   my $error = $new->replace($cust_main, \@invoicing_list);
203   return { 'error' => $error } if $error;
204   #$cust_main = $new;
205   
206   return { 'error' => '' };
207 }
208
209 sub payment_info {
210   my $p = shift;
211   my $session = $cache->get($p->{'session_id'})
212     or return { 'error' => "Can't resume session" }; #better error message
213
214   ##
215   #generic
216   ##
217
218   my $conf = new FS::Conf;
219   my %states = map { $_->state => 1 }
220                  qsearch('cust_main_county', {
221                    'country' => $conf->config('defaultcountry') || 'US'
222                  } );
223
224   use vars qw($payment_info); #cache for performance
225   $payment_info ||= {
226
227     #list all counties/states/countries
228     'cust_main_county' => 
229       [ map { $_->hashref } qsearch('cust_main_county', {}) ],
230
231     #shortcut for one-country folks
232     'states' =>
233       [ sort { $a cmp $b } keys %states ],
234
235     'card_types' => {
236       'VISA' => 'VISA card',
237       'MasterCard' => 'MasterCard',
238       'Discover' => 'Discover card',
239       'American Express' => 'American Express card',
240     },
241
242   };
243
244   ##
245   #customer-specific
246   ##
247
248   my %return = %$payment_info;
249
250   my $custnum = $session->{'custnum'};
251
252   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
253     or return { 'error' => "unknown custnum $custnum" };
254
255   $return{balance} = $cust_main->balance;
256
257   $return{payname} = $cust_main->payname
258                      || ( $cust_main->first. ' '. $cust_main->get('last') );
259
260   $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
261
262   $return{payby} = $cust_main->payby;
263
264   if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
265     #warn $return{card_type} = cardtype($cust_main->payinfo);
266     $return{payinfo} = $cust_main->payinfo;
267
268     @return{'month', 'year'} = $cust_main->paydate_monthyear;
269
270   }
271
272   #doubleclick protection
273   my $_date = time;
274   $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
275
276   return { 'error' => '',
277            %return,
278          };
279
280 };
281
282 #some false laziness with httemplate/process/payment.cgi - look there for
283 #ACH and CVV support stuff
284 sub process_payment {
285
286   my $p = shift;
287
288   my $session = $cache->get($p->{'session_id'})
289     or return { 'error' => "Can't resume session" }; #better error message
290
291   my %return;
292
293   my $custnum = $session->{'custnum'};
294
295   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
296     or return { 'error' => "unknown custnum $custnum" };
297
298   $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
299     or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
300   my $payname = $1;
301
302   $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
303     or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
304   my $paybatch = $1;
305
306   my $payinfo;
307   my $paycvv = '';
308   #if ( $payby eq 'CHEK' ) {
309   #
310   #  $p->{'payinfo1'} =~ /^(\d+)$/
311   #    or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
312   #  my $payinfo1 = $1;
313   #   $p->{'payinfo2'} =~ /^(\d+)$/
314   #    or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
315   #  my $payinfo2 = $1;
316   #  $payinfo = $payinfo1. '@'. $payinfo2;
317   # 
318   #} elsif ( $payby eq 'CARD' ) {
319    
320     $payinfo = $p->{'payinfo'};
321     $payinfo =~ s/\D//g;
322     $payinfo =~ /^(\d{13,16})$/
323       or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
324     $payinfo = $1;
325     validate($payinfo)
326       or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
327     return { 'error' => gettext('unknown_card_type') }
328       if cardtype($payinfo) eq "Unknown";
329
330     if ( defined $cust_main->dbdef_table->column('paycvv') ) {
331       if ( length($p->{'paycvv'} ) ) {
332         if ( cardtype($payinfo) eq 'American Express card' ) {
333           $p->{'paycvv'} =~ /^(\d{4})$/
334             or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
335           $paycvv = $1;
336         } else {
337           $p->{'paycvv'} =~ /^(\d{3})$/
338             or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
339           $paycvv = $1;
340         }
341       }
342     }
343   
344   #} else {
345   #  die "unknown payby $payby";
346   #}
347
348   my $error = $cust_main->realtime_bop( 'CC', $p->{'amount'},
349     'quiet'    => 1,
350     'payinfo'  => $payinfo,
351     'paydate'  => $p->{'year'}. '-'. $p->{'month'}. '-01',
352     'payname'  => $payname,
353     'paybatch' => $paybatch,
354     'paycvv'   => $paycvv,
355     map { $_ => $p->{$_} } qw( address1 address2 city state zip )
356   );
357   return { 'error' => $error } if $error;
358
359   $cust_main->apply_payments;
360
361   if ( $p->{'save'} ) {
362     my $new = new FS::cust_main { $cust_main->hash };
363     $new->set( $_ => $p->{$_} )
364       foreach qw( payname address1 address2 city state zip payinfo );
365     $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
366     $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
367     my $error = $new->replace($cust_main);
368     return { 'error' => $error } if $error;
369     $cust_main = $new;
370   }
371
372   return { 'error' => '' };
373
374 }
375
376 sub invoice {
377   my $p = shift;
378   my $session = $cache->get($p->{'session_id'})
379     or return { 'error' => "Can't resume session" }; #better error message
380
381   my $custnum = $session->{'custnum'};
382
383   my $invnum = $p->{'invnum'};
384
385   my $cust_bill = qsearchs('cust_bill', { 'invnum'  => $invnum,
386                                           'custnum' => $custnum } )
387     or return { 'error' => "Can't find invnum" };
388
389   #my %return;
390
391   return { 'error'        => '',
392            'invnum'       => $invnum,
393            'invoice_text' => join('', $cust_bill->print_text ),
394          };
395
396 }
397
398 sub list_invoices {
399   my $p = shift;
400   my $session = $cache->get($p->{'session_id'})
401     or return { 'error' => "Can't resume session" }; #better error message
402
403   my $custnum = $session->{'custnum'};
404
405   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
406     or return { 'error' => "unknown custnum $custnum" };
407
408   my @cust_bill = $cust_main->cust_bill;
409
410   return  { 'error'       => '',
411             'invoices'    =>  [ map { { 'invnum' => $_->invnum,
412                                         '_date'  => $_->_date,
413                                       }
414                                     } @cust_bill
415                               ]
416           };
417 }
418
419 sub cancel {
420   my $p = shift;
421   my $session = $cache->get($p->{'session_id'})
422     or return { 'error' => "Can't resume session" }; #better error message
423
424   my $custnum = $session->{'custnum'};
425
426   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
427     or return { 'error' => "unknown custnum $custnum" };
428
429   my @errors = $cust_main->cancel( 'quiet'=>1 );
430
431   my $error = scalar(@errors) ? join(' / ', @errors) : '';
432
433   return { 'error' => $error };
434
435 }
436
437 sub list_pkgs {
438   my $p = shift;
439
440   my($context, $session, $custnum) = _custoragent_session_custnum($p);
441   return { 'error' => $session } if $context eq 'error';
442
443   my $search = { 'custnum' => $custnum };
444   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
445   my $cust_main = qsearchs('cust_main', $search )
446     or return { 'error' => "unknown custnum $custnum" };
447
448   #return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
449
450   my $conf = new FS::Conf;
451
452   { 'svcnum'   => $session->{'svcnum'},
453     'custnum'  => $custnum,
454     'cust_pkg' => [ map {
455                           { $_->hash,
456                             $_->part_pkg->hash,
457                             part_svc =>
458                               [ map $_->hashref, $_->available_part_svc ],
459                             cust_svc => 
460                               [ map { my $ref = { $_->hash,
461                                                   label => [ $_->label ],
462                                                 };
463                                       $ref->{_password} = $_->svc_x->_password
464                                         if $context eq 'agent'
465                                         && $conf->exists('agent-showpasswords')
466                                         && $_->part_svc->svcdb eq 'svc_acct';
467                                       $ref;
468                                     } $_->cust_svc
469                               ],
470                           };
471                         } $cust_main->ncancelled_pkgs
472                   ],
473     'small_custview' =>
474       small_custview( $cust_main, $conf->config('defaultcountry') ),
475   };
476
477 }
478
479 sub order_pkg {
480   my $p = shift;
481
482   my($context, $session, $custnum) = _custoragent_session_custnum($p);
483   return { 'error' => $session } if $context eq 'error';
484
485   my $search = { 'custnum' => $custnum };
486   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
487   my $cust_main = qsearchs('cust_main', $search )
488     or return { 'error' => "unknown custnum $custnum" };
489
490   #false laziness w/ClientAPI/Signup.pm
491
492   my $cust_pkg = new FS::cust_pkg ( {
493     'custnum' => $custnum,
494     'pkgpart' => $p->{'pkgpart'},
495   } );
496   my $error = $cust_pkg->check;
497   return { 'error' => $error } if $error;
498
499   my @svc = ();
500   unless ( $p->{'svcpart'} eq 'none' ) {
501
502     my $svcdb;
503     my $svcpart = '';
504     if ( $p->{'svcpart'} =~ /^(\d+)$/ ) {
505       $svcpart = $1;
506       my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
507       return { 'error' => "Unknown svcpart $svcpart" } unless $part_svc;
508       $svcdb = $part_svc->svcdb;
509     } else {
510       $svcdb = 'svc_acct';
511     }
512     $svcpart ||= $cust_pkg->part_pkg->svcpart($svcdb);
513
514     my %fields = (
515       'svc_acct'     => [ qw( username _password sec_phrase popnum ) ],
516       'svc_domain'   => [ qw( domain ) ],
517       'svc_external' => [ qw( id title ) ],
518     );
519   
520     my $svc_x = "FS::$svcdb"->new( {
521       'svcpart'   => $svcpart,
522       map { $_ => $p->{$_} } @{$fields{$svcdb}}
523     } );
524     
525     if ( $svcdb eq 'svc_acct' ) {
526       my @acct_snarf;
527       my $snarfnum = 1;
528       while ( length($p->{"snarf_machine$snarfnum"}) ) {
529         my $acct_snarf = new FS::acct_snarf ( {
530           'machine'   => $p->{"snarf_machine$snarfnum"},
531           'protocol'  => $p->{"snarf_protocol$snarfnum"},
532           'username'  => $p->{"snarf_username$snarfnum"},
533           '_password' => $p->{"snarf_password$snarfnum"},
534         } );
535         $snarfnum++;
536         push @acct_snarf, $acct_snarf;
537       }
538       $svc_x->child_objects( \@acct_snarf );
539     }
540     
541     my $y = $svc_x->setdefault; # arguably should be in new method
542     return { 'error' => $y } if $y && !ref($y);
543   
544     $error = $svc_x->check;
545     return { 'error' => $error } if $error;
546
547     push @svc, $svc_x;
548
549   }
550
551   use Tie::RefHash;
552   tie my %hash, 'Tie::RefHash';
553   %hash = ( $cust_pkg => \@svc );
554   #msgcat
555   $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
556   return { 'error' => $error } if $error;
557
558   my $conf = new FS::Conf;
559   if ( $conf->exists('signup_server-realtime') ) {
560
561     my $old_balance = $cust_main->balance;
562
563     my $bill_error = $cust_main->bill;
564     $cust_main->apply_payments;
565     $cust_main->apply_credits;
566     $bill_error = $cust_main->collect;
567
568     if (    $cust_main->balance > $old_balance
569          && $cust_main->balance > 0
570          && $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/ ) {
571       #this makes sense.  credit is "un-doing" the invoice
572       $cust_main->credit( sprintf("%.2f", $cust_main->balance - $old_balance ),
573                           'self-service decline' );
574       $cust_main->apply_credits( 'order' => 'newest' );
575
576       $cust_pkg->cancel('quiet'=>1);
577       return { 'error' => '_decline', 'bill_error' => $bill_error };
578     } else {
579       $cust_pkg->reexport;
580     }
581
582   } else {
583     $cust_pkg->reexport;
584   }
585
586   return { error => '', pkgnum => $cust_pkg->pkgnum };
587
588 }
589
590 sub cancel_pkg {
591   my $p = shift;
592   my $session = $cache->get($p->{'session_id'})
593     or return { 'error' => "Can't resume session" }; #better error message
594
595   my $custnum = $session->{'custnum'};
596
597   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
598     or return { 'error' => "unknown custnum $custnum" };
599
600   my $pkgnum = $p->{'pkgnum'};
601
602   my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
603                                         'pkgnum'  => $pkgnum,   } )
604     or return { 'error' => "unknown pkgnum $pkgnum" };
605
606   my $error = $cust_pkg->cancel( 'quiet'=>1 );
607   return { 'error' => $error };
608
609 }
610
611 sub provision_acct {
612   my $p = shift;
613
614   my($context, $session, $custnum) = _custoragent_session_custnum($p);
615   return { 'error' => $session } if $context eq 'error';
616
617   my $search = { 'custnum' => $custnum };
618   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
619   my $cust_main = qsearchs('cust_main', $search )
620     or return { 'error' => "unknown custnum $custnum" };
621
622   my $pkgnum = $p->{'pkgnum'};
623
624   my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
625                                         'pkgnum'  => $pkgnum,
626                                                                } )
627     or return { 'error' => "unknown pkgnum $pkgnum" };
628
629   my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
630     or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
631
632   return { 'error' => gettext('passwords_dont_match') }
633     if $p->{'_password'} ne $p->{'_password2'};
634   return { 'error' => gettext('empty_password') }
635     unless length($p->{'_password'});
636
637   my $svc_acct = new FS::svc_acct( {
638     'pkgnum'    => $p->{'pkgnum'},
639     'svcpart'   => $p->{'svcpart'},
640     'username'  => $p->{'username'},
641     '_password' => $p->{'_password'},
642   } );
643
644   return { 'svc'   => $part_svc->svc,
645            'error' => $svc_acct->insert
646          };
647
648 }
649
650 sub part_svc_info {
651   my $p = shift;
652
653   my($context, $session, $custnum) = _custoragent_session_custnum($p);
654   return { 'error' => $session } if $context eq 'error';
655
656   my $search = { 'custnum' => $custnum };
657   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
658   my $cust_main = qsearchs('cust_main', $search )
659     or return { 'error' => "unknown custnum $custnum" };
660
661   my $pkgnum = $p->{'pkgnum'};
662
663   my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
664                                         'pkgnum'  => $pkgnum,
665                                                                } )
666     or return { 'error' => "unknown pkgnum $pkgnum" };
667
668   my $svcpart = $p->{'svcpart'};
669
670   my $pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $cust_pkg->pkgpart,
671                                       'svcpart' => $svcpart,           } )
672     or return { 'error' => "unknown svcpart $svcpart for pkgnum $pkgnum" };
673   my $part_svc = $pkg_svc->part_svc;
674
675   my $conf = new FS::Conf;
676
677   return {
678     'svc'     => $part_svc->svc,
679     'svcdb'   => $part_svc->svcdb,
680     'pkgnum'  => $pkgnum,
681     'svcpart' => $svcpart,
682     'custnum' => $custnum,
683
684     'security_phrase' => 0, #XXX !
685     'svc_acct_pop'    => [], #XXX !
686     'popnum'          => '',
687     'init_popstate'   => '',
688     'popac'           => '',
689     'acstate'         => '',
690
691     'small_custview' =>
692       small_custview( $cust_main, $conf->config('defaultcountry') ),
693
694   };
695
696 }
697
698 sub unprovision_svc {
699   my $p = shift;
700
701   my($context, $session, $custnum) = _custoragent_session_custnum($p);
702   return { 'error' => $session } if $context eq 'error';
703
704   my $search = { 'custnum' => $custnum };
705   $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
706   my $cust_main = qsearchs('cust_main', $search )
707     or return { 'error' => "unknown custnum $custnum" };
708
709   my $svcnum = $p->{'svcnum'};
710
711   my $cust_svc = qsearchs('cust_svc', { 'svcnum'  => $svcnum, } )
712     or return { 'error' => "unknown svcnum $svcnum" };
713
714   return { 'error' => "Service $svcnum does not belong to customer $custnum" }
715     unless $cust_svc->cust_pkg->custnum == $custnum;
716
717   my $conf = new FS::Conf;
718
719   return { 'svc'   => $cust_svc->part_svc->svc,
720            'error' => $cust_svc->cancel,
721            'small_custview' =>
722              small_custview( $cust_main, $conf->config('defaultcountry') ),
723          };
724
725 }
726
727 #--
728
729 sub _custoragent_session_custnum {
730   my $p = shift;
731
732   my($context, $session, $custnum);
733   if ( $p->{'session_id'} ) {
734
735     $context = 'customer';
736     $session = $cache->get($p->{'session_id'})
737       or return { 'error' => "Can't resume session" }; #better error message
738     $custnum = $session->{'custnum'};
739
740   } elsif ( $p->{'agent_session_id'} ) {
741
742     $context = 'agent';
743     my $agent_cache = new Cache::SharedMemoryCache( {
744       'namespace' => 'FS::ClientAPI::Agent',
745     } );
746     $session = $agent_cache->get($p->{'agent_session_id'})
747       or return { 'error' => "Can't resume session" }; #better error message
748     $custnum = $p->{'custnum'};
749
750   } else {
751     return { 'error' => "Can't resume session" }; #better error message
752   }
753
754   ($context, $session, $custnum);
755
756 }
757
758
759 1;
760