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