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