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