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