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