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