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