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