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