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