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