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