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