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