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