POD cleanup
[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
598
599 Name of this service
600
601 =item
602
603 Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)
604
605 =item
606
607 Table name of this service
608
609 =back
610
611 =item svcnum
612
613 Primary key for this service
614
615 =item svcpart
616
617 Service definition (part_pkg)
618
619 =item pkgnum
620
621 Customer package (cust_pkg)
622
623 =item overlimit
624
625 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
626
627 =back
628
629 =back
630
631 =item error
632
633 Empty on success, or an error message on errors.
634
635 =back
636
637 =item list_svcs
638
639 Returns service information for this customer.
640
641 Takes a hash reference as parameter with a single key: B<session_id>
642
643 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
644
645 =over 4
646
647 =item custnum
648
649 Customer number
650
651 =item svcs
652
653 An array of hash references indicating information on all of this customer's
654 services.  Each has the following keys:
655
656 =over 4
657
658 =item svcnum
659
660 Primary key for this service
661
662 =item label
663
664 Name of this service
665
666 =item value
667
668 Meaningful user-specific identifier for the service (i.e. username, domain, or
669 mail alias).
670
671 =back
672
673 Account (svc_acct) services also have the following keys:
674
675 =item username
676
677 Username
678
679 =item email
680
681 username@domain
682
683 =item seconds
684
685 Seconds remaining
686
687 =item upbytes
688
689 Upload bytes remaining
690
691 =item downbytes
692
693 Download bytes remaining
694
695 =item totalbytes
696
697 Total bytes remaining
698
699 =item recharge_amount
700
701 Cost of a recharge
702
703 =item recharge_seconds
704
705 Number of seconds gained by recharge
706
707 =item recharge_upbytes
708
709 Number of upload bytes gained by recharge
710
711 =item recharge_downbytes
712
713 Number of download bytes gained by recharge
714
715 =item recharge_totalbytes
716
717 Number of total bytes gained by recharge
718
719 =back
720
721 =back
722
723 =item order_pkg
724
725 Orders a package for this customer.
726
727 Takes a hash reference as parameter with the following keys:
728
729 =over 4
730
731 =item session_id
732
733 Session identifier
734
735 =item pkgpart
736
737 pkgpart of package to order
738
739 =item svcpart
740
741 optional svcpart, required only if the package definition does not contain
742 one svc_acct service definition with quantity 1 (it may contain others with
743 quantity >1)
744
745 =item username
746
747 Username
748
749 =item _password
750
751 Password
752
753 =item sec_phrase
754
755 Optional security phrase
756
757 =item popnum
758
759 Optional Access number number
760
761 =back
762
763 Returns a hash reference with a single key, B<error>, empty on success, or an
764 error message on errors.  The special error '_decline' is returned for
765 declined transactions.
766
767 =item cancel_pkg
768
769 Cancels a package for this customer.
770
771 Takes a hash reference as parameter with the following keys:
772
773 =over 4
774
775 =item session_id
776
777 Session identifier
778
779 =item pkgpart
780
781 pkgpart of package to cancel
782
783 =back
784
785 Returns a hash reference with a single key, B<error>, empty on success, or an
786 error message on errors.
787
788 =back
789
790 =head1 SIGNUP FUNCTIONS
791
792 =over 4
793
794 =item signup_info HASHREF
795
796 Takes a hash reference as parameter with the following keys:
797
798 =over 4
799
800 =item session_id - Optional agent/reseller interface session
801
802 =back
803
804 Returns a hash reference containing information that may be useful in
805 displaying a signup page.  The hash reference contains the following keys:
806
807 =over 4
808
809 =item cust_main_county
810
811 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.
812
813 =item part_pkg
814
815 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
816 an agentnum specified explicitly via reseller interface session_id in the
817 options.
818
819 =item agent
820
821 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.
822
823 =item agentnum2part_pkg
824
825 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.
826
827 =item svc_acct_pop
828
829 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.
830
831 =item security_phrase
832
833 True if the "security_phrase" feature is enabled
834
835 =item payby
836
837 Array reference of acceptable payment types for signup
838
839 =over 4
840
841 =item CARD
842
843 credit card - automatic
844
845 =item DCRD
846
847 credit card - on-demand - version 1.5+ only
848
849 =item CHEK
850
851 electronic check - automatic
852
853 =item DCHK
854
855 electronic check - on-demand - version 1.5+ only
856
857 =item LECB
858
859 Phone bill billing
860
861 =item BILL
862
863 billing, not recommended for signups
864
865 =item COMP
866
867 free, definitely not recommended for signups
868
869 =item PREPAY
870
871 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
872
873 =back
874
875 =item cvv_enabled
876
877 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
878
879 =item msgcat
880
881 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".
882
883 =item statedefault
884
885 Default state
886
887 =item countrydefault
888
889 Default country
890
891 =back
892
893 =item new_customer HASHREF
894
895 Creates a new customer.  Takes a hash reference as parameter with the
896 following keys:
897
898 =over 4
899
900 =item first
901
902 first name (required)
903
904 =item last
905
906 last name (required)
907
908 =item ss
909
910 (not typically collected; mostly used for ACH transactions)
911
912 =item company
913
914 Company name
915
916 =item address1 (required)
917
918 Address line one
919
920 =item address2
921
922 Address line two
923
924 =item city (required)
925
926 City
927
928 =item county
929
930 County
931
932 =item state (required)
933
934 State
935
936 =item zip (required)
937
938 Zip or postal code
939
940 =item daytime
941
942 Daytime phone number
943
944 =item night
945
946 Evening phone number
947
948 =item fax
949
950 Fax number
951
952 =item payby
953
954 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
955
956 =item payinfo
957
958 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
959
960 =item paycvv
961
962 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
963
964 =item paydate
965
966 Expiration date for CARD/DCRD
967
968 =item payname
969
970 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
971
972 =item invoicing_list
973
974 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),
975
976 =item referral_custnum
977
978 referring customer number
979
980 =item pkgpart
981
982 pkgpart of initial package
983
984 =item username
985
986 Username
987
988 =item _password
989
990 Password
991
992 =item sec_phrase
993
994 Security phrase
995
996 =item popnum
997
998 Access number (index, not the literal number)
999
1000 =item agentnum
1001
1002 Agent number
1003
1004 =back
1005
1006 Returns a hash reference with the following keys:
1007
1008 =over 4
1009
1010 =item error
1011
1012 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)
1013
1014 =back
1015
1016 =item regionselector HASHREF | LIST
1017
1018 Takes as input a hashref or list of key/value pairs with the following keys:
1019
1020 =over 4
1021
1022 =item selected_county
1023
1024 Currently selected county
1025
1026 =item selected_state
1027
1028 Currently selected state
1029
1030 =item selected_country
1031
1032 Currently selected country
1033
1034 =item prefix
1035
1036 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1037
1038 =item onchange
1039
1040 Specify a javascript subroutine to call on changes
1041
1042 =item default_state
1043
1044 Default state
1045
1046 =item default_country
1047
1048 Default country
1049
1050 =item locales
1051
1052 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>.
1053
1054 =back
1055
1056 Returns a list consisting of three HTML fragments for county selection,
1057 state selection and country selection, respectively.
1058
1059 =cut
1060
1061 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1062 sub regionselector {
1063   my $param;
1064   if ( ref($_[0]) ) {
1065     $param = shift;
1066   } else {
1067     $param = { @_ };
1068   }
1069   $param->{'selected_country'} ||= $param->{'default_country'};
1070   $param->{'selected_state'} ||= $param->{'default_state'};
1071
1072   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1073
1074   my $countyflag = 0;
1075
1076   my %cust_main_county;
1077
1078 #  unless ( @cust_main_county ) { #cache 
1079     #@cust_main_county = qsearch('cust_main_county', {} );
1080     #foreach my $c ( @cust_main_county ) {
1081     foreach my $c ( @{ $param->{'locales'} } ) {
1082       #$countyflag=1 if $c->county;
1083       $countyflag=1 if $c->{county};
1084       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1085       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1086       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1087     }
1088 #  }
1089   $countyflag=1 if $param->{selected_county};
1090
1091   my $script_html = <<END;
1092     <SCRIPT>
1093     function opt(what,value,text) {
1094       var optionName = new Option(text, value, false, false);
1095       var length = what.length;
1096       what.options[length] = optionName;
1097     }
1098     function ${prefix}country_changed(what) {
1099       country = what.options[what.selectedIndex].text;
1100       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1101           what.form.${prefix}state.options[i] = null;
1102 END
1103       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1104
1105   foreach my $country ( sort keys %cust_main_county ) {
1106     $script_html .= "\nif ( country == \"$country\" ) {\n";
1107     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1108       my $text = $state || '(n/a)';
1109       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1110     }
1111     $script_html .= "}\n";
1112   }
1113
1114   $script_html .= <<END;
1115     }
1116     function ${prefix}state_changed(what) {
1117 END
1118
1119   if ( $countyflag ) {
1120     $script_html .= <<END;
1121       state = what.options[what.selectedIndex].text;
1122       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1123       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1124           what.form.${prefix}county.options[i] = null;
1125 END
1126
1127     foreach my $country ( sort keys %cust_main_county ) {
1128       $script_html .= "\nif ( country == \"$country\" ) {\n";
1129       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1130         $script_html .= "\nif ( state == \"$state\" ) {\n";
1131           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1132           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1133             my $text = $county || '(n/a)';
1134             $script_html .=
1135               qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1136           }
1137         $script_html .= "}\n";
1138       }
1139       $script_html .= "}\n";
1140     }
1141   }
1142
1143   $script_html .= <<END;
1144     }
1145     </SCRIPT>
1146 END
1147
1148   my $county_html = $script_html;
1149   if ( $countyflag ) {
1150     $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1151     $county_html .= '</SELECT>';
1152   } else {
1153     $county_html .=
1154       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1155   }
1156
1157   my $state_html = qq!<SELECT NAME="${prefix}state" !.
1158                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1159   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1160     my $text = $state || '(n/a)';
1161     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1162     $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1163   }
1164   $state_html .= '</SELECT>';
1165
1166   $state_html .= '</SELECT>';
1167
1168   my $country_html = qq!<SELECT NAME="${prefix}country" !.
1169                      qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
1170   my $countrydefault = $param->{default_country} || 'US';
1171   foreach my $country (
1172     sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1173       keys %cust_main_county
1174   ) {
1175     my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
1176     $country_html .= "\n<OPTION$selected>$country</OPTION>"
1177   }
1178   $country_html .= '</SELECT>';
1179
1180   ($county_html, $state_html, $country_html);
1181
1182 }
1183
1184 #=item expselect HASHREF | LIST
1185 #
1186 #Takes as input a hashref or list of key/value pairs with the following keys:
1187 #
1188 #=over 4
1189 #
1190 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1191 #
1192 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1193 #
1194 #=back
1195
1196 =item expselect PREFIX [ DATE ]
1197
1198 Takes as input a unique prefix string and the current expiration date, in
1199 yyyy-mm-dd or m-d-yyyy format
1200
1201 Returns an HTML fragments for expiration date selection.
1202
1203 =cut
1204
1205 sub expselect {
1206   #my $param;
1207   #if ( ref($_[0]) ) {
1208   #  $param = shift;
1209   #} else {
1210   #  $param = { @_ };
1211   #my $prefix = $param->{'prefix'};
1212   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1213   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
1214   my $prefix = shift;
1215   my $date = scalar(@_) ? shift : '';
1216
1217   my( $m, $y ) = ( 0, 0 );
1218   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1219     ( $m, $y ) = ( $2, $1 );
1220   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1221     ( $m, $y ) = ( $1, $3 );
1222   }
1223   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1224   for ( 1 .. 12 ) {
1225     $return .= qq!<OPTION VALUE="$_"!;
1226     $return .= " SELECTED" if $_ == $m;
1227     $return .= ">$_";
1228   }
1229   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1230   my @t = localtime;
1231   my $thisYear = $t[5] + 1900;
1232   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1233     $return .= qq!<OPTION VALUE="$_"!;
1234     $return .= " SELECTED" if $_ == $y;
1235     $return .= ">$_";
1236   }
1237   $return .= "</SELECT>";
1238
1239   $return;
1240 }
1241
1242 =item popselector HASHREF | LIST
1243
1244 Takes as input a hashref or list of key/value pairs with the following keys:
1245
1246 =over 4
1247
1248 =item popnum
1249
1250 Access number number
1251
1252 =item pops
1253
1254 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>.
1255
1256 =back
1257
1258 Returns an HTML fragment for access number selection.
1259
1260 =cut
1261
1262 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1263 sub popselector {
1264   my $param;
1265   if ( ref($_[0]) ) {
1266     $param = shift;
1267   } else {
1268     $param = { @_ };
1269   }
1270   my $popnum = $param->{'popnum'};
1271   my $pops = $param->{'pops'};
1272
1273   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1274   return $pops->[0]{city}. ', '. $pops->[0]{state}.
1275          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1276          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1277     if scalar(@$pops) == 1;
1278
1279   my %pop = ();
1280   my %popnum2pop = ();
1281   foreach (@$pops) {
1282     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1283     $popnum2pop{$_->{popnum}} = $_;
1284   }
1285
1286   my $text = <<END;
1287     <SCRIPT>
1288     function opt(what,href,text) {
1289       var optionName = new Option(text, href, false, false)
1290       var length = what.length;
1291       what.options[length] = optionName;
1292     }
1293 END
1294
1295   my $init_popstate = $param->{'init_popstate'};
1296   if ( $init_popstate ) {
1297     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1298              $init_popstate. '">';
1299   } else {
1300     $text .= <<END;
1301       function acstate_changed(what) {
1302         state = what.options[what.selectedIndex].text;
1303         what.form.popac.options.length = 0
1304         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1305 END
1306   } 
1307
1308   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1309   foreach my $state ( sort { $a cmp $b } @states ) {
1310     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1311
1312     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1313       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1314       if ($ac eq $param->{'popac'}) {
1315         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1316       }
1317     }
1318     $text .= "}\n" unless $init_popstate;
1319   }
1320   $text .= "popac_changed(what.form.popac)}\n";
1321
1322   $text .= <<END;
1323   function popac_changed(what) {
1324     ac = what.options[what.selectedIndex].text;
1325     what.form.popnum.options.length = 0;
1326     what.form.popnum.options[0] = new Option("City", "-1", false, true);
1327
1328 END
1329
1330   foreach my $state ( @states ) {
1331     foreach my $popac ( keys %{ $pop{$state} } ) {
1332       $text .= "\nif ( ac == \"$popac\" ) {\n";
1333
1334       foreach my $pop ( @{$pop{$state}->{$popac}}) {
1335         my $o_popnum = $pop->{popnum};
1336         my $poptext =  $pop->{city}. ', '. $pop->{state}.
1337                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1338
1339         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1340         if ($popnum == $o_popnum) {
1341           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1342         }
1343       }
1344       $text .= "}\n";
1345     }
1346   }
1347
1348
1349   $text .= "}\n</SCRIPT>\n";
1350
1351   $text .=
1352     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1353     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1354   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1355            ">$_" foreach sort { $a cmp $b } @states;
1356   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
1357
1358   $text .=
1359     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1360     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1361
1362   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1363
1364
1365   #comment this block to disable initial list polulation
1366   my @initial_select = ();
1367   if ( scalar( @$pops ) > 100 ) {
1368     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1369   } else {
1370     @initial_select = @$pops;
1371   }
1372   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1373     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1374              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1375              $pop->{city}. ', '. $pop->{state}.
1376                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1377   }
1378
1379   $text .= qq!</SELECT></TD></TR></TABLE>!;
1380
1381   $text;
1382
1383 }
1384
1385 =item domainselector HASHREF | LIST
1386
1387 Takes as input a hashref or list of key/value pairs with the following keys:
1388
1389 =over 4
1390
1391 =item pkgnum
1392
1393 Package number
1394
1395 =item domsvc
1396
1397 Service number of the selected item.
1398
1399 =back
1400
1401 Returns an HTML fragment for domain selection.
1402
1403 =cut
1404
1405 sub domainselector {
1406   my $param;
1407   if ( ref($_[0]) ) {
1408     $param = shift;
1409   } else {
1410     $param = { @_ };
1411   }
1412   my $domsvc= $param->{'domsvc'};
1413   my $rv = 
1414       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1415   my $domains = $rv->{'domains'};
1416   $domsvc = $rv->{'domsvc'} unless $domsvc;
1417
1418   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1419     unless scalar(keys %$domains);
1420     
1421   if (scalar(keys %$domains) == 1) {
1422     my $key;
1423     foreach(keys %$domains) {
1424       $key = $_;
1425     }
1426     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1427            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1428   }
1429
1430   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1431
1432
1433   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1434     $text .= qq!<OPTION VALUE="!. $domain. '"'.
1435              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1436              $domains->{$domain};
1437   }
1438
1439   $text .= qq!</SELECT></TD></TR>!;
1440
1441   $text;
1442
1443 }
1444
1445 =back
1446
1447 =head1 RESELLER FUNCTIONS
1448
1449 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1450 with their active session, and the B<customer_info> and B<order_pkg> functions
1451 with their active session and an additional I<custnum> parameter.
1452
1453 =over 4
1454
1455 =item agent_login
1456
1457 =item agent_info
1458
1459 =item agent_list_customers
1460
1461 =back
1462
1463 =head1 BUGS
1464
1465 =head1 SEE ALSO
1466
1467 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
1468
1469 =cut
1470
1471 1;
1472