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