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