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