Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / fs_selfservice / FS-SelfService / SelfService.pm
1 package FS::SelfService;
2
3 use strict;
4 use vars qw( $VERSION @ISA @EXPORT_OK $DEBUG
5              $skip_uid_check $dir $socket %autoload $tag );
6 use Exporter;
7 use Socket;
8 use FileHandle;
9 #use IO::Handle;
10 use IO::Select;
11 use Storable 2.09 qw(nstore_fd fd_retrieve);
12
13 $VERSION = '0.03';
14
15 @ISA = qw( Exporter );
16
17 $DEBUG = 0;
18
19 $dir = "/usr/local/freeside";
20 $socket =  "$dir/selfservice_socket";
21 $socket .= '.'.$tag if defined $tag && length($tag);
22
23 #maybe should ask ClientAPI for this list
24 %autoload = (
25   'passwd'                    => 'passwd/passwd',
26   'chfn'                      => 'passwd/passwd',
27   'chsh'                      => 'passwd/passwd',
28   'login_info'                => 'MyAccount/login_info',
29   'login_banner_image'        => 'MyAccount/login_banner_image',
30   'login'                     => 'MyAccount/login',
31   'logout'                    => 'MyAccount/logout',
32   'switch_acct'               => 'MyAccount/switch_acct',
33   'switch_cust'               => 'MyAccount/switch_cust',
34   'customer_info'             => 'MyAccount/customer_info',
35   'customer_info_short'       => 'MyAccount/customer_info_short',
36   'billing_history'           => 'MyAccount/billing_history',
37   'edit_info'                 => 'MyAccount/edit_info',     #add to ss cgi!
38   'invoice'                   => 'MyAccount/invoice',
39   'invoice_pdf'               => 'MyAccount/invoice_pdf',
40   'legacy_invoice'            => 'MyAccount/legacy_invoice',
41   'legacy_invoice_pdf'        => 'MyAccount/legacy_invoice_pdf',
42   'invoice_logo'              => 'MyAccount/invoice_logo',
43   'list_invoices'             => 'MyAccount/list_invoices', #?
44   'cancel'                    => 'MyAccount/cancel',        #add to ss cgi!
45   'payment_info'              => 'MyAccount/payment_info',
46   'payment_info_renew_info'   => 'MyAccount/payment_info_renew_info',
47   'process_payment'           => 'MyAccount/process_payment',
48   'store_payment'             => 'MyAccount/store_payment',
49   'process_stored_payment'    => 'MyAccount/process_stored_payment',
50   'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
51   'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
52   'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
53   'process_prepay'            => 'MyAccount/process_prepay',
54   'realtime_collect'          => 'MyAccount/realtime_collect',
55   'list_pkgs'                 => 'MyAccount/list_pkgs',     #add to ss (added?)
56   'list_svcs'                 => 'MyAccount/list_svcs',     #add to ss (added?)
57   'list_svc_usage'            => 'MyAccount/list_svc_usage',   
58   'svc_status_html'           => 'MyAccount/svc_status_html',
59   'svc_status_hash'           => 'MyAccount/svc_status_hash',
60   'set_svc_status_hash'       => 'MyAccount/set_svc_status_hash',
61   'set_svc_status_listadd'    => 'MyAccount/set_svc_status_listadd',
62   'set_svc_status_listdel'    => 'MyAccount/set_svc_status_listdel',
63   'set_svc_status_vacationadd'=> 'MyAccount/set_svc_status_vacationadd',
64   'set_svc_status_vacationdel'=> 'MyAccount/set_svc_status_vacationdel',
65   'acct_forward_info'         => 'MyAccount/acct_forward_info',
66   'process_acct_forward'      => 'MyAccount/process_acct_forward',
67   'list_dsl_devices'          => 'MyAccount/list_dsl_devices',   
68   'add_dsl_device'            => 'MyAccount/add_dsl_device',   
69   'delete_dsl_device'         => 'MyAccount/delete_dsl_device',   
70   'port_graph'                => 'MyAccount/port_graph',   
71   'list_cdr_usage'            => 'MyAccount/list_cdr_usage',   
72   'list_support_usage'        => 'MyAccount/list_support_usage',   
73   'order_pkg'                 => 'MyAccount/order_pkg',     #add to ss cgi!
74   'change_pkg'                => 'MyAccount/change_pkg', 
75   'order_recharge'            => 'MyAccount/order_recharge',
76   'renew_info'                => 'MyAccount/renew_info',
77   'order_renew'               => 'MyAccount/order_renew',
78   'cancel_pkg'                => 'MyAccount/cancel_pkg',    #add to ss cgi!
79   'suspend_pkg'               => 'MyAccount/suspend_pkg',   #add to ss cgi!
80   'charge'                    => 'MyAccount/charge',        #?
81   'part_svc_info'             => 'MyAccount/part_svc_info',
82   'provision_acct'            => 'MyAccount/provision_acct',
83   'provision_phone'           => 'MyAccount/provision_phone',
84   'provision_external'        => 'MyAccount/provision_external',
85   'unprovision_svc'           => 'MyAccount/unprovision_svc',
86   'myaccount_passwd'          => 'MyAccount/myaccount_passwd',
87   'reset_passwd'              => 'MyAccount/reset_passwd',
88   'check_reset_passwd'        => 'MyAccount/check_reset_passwd',
89   'process_reset_passwd'      => 'MyAccount/process_reset_passwd',
90   'list_tickets'              => 'MyAccount/list_tickets',
91   'create_ticket'             => 'MyAccount/create_ticket',
92   'get_ticket'                => 'MyAccount/get_ticket',
93   'adjust_ticket_priority'    => 'MyAccount/adjust_ticket_priority',
94   'did_report'                => 'MyAccount/did_report',
95   'signup_info'               => 'Signup/signup_info',
96   'skin_info'                 => 'MyAccount/skin_info',
97   'access_info'               => 'MyAccount/access_info',
98   'domain_select_hash'        => 'Signup/domain_select_hash',  # expose?
99   'new_customer'              => 'Signup/new_customer',
100   'new_customer_minimal'      => 'Signup/new_customer_minimal',
101   'capture_payment'           => 'Signup/capture_payment',
102   #N/A 'clear_signup_cache'        => 'Signup/clear_cache',
103   'new_agent'                 => 'Agent/new_agent',
104   'agent_login'               => 'Agent/agent_login',
105   'agent_logout'              => 'Agent/agent_logout',
106   'agent_info'                => 'Agent/agent_info',
107   'agent_list_customers'      => 'Agent/agent_list_customers',
108   'check_username'            => 'Agent/check_username',
109   'suspend_username'          => 'Agent/suspend_username',
110   'unsuspend_username'        => 'Agent/unsuspend_username',
111   'mason_comp'                => 'MasonComponent/mason_comp',
112   'call_time'                 => 'PrepaidPhone/call_time',
113   'call_time_nanpa'           => 'PrepaidPhone/call_time_nanpa',
114   'phonenum_balance'          => 'PrepaidPhone/phonenum_balance',
115
116   'start_thirdparty'          => 'MyAccount/start_thirdparty',
117   'finish_thirdparty'         => 'MyAccount/finish_thirdparty',
118
119   'quotation_info'            => 'MyAccount/quotation/quotation_info',
120   'quotation_print'           => 'MyAccount/quotation/quotation_print',
121   'quotation_add_pkg'         => 'MyAccount/quotation/quotation_add_pkg',
122   'quotation_remove_pkg'      => 'MyAccount/quotation/quotation_remove_pkg',
123   'quotation_order'           => 'MyAccount/quotation/quotation_order',
124
125 );
126 @EXPORT_OK = (
127   keys(%autoload),
128   qw( regionselector regionselector_hashref location_form
129       expselect popselector domainselector didselector
130     )
131 );
132
133 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
134 $ENV{'SHELL'} = '/bin/sh';
135 $ENV{'IFS'} = " \t\n";
136 $ENV{'CDPATH'} = '';
137 $ENV{'ENV'} = '';
138 $ENV{'BASH_ENV'} = '';
139
140 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; } 
141 #if you grant appropriate permissions to whatever user
142 my $freeside_uid = scalar(getpwnam('freeside'));
143 die "not running as the freeside user\n"
144   if $> != $freeside_uid && ! $skip_uid_check;
145
146 -e $dir or die "FATAL: $dir doesn't exist!";
147 -d $dir or die "FATAL: $dir isn't a directory!";
148 -r $dir or die "FATAL: Can't read $dir as freeside user!";
149 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
150
151 foreach my $autoload ( keys %autoload ) {
152
153   my $eval =
154   "sub $autoload { ". '
155                    my $param;
156                    if ( ref($_[0]) ) {
157                      $param = shift;
158                    } else {
159                      #warn scalar(@_). ": ". join(" / ", @_);
160                      $param = { @_ };
161                    }
162
163                    $param->{_packet} = \''. $autoload{$autoload}. '\';
164
165                    simple_packet($param);
166                  }';
167
168   eval $eval;
169   die $@ if $@;
170
171 }
172
173 sub simple_packet {
174   my $packet = shift;
175   warn "sending ". $packet->{_packet}. " to server"
176     if $DEBUG;
177   socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
178   connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
179   nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
180   SOCK->flush;
181
182   #shoudl trap: Magic number checking on storable file failed at blib/lib/Storable.pm (autosplit into blib/lib/auto/Storable/fd_retrieve.al) line 337, at /usr/local/share/perl/5.6.1/FS/SelfService.pm line 71
183
184   #block until there is a message on socket
185 #  my $w = new IO::Select;
186 #  $w->add(\*SOCK);
187 #  my @wait = $w->can_read;
188
189   warn "reading message from server"
190     if $DEBUG;
191
192   my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
193   die $return->{'_error'} if defined $return->{_error} && $return->{_error};
194
195   warn "returning message to client"
196     if $DEBUG;
197
198   $return;
199 }
200
201 =head1 NAME
202
203 FS::SelfService - Freeside self-service API
204
205 =head1 SYNOPSIS
206
207   # password and shell account changes
208   use FS::SelfService qw(passwd chfn chsh);
209
210   # "my account" functionality
211   use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
212
213   #new-style login with an email address and password
214   # can also be used for svc_acct login, set $emailaddress to username@domain
215   my $rv = login ( { 'email'    => $emailaddress,
216                      'password' => $password,
217                    },
218                  );
219   if ( $rv->{'error'} ) {
220     #handle login error...
221   } else {
222     #successful login
223     $session_id = $rv->{'session_id'};
224   }
225
226   #classic svc_acct-based login with separate username and password
227   my $rv = login( { 'username' => $username,
228                     'domain'   => $domain,
229                     'password' => $password,
230                   }
231                 );
232   if ( $rv->{'error'} ) {
233     #handle login error...
234   } else {
235     #successful login
236     $session_id = $rv->{'session_id'};
237   }
238
239   #svc_phone login with phone number and PIN
240   my $rv = login( { 'username' => $phone_number,
241                     'domain'   => 'svc_phone',
242                     'password' => $pin,
243                   }
244                 );
245   if ( $rv->{'error'} ) {
246     #handle login error...
247   } else {
248     #successful login
249     $session_id = $rv->{'session_id'};
250   }
251
252   my $customer_info = customer_info( { 'session_id' => $session_id } );
253
254   #payment_info and process_payment are available in 1.5+ only
255   my $payment_info = payment_info( { 'session_id' => $session_id } );
256
257   #!!! process_payment example
258
259   #!!! list_pkgs example
260
261   #ordering a package with an svc_acct service
262   my $rv = order_pkg( { 'session_id' => $session_id,
263                         'pkgpart'    => $pkgpart,
264                         'svcpart'    => $svcpart,
265                         'username'   => $username,
266                         'domsvc'     => $domsvc, #svcnum of svc_domain
267                         '_password'  => $password,
268                       }
269                     );
270
271   #!!! ordering a package with an svc_domain service example
272
273   #!!! ordering a package with an svc_phone service example
274
275   #!!! ordering a package with an svc_external service example
276
277   #!!! ordering a package with an svc_pbx service
278
279   #ordering a package with no service
280   my $rv = order_pkg( { 'session_id' => $session_id,
281                         'pkgpart'    => $pkgpart,
282                         'svcpart'    => 'none',
283                       }
284                     );
285
286   #!!! cancel_pkg example
287
288   # signup functionality
289   use FS::SelfService qw( signup_info new_customer new_customer_minimal );
290
291   my $signup_info = signup_info;
292
293   $rv = new_customer( {
294                         'first'            => $first,
295                         'last'             => $last,
296                         'company'          => $company,
297                         'address1'         => $address1,
298                         'address2'         => $address2,
299                         'city'             => $city,
300                         'state'            => $state,
301                         'zip'              => $zip,
302                         'country'          => $country,
303                         'daytime'          => $daytime,
304                         'night'            => $night,
305                         'fax'              => $fax,
306                         'payby'            => $payby,
307                         'payinfo'          => $payinfo,
308                         'paycvv'           => $paycvv,
309                         'paystart_month'   => $paystart_month
310                         'paystart_year'    => $paystart_year,
311                         'payissue'         => $payissue,
312                         'payip'            => $payip
313                         'paydate'          => $paydate,
314                         'payname'          => $payname,
315                         'invoicing_list'   => $invoicing_list,
316                         'referral_custnum' => $referral_custnum,
317                         'agentnum'         => $agentnum,
318                         'pkgpart'          => $pkgpart,
319
320                         'username'         => $username,
321                         '_password'        => $password,
322                         'popnum'           => $popnum,
323                         #OR
324                         'countrycode'      => 1,
325                         'phonenum'         => $phonenum,
326                         'pin'              => $pin,
327                       }
328                     );
329   
330   my $error = $rv->{'error'};
331   if ( $error eq '_decline' ) {
332     print_decline();
333   } elsif ( $error ) {
334     reprint_signup();
335   } else {
336     print_success();
337   }
338
339 =head1 DESCRIPTION
340
341 Use this API to implement your own client "self-service" module.
342
343 If you just want to customize the look of the existing "self-service" module,
344 see XXXX instead.
345
346 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
347
348 =over 4
349
350 =item passwd
351
352 Changes the password for an existing user in svc_acct.  Takes a hash
353 reference with the following keys:
354
355 =over 4
356
357 =item username
358
359 Username of the account (required)
360
361 =item domain
362
363 Domain of the account (required)
364
365 =item old_password
366
367 Old password (required)
368
369 =item new_password
370  
371 New password (required)
372
373 =item new_gecos
374
375 New gecos
376
377 =item new_shell
378
379 New Shell
380
381 =back 
382
383 =item chfn
384
385 =item chsh
386
387 =back
388
389 =head1 "MY ACCOUNT" FUNCTIONS
390
391 =over 4
392
393 =item login HASHREF
394
395 Creates a user session.  Takes a hash reference as parameter with the
396 following keys:
397
398 =over 4
399
400 =item email
401
402 Email address (username@domain), instead of username and domain.  Required for
403 contact-based self-service login, can also be used for svc_acct-based login.
404
405 =item username
406
407 Username
408
409 =item domain
410
411 Domain
412
413 =item password
414
415 Password
416
417 =back
418
419 Returns a hash reference with the following keys:
420
421 =over 4
422
423 =item error
424
425 Empty on success, or an error message on errors.
426
427 =item session_id
428
429 Session identifier for successful logins
430
431 =back
432
433 =item customer_info HASHREF
434
435 Returns general customer information.
436
437 Takes a hash reference as parameter with a single key: B<session_id>
438
439 Returns a hash reference with the following keys:
440
441 =over 4
442
443 =item name
444
445 Customer name
446
447 =item balance
448
449 Balance owed
450
451 =item open
452
453 Array reference of hash references of open inoices.  Each hash reference has
454 the following keys: invnum, date, owed
455
456 =item small_custview
457
458 An HTML fragment containing shipping and billing addresses.
459
460 =item The following fields are also returned
461
462 first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo payname month year invoicing_list postal_invoicing
463
464 =back
465
466 =item edit_info HASHREF
467
468 Takes a hash reference as parameter with any of the following keys:
469
470 first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo paycvv payname month year invoicing_list postal_invoicing
471
472 If a field exists, the customer record is updated with the new value of that
473 field.  If a field does not exist, that field is not changed on the customer
474 record.
475
476 Returns a hash reference with a single key, B<error>, empty on success, or an
477 error message on errors
478
479 =item invoice HASHREF
480
481 Returns an invoice.  Takes a hash reference as parameter with two keys:
482 session_id and invnum
483
484 Returns a hash reference with the following keys:
485
486 =over 4
487
488 =item error
489
490 Empty on success, or an error message on errors
491
492 =item invnum
493
494 Invoice number
495
496 =item invoice_text
497
498 Invoice text
499
500 =back
501
502 =item list_invoices HASHREF
503
504 Returns a list of all customer invoices.  Takes a hash references with a single
505 key, session_id.
506
507 Returns a hash reference with the following keys:
508
509 =over 4
510
511 =item error
512
513 Empty on success, or an error message on errors
514
515 =item invoices
516
517 Reference to array of hash references with the following keys:
518
519 =over 4
520
521 =item invnum
522
523 Invoice ID
524
525 =item _date
526
527 Invoice date, in UNIX epoch time
528
529 =back
530
531 =back
532
533 =item cancel HASHREF
534
535 Cancels this customer.
536
537 Takes a hash reference as parameter with a single key: B<session_id>
538
539 Returns a hash reference with a single key, B<error>, which is empty on
540 success or an error message on errors.
541
542 =item payment_info HASHREF
543
544 Returns information that may be useful in displaying a payment page.
545
546 Takes a hash reference as parameter with a single key: B<session_id>.
547
548 Returns a hash reference with the following keys:
549
550 =over 4
551
552 =item error
553
554 Empty on success, or an error message on errors
555
556 =item balance
557
558 Balance owed
559
560 =item payname
561
562 Exact name on credit card (CARD/DCRD)
563
564 =item address1
565
566 Address line one
567
568 =item address2
569
570 Address line two
571
572 =item city
573
574 City
575
576 =item state
577
578 State
579
580 =item zip
581
582 Zip or postal code
583
584 =item payby
585
586 Customer's current default payment type.
587
588 =item card_type
589
590 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
591
592 =item payinfo
593
594 For CARD/DCRD payment types, the card number
595
596 =item month
597
598 For CARD/DCRD payment types, expiration month
599
600 =item year
601
602 For CARD/DCRD payment types, expiration year
603
604 =item cust_main_county
605
606 County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>).  Note these are not FS::cust_main_county objects, but hash references of columns and values.
607
608 =item states
609
610 Array reference of all states in the current default country.
611
612 =item card_types
613
614 Hash reference of card types; keys are card types, values are the exact strings
615 passed to the process_payment function
616
617 =cut
618
619 #this doesn't actually work yet
620 #
621 #=item paybatch
622 #
623 #Unique transaction identifier (prevents multiple charges), passed to the
624 #process_payment function
625
626 =back
627
628 =item process_payment HASHREF
629
630 Processes a payment and possible change of address or payment type.  Takes a
631 hash reference as parameter with the following keys:
632
633 =over 4
634
635 =item session_id
636
637 Session identifier
638
639 =item amount
640
641 Amount
642
643 =item save
644
645 If true, address and card information entered will be saved for subsequent
646 transactions.
647
648 =item auto
649
650 If true, future credit card payments will be done automatically (sets payby to
651 CARD).  If false, future credit card payments will be done on-demand (sets
652 payby to DCRD).  This option only has meaning if B<save> is set true.  
653
654 =item payname
655
656 Name on card
657
658 =item address1
659
660 Address line one
661
662 =item address2
663
664 Address line two
665
666 =item city
667
668 City
669
670 =item state
671
672 State
673
674 =item zip
675
676 Zip or postal code
677
678 =item country
679
680 Two-letter country code
681
682 =item payinfo
683
684 Card number
685
686 =item month
687
688 Card expiration month
689
690 =item year
691
692 Card expiration year
693
694 =cut
695
696 #this doesn't actually work yet
697 #
698 #=item paybatch
699 #
700 #Unique transaction identifier, returned from the payment_info function.
701 #Prevents multiple charges.
702
703 =back
704
705 Returns a hash reference with a single key, B<error>, empty on success, or an
706 error message on errors.
707
708 =item process_payment_order_pkg
709
710 Combines the B<process_payment> and B<order_pkg> functions in one step.  If the
711 payment processes sucessfully, the package is ordered.  Takes a hash reference
712 as parameter with the keys of both methods.
713
714 Returns a hash reference with a single key, B<error>, empty on success, or an
715 error message on errors.
716
717 =item process_payment_change_pkg
718
719 Combines the B<process_payment> and B<change_pkg> functions in one step.  If the
720 payment processes sucessfully, the package is ordered.  Takes a hash reference
721 as parameter with the keys of both methods.
722
723 Returns a hash reference with a single key, B<error>, empty on success, or an
724 error message on errors.
725
726
727 =item process_payment_order_renew
728
729 Combines the B<process_payment> and B<order_renew> functions in one step.  If
730 the payment processes sucessfully, the renewal is processed.  Takes a hash
731 reference as parameter with the keys of both methods.
732
733 Returns a hash reference with a single key, B<error>, empty on success, or an
734 error message on errors.
735
736 =item list_pkgs
737
738 Returns package information for this customer.  For more detail on services,
739 see L</list_svcs>.
740
741 Takes a hash reference as parameter with a single key: B<session_id>
742
743 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
744
745 =over 4
746
747 =item custnum
748
749 Customer number
750
751 =item error
752
753 Empty on success, or an error message on errors.
754
755 =item cust_pkg HASHREF
756
757 Array reference of hash references, each of which has the fields of a cust_pkg
758 record (see L<FS::cust_pkg>) as well as the fields below.  Note these are not
759 the internal FS:: objects, but hash references of columns and values.
760
761 =over 4
762
763 =item part_pkg fields
764
765 All fields of part_pkg for this specific cust_pkg (be careful with this
766 information - it may reveal more about your available packages than you would
767 like users to know in aggregate) 
768
769 =cut
770
771 #XXX pare part_pkg fields down to a more secure subset
772
773 =item part_svc
774
775 An array of hash references indicating information on unprovisioned services
776 available for provisioning for this specific cust_pkg.  Each has the following
777 keys:
778
779 =over 4
780
781 =item part_svc fields
782
783 All fields of part_svc (be careful with this information - it may reveal more
784 about your available packages than you would like users to know in aggregate) 
785
786 =cut
787
788 #XXX pare part_svc fields down to a more secure subset
789
790 =back
791
792 =item cust_svc
793
794 An array of hash references indicating information on the customer services
795 already provisioned for this specific cust_pkg.  Each has the following keys:
796
797 =over 4
798
799 =item label
800
801 Array reference with three elements: The first element is the name of this service.  The second element is a meaningful user-specific identifier for the service (i.e. username, domain or mail alias).  The last element is the table name of this service.
802
803 =back
804
805 =item svcnum
806
807 Primary key for this service
808
809 =item svcpart
810
811 Service definition (see L<FS::part_svc>)
812
813 =item pkgnum
814
815 Customer package (see L<FS::cust_pkg>)
816
817 =item overlimit
818
819 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
820
821 =back
822
823 =back
824
825 =item list_svcs
826
827 Returns service information for this customer.
828
829 Takes a hash reference as parameter with a single key: B<session_id>
830
831 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
832
833 =over 4
834
835 =item custnum
836
837 Customer number
838
839 =item svcs
840
841 An array of hash references indicating information on all of this customer's
842 services.  Each has the following keys:
843
844 =over 4
845
846 =item svcnum
847
848 Primary key for this service
849
850 =item label
851
852 Name of this service
853
854 =item value
855
856 Meaningful user-specific identifier for the service (i.e. username, domain, or
857 mail alias).
858
859 =back
860
861 Account (svc_acct) services also have the following keys:
862
863 =over 4
864
865 =item username
866
867 Username
868
869 =item email
870
871 username@domain
872
873 =item seconds
874
875 Seconds remaining
876
877 =item upbytes
878
879 Upload bytes remaining
880
881 =item downbytes
882
883 Download bytes remaining
884
885 =item totalbytes
886
887 Total bytes remaining
888
889 =item recharge_amount
890
891 Cost of a recharge
892
893 =item recharge_seconds
894
895 Number of seconds gained by recharge
896
897 =item recharge_upbytes
898
899 Number of upload bytes gained by recharge
900
901 =item recharge_downbytes
902
903 Number of download bytes gained by recharge
904
905 =item recharge_totalbytes
906
907 Number of total bytes gained by recharge
908
909 =back
910
911 =back
912
913 =item order_pkg
914
915 Orders a package for this customer.
916
917 Takes a hash reference as parameter with the following keys:
918
919 =over 4
920
921 =item session_id
922
923 Session identifier
924
925 =item pkgpart
926
927 Package to order (see L<FS::part_pkg>).
928
929 =item quantity
930
931 Quantity for this package order (default 1).
932
933 =item locationnum
934
935 Optional locationnum for this package order, for existing locations.
936
937 Or, for new locations, pass the following fields: address1*, address2, city*,
938 county, state*, zip*, country.  (* = required in this case)
939
940 =item address1
941
942 =item address 2
943
944 =item city
945
946 =item 
947
948 =item svcpart
949
950 Service to order (see L<FS::part_svc>).
951
952 Normally optional; required only to provision a non-svc_acct service, or if the
953 package definition does not contain one svc_acct service definition with
954 quantity 1 (it may contain others with quantity >1).  A svcpart of "none" can
955 also be specified to indicate that no initial service should be provisioned.
956
957 =back
958
959 Fields used when provisioning an svc_acct service:
960
961 =over 4
962
963 =item username
964
965 Username
966
967 =item _password
968
969 Password
970
971 =item sec_phrase
972
973 Optional security phrase
974
975 =item popnum
976
977 Optional Access number number
978
979 =back
980
981 Fields used when provisioning an svc_domain service:
982
983 =over 4
984
985 =item domain
986
987 Domain
988
989 =back
990
991 Fields used when provisioning an svc_phone service:
992
993 =over 4
994
995 =item phonenum
996
997 Phone number
998
999 =item pin
1000
1001 Voicemail PIN
1002
1003 =item sip_password
1004
1005 SIP password
1006
1007 =back
1008
1009 Fields used when provisioning an svc_external service:
1010
1011 =over 4
1012
1013 =item id
1014
1015 External numeric ID.
1016
1017 =item title
1018
1019 External text title.
1020
1021 =back
1022
1023 Fields used when provisioning an svc_pbx service:
1024
1025 =over 4
1026
1027 =item id
1028
1029 Numeric ID.
1030
1031 =item name
1032
1033 Text name.
1034
1035 =back
1036
1037 Returns a hash reference with a single key, B<error>, empty on success, or an
1038 error message on errors.  The special error '_decline' is returned for
1039 declined transactions.
1040
1041 =item change_pkg
1042
1043 Changes a package for this customer.
1044
1045 Takes a hash reference as parameter with the following keys:
1046
1047 =over 4
1048
1049 =item session_id
1050
1051 Session identifier
1052
1053 =item pkgnum
1054
1055 Existing customer package.
1056
1057 =item pkgpart
1058
1059 New package to order (see L<FS::part_pkg>).
1060
1061 =item quantity
1062
1063 Quantity for this package order (default 1).
1064
1065 =back
1066
1067 Returns a hash reference with the following keys:
1068
1069 =over 4
1070
1071 =item error
1072
1073 Empty on success, or an error message on errors.  
1074
1075 =item pkgnum
1076
1077 On success, the new pkgnum
1078
1079 =back
1080
1081
1082 =item renew_info
1083
1084 Provides useful info for early renewals.
1085
1086 Takes a hash reference as parameter with the following keys:
1087
1088 =over 4
1089
1090 =item session_id
1091
1092 Session identifier
1093
1094 =back
1095
1096 Returns a hash reference.  On errors, it contains a single key, B<error>, with
1097 the error message.  Otherwise, contains a single key, B<dates>, pointing to
1098 an array refernce of hash references.  Each hash reference contains the
1099 following keys:
1100
1101 =over 4
1102
1103 =item bill_date
1104
1105 (Future) Bill date.  Indicates a future date for which billing could be run.
1106 Specified as a integer UNIX timestamp.  Pass this value to the B<order_renew>
1107 function.
1108
1109 =item bill_date_pretty
1110
1111 (Future) Bill date as a human-readable string.  (Convenience for display;
1112 subject to change, so best not to parse for the date.)
1113
1114 =item amount
1115
1116 Base amount which will be charged if renewed early as of this date.
1117
1118 =item renew_date
1119
1120 Renewal date; i.e. even-futher future date at which the customer will be paid
1121 through if the early renewal is completed with the given B<bill-date>.
1122 Specified as a integer UNIX timestamp.
1123
1124 =item renew_date_pretty
1125
1126 Renewal date as a human-readable string.  (Convenience for display;
1127 subject to change, so best not to parse for the date.)
1128
1129 =item pkgnum
1130
1131 Package that will be renewed.
1132
1133 =item expire_date
1134
1135 Expiration date of the package that will be renewed.
1136
1137 =item expire_date_pretty
1138
1139 Expiration date of the package that will be renewed, as a human-readable
1140 string.  (Convenience for display; subject to change, so best not to parse for
1141 the date.)
1142
1143 =back
1144
1145 =item order_renew
1146
1147 Renews this customer early; i.e. runs billing for this customer in advance.
1148
1149 Takes a hash reference as parameter with the following keys:
1150
1151 =over 4
1152
1153 =item session_id
1154
1155 Session identifier
1156
1157 =item date
1158
1159 Integer date as returned by the B<renew_info> function, indicating the advance
1160 date for which to run billing.
1161
1162 =back
1163
1164 Returns a hash reference with a single key, B<error>, empty on success, or an
1165 error message on errors.
1166
1167 =item cancel_pkg
1168
1169 Cancels a package for this customer.
1170
1171 Takes a hash reference as parameter with the following keys:
1172
1173 =over 4
1174
1175 =item session_id
1176
1177 Session identifier
1178
1179 =item pkgpart
1180
1181 pkgpart of package to cancel
1182
1183 =back
1184
1185 Returns a hash reference with a single key, B<error>, empty on success, or an
1186 error message on errors.
1187
1188 =item provision_acct 
1189
1190 Provisions an account (svc_acct).
1191
1192 Takes a hash references as parameter with the following keys:
1193
1194 =over 4
1195
1196 =item session_id
1197
1198 Session identifier
1199
1200 =item pkgnum
1201
1202 pkgnum of package into which this service is provisioned
1203
1204 =item svcpart
1205
1206 svcpart or service definition to provision
1207
1208 =item username
1209
1210 =item domsvc
1211
1212 =item _password
1213
1214 =back
1215
1216 =item provision_phone
1217
1218 Provisions a phone number (svc_phone).
1219
1220 Takes a hash references as parameter with the following keys:
1221
1222 =over 4
1223
1224 =item session_id
1225
1226 Session identifier
1227
1228 =item pkgnum
1229
1230 pkgnum of package into which this service is provisioned
1231
1232 =item svcpart
1233
1234 svcpart or service definition to provision
1235
1236 =item countrycode
1237
1238 =item phonenum
1239
1240 =item address1
1241
1242 =item address2
1243
1244 =item city
1245
1246 =item county
1247
1248 =item state
1249
1250 =item zip
1251
1252 =item country
1253
1254 E911 Address (optional)
1255
1256 =back
1257
1258 =item provision_external
1259
1260 Provisions an external service (svc_external).
1261
1262 Takes a hash references as parameter with the following keys:
1263
1264 =over 4
1265
1266 =item session_id
1267
1268 Session identifier
1269
1270 =item pkgnum
1271
1272 pkgnum of package into which this service is provisioned
1273
1274 =item svcpart
1275
1276 svcpart or service definition to provision
1277
1278 =item id
1279
1280 =item title
1281
1282 =back
1283
1284 =back
1285
1286 =head1 SIGNUP FUNCTIONS
1287
1288 =over 4
1289
1290 =item signup_info HASHREF
1291
1292 Takes a hash reference as parameter with the following keys:
1293
1294 =over 4
1295
1296 =item session_id - Optional agent/reseller interface session
1297
1298 =back
1299
1300 Returns a hash reference containing information that may be useful in
1301 displaying a signup page.  The hash reference contains the following keys:
1302
1303 =over 4
1304
1305 =item cust_main_county
1306
1307 County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>).  Note these are not FS::cust_main_county objects, but hash references of columns and values.
1308
1309 =item part_pkg
1310
1311 Available packages - array reference of hash references, each of which has the fields of a part_pkg record (see L<FS::part_pkg>).  Each hash reference also has an additional 'payby' field containing an array reference of acceptable payment types specific to this package (see below and L<FS::part_pkg/payby>).  Note these are not FS::part_pkg objects, but hash references of columns and values.  Requires the 'signup_server-default_agentnum' configuration value to be set, or
1312 an agentnum specified explicitly via reseller interface session_id in the
1313 options.
1314
1315 =item agent
1316
1317 Array reference of hash references, each of which has the fields of an agent record (see L<FS::agent>).  Note these are not FS::agent objects, but hash references of columns and values.
1318
1319 =item agentnum2part_pkg
1320
1321 Hash reference; keys are agentnums, values are array references of available packages for that agent, in the same format as the part_pkg arrayref above.
1322
1323 =item svc_acct_pop
1324
1325 Access numbers - array reference of hash references, each of which has the fields of an svc_acct_pop record (see L<FS::svc_acct_pop>).  Note these are not FS::svc_acct_pop objects, but hash references of columns and values.
1326
1327 =item security_phrase
1328
1329 True if the "security_phrase" feature is enabled
1330
1331 =item payby
1332
1333 Array reference of acceptable payment types for signup
1334
1335 =over 4
1336
1337 =item CARD
1338
1339 credit card - automatic
1340
1341 =item DCRD
1342
1343 credit card - on-demand - version 1.5+ only
1344
1345 =item CHEK
1346
1347 electronic check - automatic
1348
1349 =item DCHK
1350
1351 electronic check - on-demand - version 1.5+ only
1352
1353 =item LECB
1354
1355 Phone bill billing
1356
1357 =item BILL
1358
1359 billing, not recommended for signups
1360
1361 =item COMP
1362
1363 free, definitely not recommended for signups
1364
1365 =item PREPAY
1366
1367 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1368
1369 =back
1370
1371 =item cvv_enabled
1372
1373 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1374
1375 =item msgcat
1376
1377 Hash reference of message catalog values, to support error message customization.  Currently available keys are: passwords_dont_match, invalid_card, unknown_card_type, and not_a (as in "Not a Discover card").  Values are configured in the web interface under "View/Edit message catalog".
1378
1379 =item statedefault
1380
1381 Default state
1382
1383 =item countrydefault
1384
1385 Default country
1386
1387 =back
1388
1389 =item new_customer_minimal HASHREF
1390
1391 Creates a new customer.
1392
1393 Current differences from new_customer: An address is not required.  promo_code
1394 and reg_code are not supported.  If invoicing_list and _password is passed, a
1395 contact will be created with self-service access (no pkgpart or username is
1396 necessary).  No initial billing is run (this may change in a future version).
1397
1398 Takes a hash reference as parameter with the following keys:
1399
1400 =over 4
1401
1402 =item first
1403
1404 first name (required)
1405
1406 =item last
1407
1408 last name (required)
1409
1410 =item ss
1411
1412 (not typically collected; mostly used for ACH transactions)
1413
1414 =item company
1415
1416 Company name
1417
1418 =item address1
1419
1420 Address line one
1421
1422 =item address2
1423
1424 Address line two
1425
1426 =item city
1427
1428 City
1429
1430 =item county
1431
1432 County
1433
1434 =item state
1435
1436 State
1437
1438 =item zip
1439
1440 Zip or postal code
1441
1442 =item daytime
1443
1444 Daytime phone number
1445
1446 =item night
1447
1448 Evening phone number
1449
1450 =item fax
1451
1452 Fax number
1453
1454 =item payby
1455
1456 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1457
1458 =item payinfo
1459
1460 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1461
1462 =item paycvv
1463
1464 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1465
1466 =item paydate
1467
1468 Expiration date for CARD/DCRD
1469
1470 =item payname
1471
1472 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1473
1474 =item invoicing_list
1475
1476 comma-separated list of email addresses for email invoices.  The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses),
1477
1478 =item referral_custnum
1479
1480 referring customer number
1481
1482 =item agentnum
1483
1484 Agent number
1485
1486 =item pkgpart
1487
1488 pkgpart of initial package
1489
1490 =item username
1491
1492 Username
1493
1494 =item _password
1495
1496 Password
1497
1498 =item sec_phrase
1499
1500 Security phrase
1501
1502 =item popnum
1503
1504 Access number (index, not the literal number)
1505
1506 =item countrycode
1507
1508 Country code (to be provisioned as a service)
1509
1510 =item phonenum
1511
1512 Phone number (to be provisioned as a service)
1513
1514 =item pin
1515
1516 Voicemail PIN
1517
1518 =back
1519
1520 Returns a hash reference with the following keys:
1521
1522 =over 4
1523
1524 =item error
1525
1526 Empty on success, or an error message on errors.  The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Configuration | View/Edit message catalog)
1527
1528 =back
1529
1530 =item new_customer HASHREF
1531
1532 Creates a new customer.  Takes a hash reference as parameter with the
1533 following keys:
1534
1535 =over 4
1536
1537 =item first
1538
1539 first name (required)
1540
1541 =item last
1542
1543 last name (required)
1544
1545 =item ss
1546
1547 (not typically collected; mostly used for ACH transactions)
1548
1549 =item company
1550
1551 Company name
1552
1553 =item address1 (required)
1554
1555 Address line one
1556
1557 =item address2
1558
1559 Address line two
1560
1561 =item city (required)
1562
1563 City
1564
1565 =item county
1566
1567 County
1568
1569 =item state (required)
1570
1571 State
1572
1573 =item zip (required)
1574
1575 Zip or postal code
1576
1577 =item daytime
1578
1579 Daytime phone number
1580
1581 =item night
1582
1583 Evening phone number
1584
1585 =item fax
1586
1587 Fax number
1588
1589 =item payby
1590
1591 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1592
1593 =item payinfo
1594
1595 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1596
1597 =item paycvv
1598
1599 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1600
1601 =item paydate
1602
1603 Expiration date for CARD/DCRD
1604
1605 =item payname
1606
1607 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1608
1609 =item invoicing_list
1610
1611 comma-separated list of email addresses for email invoices.  The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses),
1612
1613 =item referral_custnum
1614
1615 referring customer number
1616
1617 =item agentnum
1618
1619 Agent number
1620
1621 =item pkgpart
1622
1623 pkgpart of initial package
1624
1625 =item username
1626
1627 Username
1628
1629 =item _password
1630
1631 Password
1632
1633 =item sec_phrase
1634
1635 Security phrase
1636
1637 =item popnum
1638
1639 Access number (index, not the literal number)
1640
1641 =item countrycode
1642
1643 Country code (to be provisioned as a service)
1644
1645 =item phonenum
1646
1647 Phone number (to be provisioned as a service)
1648
1649 =item pin
1650
1651 Voicemail PIN
1652
1653 =back
1654
1655 Returns a hash reference with the following keys:
1656
1657 =over 4
1658
1659 =item error
1660
1661 Empty on success, or an error message on errors.  The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Configuration | View/Edit message catalog)
1662
1663 =back
1664
1665 =item regionselector HASHREF | LIST
1666
1667 Takes as input a hashref or list of key/value pairs with the following keys:
1668
1669 =over 4
1670
1671 =item selected_county
1672
1673 Currently selected county
1674
1675 =item selected_state
1676
1677 Currently selected state
1678
1679 =item selected_country
1680
1681 Currently selected country
1682
1683 =item prefix
1684
1685 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1686
1687 =item onchange
1688
1689 Specify a javascript subroutine to call on changes
1690
1691 =item default_state
1692
1693 Default state
1694
1695 =item default_country
1696
1697 Default country
1698
1699 =item locales
1700
1701 An arrayref of hash references specifying regions.  Normally you can just pass the value of the I<cust_main_county> field returned by B<signup_info>.
1702
1703 =back
1704
1705 Returns a list consisting of three HTML fragments for county selection,
1706 state selection and country selection, respectively.
1707
1708 =cut
1709
1710 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1711 sub regionselector {
1712   my $param;
1713   if ( ref($_[0]) ) {
1714     $param = shift;
1715   } else {
1716     $param = { @_ };
1717   }
1718   $param->{'selected_country'} ||= $param->{'default_country'};
1719   $param->{'selected_state'} ||= $param->{'default_state'};
1720
1721   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1722
1723   my $countyflag = 0;
1724
1725   my %cust_main_county;
1726
1727 #  unless ( @cust_main_county ) { #cache 
1728     #@cust_main_county = qsearch('cust_main_county', {} );
1729     #foreach my $c ( @cust_main_county ) {
1730     foreach my $c ( @{ $param->{'locales'} } ) {
1731       #$countyflag=1 if $c->county;
1732       $countyflag=1 if $c->{county};
1733       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1734       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1735       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1736     }
1737 #  }
1738   $countyflag=1 if $param->{selected_county};
1739
1740   my $script_html = <<END;
1741     <SCRIPT>
1742     function opt(what,value,text) {
1743       var optionName = new Option(text, value, false, false);
1744       var length = what.length;
1745       what.options[length] = optionName;
1746     }
1747     function ${prefix}country_changed(what) {
1748       country = what.options[what.selectedIndex].text;
1749       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1750           what.form.${prefix}state.options[i] = null;
1751 END
1752       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1753
1754   foreach my $country ( sort keys %cust_main_county ) {
1755     $script_html .= "\nif ( country == \"$country\" ) {\n";
1756     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1757       my $text = $state || '(n/a)';
1758       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1759     }
1760     $script_html .= "}\n";
1761   }
1762
1763   $script_html .= <<END;
1764     }
1765     function ${prefix}state_changed(what) {
1766 END
1767
1768   if ( $countyflag ) {
1769     $script_html .= <<END;
1770       state = what.options[what.selectedIndex].text;
1771       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1772       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1773           what.form.${prefix}county.options[i] = null;
1774 END
1775
1776     foreach my $country ( sort keys %cust_main_county ) {
1777       $script_html .= "\nif ( country == \"$country\" ) {\n";
1778       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1779         $script_html .= "\nif ( state == \"$state\" ) {\n";
1780           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1781           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1782             my $text = $county || '(n/a)';
1783             $script_html .=
1784               qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1785           }
1786         $script_html .= "}\n";
1787       }
1788       $script_html .= "}\n";
1789     }
1790   }
1791
1792   $script_html .= <<END;
1793     }
1794     </SCRIPT>
1795 END
1796
1797   my $county_html = $script_html;
1798   if ( $countyflag ) {
1799     $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1800     foreach my $county ( 
1801       sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1802     ) {
1803       my $text = $county || '(n/a)';
1804       $county_html .= qq!<OPTION VALUE="$county"!.
1805                       ($county eq $param->{'selected_county'} ? 
1806                         ' SELECTED>' : 
1807                         '>'
1808                       ).
1809                       $text.
1810                       '</OPTION>';
1811     }
1812     $county_html .= '</SELECT>';
1813   } else {
1814     $county_html .=
1815       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1816   }
1817
1818   my $state_html = qq!<SELECT NAME="${prefix}state" !.
1819                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1820   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1821     my $text = $state || '(n/a)';
1822     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1823     $state_html .= "\n<OPTION $selected VALUE=\"$state\">$text</OPTION>"
1824   }
1825   $state_html .= '</SELECT>';
1826
1827   my $country_html = '';
1828   if ( scalar( keys %cust_main_county ) > 1 )  {
1829
1830     $country_html = qq(<SELECT NAME="${prefix}country" ).
1831                     qq(onChange="${prefix}country_changed(this); ).
1832                                  $param->{'onchange'}.
1833                                '"'.
1834                       '>';
1835     my $countrydefault = $param->{default_country} || 'US';
1836     foreach my $country (
1837       sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1838         keys %cust_main_county
1839     ) {
1840       my $selected = $country eq $param->{'selected_country'}
1841                        ? ' SELECTED'
1842                        : '';
1843       $country_html .= "\n<OPTION $selected>$country</OPTION>"
1844     }
1845     $country_html .= '</SELECT>';
1846   } else {
1847
1848     $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1849                             ' VALUE="'. (keys %cust_main_county )[0]. '">';
1850
1851   }
1852
1853   ($county_html, $state_html, $country_html);
1854
1855 }
1856
1857 sub regionselector_hashref {
1858   my ($county_html, $state_html, $country_html) = regionselector(@_);
1859   {
1860     'county_html'  => $county_html,
1861     'state_html'   => $state_html,
1862     'country_html' => $country_html,
1863   };
1864 }
1865
1866 =item location_form HASHREF | LIST
1867
1868 Takes as input a hashref or list of key/value pairs with the following keys:
1869
1870 =over 4
1871
1872 =item session_id
1873
1874 Current customer session_id
1875
1876 =item no_asterisks
1877
1878 Omit red asterisks from required fields.
1879
1880 =item address1_label
1881
1882 Label for first address line.
1883
1884 =back
1885
1886 Returns an HTML fragment for a location form (address, city, state, zip,
1887 country)
1888
1889 =cut
1890
1891 sub location_form {
1892   my $param;
1893   if ( ref($_[0]) ) {
1894     $param = shift;
1895   } else {
1896     $param = { @_ };
1897   }
1898
1899   my $session_id = delete $param->{'session_id'};
1900
1901   my $rv = mason_comp( 'session_id' => $session_id,
1902                        'comp'       => '/elements/location.html',
1903                        'args'       => [ %$param ],
1904                      );
1905
1906   #hmm.
1907   $rv->{'error'} || $rv->{'output'};
1908
1909 }
1910
1911
1912 #=item expselect HASHREF | LIST
1913 #
1914 #Takes as input a hashref or list of key/value pairs with the following keys:
1915 #
1916 #=over 4
1917 #
1918 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1919 #
1920 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1921 #
1922 #=back
1923
1924 =item expselect PREFIX [ DATE ]
1925
1926 Takes as input a unique prefix string and the current expiration date, in
1927 yyyy-mm-dd or m-d-yyyy format
1928
1929 Returns an HTML fragments for expiration date selection.
1930
1931 =cut
1932
1933 sub expselect {
1934   #my $param;
1935   #if ( ref($_[0]) ) {
1936   #  $param = shift;
1937   #} else {
1938   #  $param = { @_ };
1939   #my $prefix = $param->{'prefix'};
1940   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1941   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
1942   my $prefix = shift;
1943   my $date = scalar(@_) ? shift : '';
1944
1945   my( $m, $y ) = ( 0, 0 );
1946   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1947     ( $m, $y ) = ( $2, $1 );
1948   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1949     ( $m, $y ) = ( $1, $3 );
1950   }
1951   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1952   for ( 1 .. 12 ) {
1953     $return .= qq!<OPTION VALUE="$_"!;
1954     $return .= " SELECTED" if $_ == $m;
1955     $return .= ">$_";
1956   }
1957   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1958   my @t = localtime;
1959   my $thisYear = $t[5] + 1900;
1960   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1961     $return .= qq!<OPTION VALUE="$_"!;
1962     $return .= " SELECTED" if $_ == $y;
1963     $return .= ">$_";
1964   }
1965   $return .= "</SELECT>";
1966
1967   $return;
1968 }
1969
1970 =item popselector HASHREF | LIST
1971
1972 Takes as input a hashref or list of key/value pairs with the following keys:
1973
1974 =over 4
1975
1976 =item popnum
1977
1978 Access number number
1979
1980 =item pops
1981
1982 An arrayref of hash references specifying access numbers.  Normally you can just pass the value of the I<svc_acct_pop> field returned by B<signup_info>.
1983
1984 =back
1985
1986 Returns an HTML fragment for access number selection.
1987
1988 =cut
1989
1990 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1991 sub popselector {
1992   my $param;
1993   if ( ref($_[0]) ) {
1994     $param = shift;
1995   } else {
1996     $param = { @_ };
1997   }
1998   my $popnum = $param->{'popnum'};
1999   my $pops = $param->{'pops'};
2000
2001   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2002   return $pops->[0]{city}. ', '. $pops->[0]{state}.
2003          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2004          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2005     if scalar(@$pops) == 1;
2006
2007   my %pop = ();
2008   my %popnum2pop = ();
2009   foreach (@$pops) {
2010     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2011     $popnum2pop{$_->{popnum}} = $_;
2012   }
2013
2014   my $text = <<END;
2015     <SCRIPT>
2016     function opt(what,href,text) {
2017       var optionName = new Option(text, href, false, false)
2018       var length = what.length;
2019       what.options[length] = optionName;
2020     }
2021 END
2022
2023   my $init_popstate = $param->{'init_popstate'};
2024   if ( $init_popstate ) {
2025     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2026              $init_popstate. '">';
2027   } else {
2028     $text .= <<END;
2029       function acstate_changed(what) {
2030         state = what.options[what.selectedIndex].text;
2031         what.form.popac.options.length = 0
2032         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2033 END
2034   } 
2035
2036   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2037   foreach my $state ( sort { $a cmp $b } @states ) {
2038     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2039
2040     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2041       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2042       if ($ac eq $param->{'popac'}) {
2043         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2044       }
2045     }
2046     $text .= "}\n" unless $init_popstate;
2047   }
2048   $text .= "popac_changed(what.form.popac)}\n";
2049
2050   $text .= <<END;
2051   function popac_changed(what) {
2052     ac = what.options[what.selectedIndex].text;
2053     what.form.popnum.options.length = 0;
2054     what.form.popnum.options[0] = new Option("City", "-1", false, true);
2055
2056 END
2057
2058   foreach my $state ( @states ) {
2059     foreach my $popac ( keys %{ $pop{$state} } ) {
2060       $text .= "\nif ( ac == \"$popac\" ) {\n";
2061
2062       foreach my $pop ( @{$pop{$state}->{$popac}}) {
2063         my $o_popnum = $pop->{popnum};
2064         my $poptext =  $pop->{city}. ', '. $pop->{state}.
2065                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2066
2067         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2068         if ($popnum == $o_popnum) {
2069           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2070         }
2071       }
2072       $text .= "}\n";
2073     }
2074   }
2075
2076
2077   $text .= "}\n</SCRIPT>\n";
2078
2079   $param->{'acstate'} = '' unless defined($param->{'acstate'});
2080
2081   $text .=
2082     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2083     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2084   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2085            ">$_" foreach sort { $a cmp $b } @states;
2086   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
2087
2088   $text .=
2089     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2090     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2091
2092   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2093
2094
2095   #comment this block to disable initial list polulation
2096   my @initial_select = ();
2097   if ( scalar( @$pops ) > 100 ) {
2098     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2099   } else {
2100     @initial_select = @$pops;
2101   }
2102   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2103     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2104              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2105              $pop->{city}. ', '. $pop->{state}.
2106                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2107   }
2108
2109   $text .= qq!</SELECT></TD></TR></TABLE>!;
2110
2111   $text;
2112
2113 }
2114
2115 =item domainselector HASHREF | LIST
2116
2117 Takes as input a hashref or list of key/value pairs with the following keys:
2118
2119 =over 4
2120
2121 =item pkgnum
2122
2123 Package number
2124
2125 =item domsvc
2126
2127 Service number of the selected item.
2128
2129 =back
2130
2131 Returns an HTML fragment for domain selection.
2132
2133 =cut
2134
2135 sub domainselector {
2136   my $param;
2137   if ( ref($_[0]) ) {
2138     $param = shift;
2139   } else {
2140     $param = { @_ };
2141   }
2142   my $domsvc= $param->{'domsvc'};
2143   my $rv = 
2144       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2145   my $domains = $rv->{'domains'};
2146   $domsvc = $rv->{'domsvc'} unless $domsvc;
2147
2148   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2149     unless scalar(keys %$domains);
2150
2151   if (scalar(keys %$domains) == 1) {
2152     my $key;
2153     foreach(keys %$domains) {
2154       $key = $_;
2155     }
2156     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2157            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2158   }
2159
2160   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2161
2162   $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2163
2164   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2165     $text .= qq!<OPTION VALUE="!. $domain. '"'.
2166              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2167              $domains->{$domain};
2168   }
2169
2170   $text .= qq!</SELECT></TD></TR>!;
2171
2172   $text;
2173
2174 }
2175
2176 =item didselector HASHREF | LIST
2177
2178 Takes as input a hashref or list of key/value pairs with the following keys:
2179
2180 =over 4
2181
2182 =item field
2183
2184 Field name for the returned HTML fragment.
2185
2186 =item svcpart
2187
2188 Service definition (see L<FS::part_svc>)
2189
2190 =back
2191
2192 Returns an HTML fragment for DID selection.
2193
2194 =cut
2195
2196 sub didselector {
2197   my $param;
2198   if ( ref($_[0]) ) {
2199     $param = shift;
2200   } else {
2201     $param = { @_ };
2202   }
2203
2204   my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2205                        'args'=>[ %$param ],
2206                      );
2207
2208   #hmm.
2209   $rv->{'error'} || $rv->{'output'};
2210
2211 }
2212
2213 =back
2214
2215 =head1 RESELLER FUNCTIONS
2216
2217 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2218 with their active session, and the B<customer_info> and B<order_pkg> functions
2219 with their active session and an additional I<custnum> parameter.
2220
2221 For the most part, development of the reseller web interface has been
2222 superceded by agent-virtualized access to the backend.
2223
2224 =over 4
2225
2226 =item agent_login
2227
2228 Agent login
2229
2230 =item agent_info
2231
2232 Agent info
2233
2234 =item agent_list_customers
2235
2236 List agent's customers.
2237
2238 =back
2239
2240 =head1 BUGS
2241
2242 =head1 SEE ALSO
2243
2244 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
2245
2246 =cut
2247
2248 1;
2249