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