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