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