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