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