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