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