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