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