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