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