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