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