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