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