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