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