new_customer_minimal accepts address, & doc it, RT#34084
[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 new_customer_minimal );
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 =item quantity
1030
1031 Quantity for this package order (default 1).
1032
1033 =back
1034
1035 Returns a hash reference with the following keys:
1036
1037 =over 4
1038
1039 =item error
1040
1041 Empty on success, or an error message on errors.  
1042
1043 =item pkgnum
1044
1045 On success, the new pkgnum
1046
1047 =back
1048
1049
1050 =item renew_info
1051
1052 Provides useful info for early renewals.
1053
1054 Takes a hash reference as parameter with the following keys:
1055
1056 =over 4
1057
1058 =item session_id
1059
1060 Session identifier
1061
1062 =back
1063
1064 Returns a hash reference.  On errors, it contains a single key, B<error>, with
1065 the error message.  Otherwise, contains a single key, B<dates>, pointing to
1066 an array refernce of hash references.  Each hash reference contains the
1067 following keys:
1068
1069 =over 4
1070
1071 =item bill_date
1072
1073 (Future) Bill date.  Indicates a future date for which billing could be run.
1074 Specified as a integer UNIX timestamp.  Pass this value to the B<order_renew>
1075 function.
1076
1077 =item bill_date_pretty
1078
1079 (Future) Bill date as a human-readable string.  (Convenience for display;
1080 subject to change, so best not to parse for the date.)
1081
1082 =item amount
1083
1084 Base amount which will be charged if renewed early as of this date.
1085
1086 =item renew_date
1087
1088 Renewal date; i.e. even-futher future date at which the customer will be paid
1089 through if the early renewal is completed with the given B<bill-date>.
1090 Specified as a integer UNIX timestamp.
1091
1092 =item renew_date_pretty
1093
1094 Renewal date as a human-readable string.  (Convenience for display;
1095 subject to change, so best not to parse for the date.)
1096
1097 =item pkgnum
1098
1099 Package that will be renewed.
1100
1101 =item expire_date
1102
1103 Expiration date of the package that will be renewed.
1104
1105 =item expire_date_pretty
1106
1107 Expiration date of the package that will be renewed, as a human-readable
1108 string.  (Convenience for display; subject to change, so best not to parse for
1109 the date.)
1110
1111 =back
1112
1113 =item order_renew
1114
1115 Renews this customer early; i.e. runs billing for this customer in advance.
1116
1117 Takes a hash reference as parameter with the following keys:
1118
1119 =over 4
1120
1121 =item session_id
1122
1123 Session identifier
1124
1125 =item date
1126
1127 Integer date as returned by the B<renew_info> function, indicating the advance
1128 date for which to run billing.
1129
1130 =back
1131
1132 Returns a hash reference with a single key, B<error>, empty on success, or an
1133 error message on errors.
1134
1135 =item cancel_pkg
1136
1137 Cancels a package for this customer.
1138
1139 Takes a hash reference as parameter with the following keys:
1140
1141 =over 4
1142
1143 =item session_id
1144
1145 Session identifier
1146
1147 =item pkgpart
1148
1149 pkgpart of package to cancel
1150
1151 =back
1152
1153 Returns a hash reference with a single key, B<error>, empty on success, or an
1154 error message on errors.
1155
1156 =back
1157
1158 =head1 SIGNUP FUNCTIONS
1159
1160 =over 4
1161
1162 =item signup_info HASHREF
1163
1164 Takes a hash reference as parameter with the following keys:
1165
1166 =over 4
1167
1168 =item session_id - Optional agent/reseller interface session
1169
1170 =back
1171
1172 Returns a hash reference containing information that may be useful in
1173 displaying a signup page.  The hash reference contains the following keys:
1174
1175 =over 4
1176
1177 =item cust_main_county
1178
1179 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.
1180
1181 =item part_pkg
1182
1183 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
1184 an agentnum specified explicitly via reseller interface session_id in the
1185 options.
1186
1187 =item agent
1188
1189 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.
1190
1191 =item agentnum2part_pkg
1192
1193 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.
1194
1195 =item svc_acct_pop
1196
1197 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.
1198
1199 =item security_phrase
1200
1201 True if the "security_phrase" feature is enabled
1202
1203 =item payby
1204
1205 Array reference of acceptable payment types for signup
1206
1207 =over 4
1208
1209 =item CARD
1210
1211 credit card - automatic
1212
1213 =item DCRD
1214
1215 credit card - on-demand - version 1.5+ only
1216
1217 =item CHEK
1218
1219 electronic check - automatic
1220
1221 =item DCHK
1222
1223 electronic check - on-demand - version 1.5+ only
1224
1225 =item LECB
1226
1227 Phone bill billing
1228
1229 =item BILL
1230
1231 billing, not recommended for signups
1232
1233 =item COMP
1234
1235 free, definitely not recommended for signups
1236
1237 =item PREPAY
1238
1239 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1240
1241 =back
1242
1243 =item cvv_enabled
1244
1245 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1246
1247 =item msgcat
1248
1249 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".
1250
1251 =item statedefault
1252
1253 Default state
1254
1255 =item countrydefault
1256
1257 Default country
1258
1259 =back
1260
1261 =item new_customer_minimal HASHREF
1262
1263 Creates a new customer.
1264
1265 Current differences from new_customer: An address is not required.  promo_code
1266 and reg_code are not supported.  If invoicing_list and _password is passed, a
1267 contact will be created with self-service access (no pkgpart or username is
1268 necessary).  No initial billing is run (this may change in a future version).
1269
1270 Takes a hash reference as parameter with the following keys:
1271
1272 =over 4
1273
1274 =item first
1275
1276 first name (required)
1277
1278 =item last
1279
1280 last name (required)
1281
1282 =item ss
1283
1284 (not typically collected; mostly used for ACH transactions)
1285
1286 =item company
1287
1288 Company name
1289
1290 =item address1
1291
1292 Address line one
1293
1294 =item address2
1295
1296 Address line two
1297
1298 =item city
1299
1300 City
1301
1302 =item county
1303
1304 County
1305
1306 =item state
1307
1308 State
1309
1310 =item zip
1311
1312 Zip or postal code
1313
1314 =item daytime
1315
1316 Daytime phone number
1317
1318 =item night
1319
1320 Evening phone number
1321
1322 =item fax
1323
1324 Fax number
1325
1326 =item payby
1327
1328 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1329
1330 =item payinfo
1331
1332 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1333
1334 =item paycvv
1335
1336 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1337
1338 =item paydate
1339
1340 Expiration date for CARD/DCRD
1341
1342 =item payname
1343
1344 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1345
1346 =item invoicing_list
1347
1348 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),
1349
1350 =item referral_custnum
1351
1352 referring customer number
1353
1354 =item agentnum
1355
1356 Agent number
1357
1358 =item pkgpart
1359
1360 pkgpart of initial package
1361
1362 =item username
1363
1364 Username
1365
1366 =item _password
1367
1368 Password
1369
1370 =item sec_phrase
1371
1372 Security phrase
1373
1374 =item popnum
1375
1376 Access number (index, not the literal number)
1377
1378 =item countrycode
1379
1380 Country code (to be provisioned as a service)
1381
1382 =item phonenum
1383
1384 Phone number (to be provisioned as a service)
1385
1386 =item pin
1387
1388 Voicemail PIN
1389
1390 =back
1391
1392 Returns a hash reference with the following keys:
1393
1394 =over 4
1395
1396 =item error
1397
1398 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)
1399
1400 =back
1401
1402 =item new_customer HASHREF
1403
1404 Creates a new customer.  Takes a hash reference as parameter with the
1405 following keys:
1406
1407 =over 4
1408
1409 =item first
1410
1411 first name (required)
1412
1413 =item last
1414
1415 last name (required)
1416
1417 =item ss
1418
1419 (not typically collected; mostly used for ACH transactions)
1420
1421 =item company
1422
1423 Company name
1424
1425 =item address1 (required)
1426
1427 Address line one
1428
1429 =item address2
1430
1431 Address line two
1432
1433 =item city (required)
1434
1435 City
1436
1437 =item county
1438
1439 County
1440
1441 =item state (required)
1442
1443 State
1444
1445 =item zip (required)
1446
1447 Zip or postal code
1448
1449 =item daytime
1450
1451 Daytime phone number
1452
1453 =item night
1454
1455 Evening phone number
1456
1457 =item fax
1458
1459 Fax number
1460
1461 =item payby
1462
1463 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1464
1465 =item payinfo
1466
1467 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1468
1469 =item paycvv
1470
1471 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1472
1473 =item paydate
1474
1475 Expiration date for CARD/DCRD
1476
1477 =item payname
1478
1479 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1480
1481 =item invoicing_list
1482
1483 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),
1484
1485 =item referral_custnum
1486
1487 referring customer number
1488
1489 =item agentnum
1490
1491 Agent number
1492
1493 =item pkgpart
1494
1495 pkgpart of initial package
1496
1497 =item username
1498
1499 Username
1500
1501 =item _password
1502
1503 Password
1504
1505 =item sec_phrase
1506
1507 Security phrase
1508
1509 =item popnum
1510
1511 Access number (index, not the literal number)
1512
1513 =item countrycode
1514
1515 Country code (to be provisioned as a service)
1516
1517 =item phonenum
1518
1519 Phone number (to be provisioned as a service)
1520
1521 =item pin
1522
1523 Voicemail PIN
1524
1525 =back
1526
1527 Returns a hash reference with the following keys:
1528
1529 =over 4
1530
1531 =item error
1532
1533 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)
1534
1535 =back
1536
1537 =item regionselector HASHREF | LIST
1538
1539 Takes as input a hashref or list of key/value pairs with the following keys:
1540
1541 =over 4
1542
1543 =item selected_county
1544
1545 Currently selected county
1546
1547 =item selected_state
1548
1549 Currently selected state
1550
1551 =item selected_country
1552
1553 Currently selected country
1554
1555 =item prefix
1556
1557 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1558
1559 =item onchange
1560
1561 Specify a javascript subroutine to call on changes
1562
1563 =item default_state
1564
1565 Default state
1566
1567 =item default_country
1568
1569 Default country
1570
1571 =item locales
1572
1573 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>.
1574
1575 =back
1576
1577 Returns a list consisting of three HTML fragments for county selection,
1578 state selection and country selection, respectively.
1579
1580 =cut
1581
1582 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1583 sub regionselector {
1584   my $param;
1585   if ( ref($_[0]) ) {
1586     $param = shift;
1587   } else {
1588     $param = { @_ };
1589   }
1590   $param->{'selected_country'} ||= $param->{'default_country'};
1591   $param->{'selected_state'} ||= $param->{'default_state'};
1592
1593   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1594
1595   my $countyflag = 0;
1596
1597   my %cust_main_county;
1598
1599 #  unless ( @cust_main_county ) { #cache 
1600     #@cust_main_county = qsearch('cust_main_county', {} );
1601     #foreach my $c ( @cust_main_county ) {
1602     foreach my $c ( @{ $param->{'locales'} } ) {
1603       #$countyflag=1 if $c->county;
1604       $countyflag=1 if $c->{county};
1605       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1606       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1607       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1608     }
1609 #  }
1610   $countyflag=1 if $param->{selected_county};
1611
1612   my $script_html = <<END;
1613     <SCRIPT>
1614     function opt(what,value,text) {
1615       var optionName = new Option(text, value, false, false);
1616       var length = what.length;
1617       what.options[length] = optionName;
1618     }
1619     function ${prefix}country_changed(what) {
1620       country = what.options[what.selectedIndex].text;
1621       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1622           what.form.${prefix}state.options[i] = null;
1623 END
1624       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1625
1626   foreach my $country ( sort keys %cust_main_county ) {
1627     $script_html .= "\nif ( country == \"$country\" ) {\n";
1628     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1629       my $text = $state || '(n/a)';
1630       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1631     }
1632     $script_html .= "}\n";
1633   }
1634
1635   $script_html .= <<END;
1636     }
1637     function ${prefix}state_changed(what) {
1638 END
1639
1640   if ( $countyflag ) {
1641     $script_html .= <<END;
1642       state = what.options[what.selectedIndex].text;
1643       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1644       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1645           what.form.${prefix}county.options[i] = null;
1646 END
1647
1648     foreach my $country ( sort keys %cust_main_county ) {
1649       $script_html .= "\nif ( country == \"$country\" ) {\n";
1650       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1651         $script_html .= "\nif ( state == \"$state\" ) {\n";
1652           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1653           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1654             my $text = $county || '(n/a)';
1655             $script_html .=
1656               qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1657           }
1658         $script_html .= "}\n";
1659       }
1660       $script_html .= "}\n";
1661     }
1662   }
1663
1664   $script_html .= <<END;
1665     }
1666     </SCRIPT>
1667 END
1668
1669   my $county_html = $script_html;
1670   if ( $countyflag ) {
1671     $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1672     foreach my $county ( 
1673       sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1674     ) {
1675       my $text = $county || '(n/a)';
1676       $county_html .= qq!<OPTION VALUE="$county"!.
1677                       ($county eq $param->{'selected_county'} ? 
1678                         ' SELECTED>' : 
1679                         '>'
1680                       ).
1681                       $text.
1682                       '</OPTION>';
1683     }
1684     $county_html .= '</SELECT>';
1685   } else {
1686     $county_html .=
1687       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1688   }
1689
1690   my $state_html = qq!<SELECT NAME="${prefix}state" !.
1691                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1692   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1693     my $text = $state || '(n/a)';
1694     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1695     $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1696   }
1697   $state_html .= '</SELECT>';
1698
1699   my $country_html = '';
1700   if ( scalar( keys %cust_main_county ) > 1 )  {
1701
1702     $country_html = qq(<SELECT NAME="${prefix}country" ).
1703                     qq(onChange="${prefix}country_changed(this); ).
1704                                  $param->{'onchange'}.
1705                                '"'.
1706                       '>';
1707     my $countrydefault = $param->{default_country} || 'US';
1708     foreach my $country (
1709       sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1710         keys %cust_main_county
1711     ) {
1712       my $selected = $country eq $param->{'selected_country'}
1713                        ? ' SELECTED'
1714                        : '';
1715       $country_html .= "\n<OPTION$selected>$country</OPTION>"
1716     }
1717     $country_html .= '</SELECT>';
1718   } else {
1719
1720     $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1721                             ' VALUE="'. (keys %cust_main_county )[0]. '">';
1722
1723   }
1724
1725   ($county_html, $state_html, $country_html);
1726
1727 }
1728
1729 sub regionselector_hashref {
1730   my ($county_html, $state_html, $country_html) = regionselector(@_);
1731   {
1732     'county_html'  => $county_html,
1733     'state_html'   => $state_html,
1734     'country_html' => $country_html,
1735   };
1736 }
1737
1738 =item location_form HASHREF | LIST
1739
1740 Takes as input a hashref or list of key/value pairs with the following keys:
1741
1742 =over 4
1743
1744 =item session_id
1745
1746 Current customer session_id
1747
1748 =item no_asterisks
1749
1750 Omit red asterisks from required fields.
1751
1752 =item address1_label
1753
1754 Label for first address line.
1755
1756 =back
1757
1758 Returns an HTML fragment for a location form (address, city, state, zip,
1759 country)
1760
1761 =cut
1762
1763 sub location_form {
1764   my $param;
1765   if ( ref($_[0]) ) {
1766     $param = shift;
1767   } else {
1768     $param = { @_ };
1769   }
1770
1771   my $session_id = delete $param->{'session_id'};
1772
1773   my $rv = mason_comp( 'session_id' => $session_id,
1774                        'comp'       => '/elements/location.html',
1775                        'args'       => [ %$param ],
1776                      );
1777
1778   #hmm.
1779   $rv->{'error'} || $rv->{'output'};
1780
1781 }
1782
1783
1784 #=item expselect HASHREF | LIST
1785 #
1786 #Takes as input a hashref or list of key/value pairs with the following keys:
1787 #
1788 #=over 4
1789 #
1790 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1791 #
1792 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1793 #
1794 #=back
1795
1796 =item expselect PREFIX [ DATE ]
1797
1798 Takes as input a unique prefix string and the current expiration date, in
1799 yyyy-mm-dd or m-d-yyyy format
1800
1801 Returns an HTML fragments for expiration date selection.
1802
1803 =cut
1804
1805 sub expselect {
1806   #my $param;
1807   #if ( ref($_[0]) ) {
1808   #  $param = shift;
1809   #} else {
1810   #  $param = { @_ };
1811   #my $prefix = $param->{'prefix'};
1812   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1813   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
1814   my $prefix = shift;
1815   my $date = scalar(@_) ? shift : '';
1816
1817   my( $m, $y ) = ( 0, 0 );
1818   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1819     ( $m, $y ) = ( $2, $1 );
1820   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1821     ( $m, $y ) = ( $1, $3 );
1822   }
1823   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1824   for ( 1 .. 12 ) {
1825     $return .= qq!<OPTION VALUE="$_"!;
1826     $return .= " SELECTED" if $_ == $m;
1827     $return .= ">$_";
1828   }
1829   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1830   my @t = localtime;
1831   my $thisYear = $t[5] + 1900;
1832   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1833     $return .= qq!<OPTION VALUE="$_"!;
1834     $return .= " SELECTED" if $_ == $y;
1835     $return .= ">$_";
1836   }
1837   $return .= "</SELECT>";
1838
1839   $return;
1840 }
1841
1842 =item popselector 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 popnum
1849
1850 Access number number
1851
1852 =item pops
1853
1854 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>.
1855
1856 =back
1857
1858 Returns an HTML fragment for access number selection.
1859
1860 =cut
1861
1862 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1863 sub popselector {
1864   my $param;
1865   if ( ref($_[0]) ) {
1866     $param = shift;
1867   } else {
1868     $param = { @_ };
1869   }
1870   my $popnum = $param->{'popnum'};
1871   my $pops = $param->{'pops'};
1872
1873   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1874   return $pops->[0]{city}. ', '. $pops->[0]{state}.
1875          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1876          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1877     if scalar(@$pops) == 1;
1878
1879   my %pop = ();
1880   my %popnum2pop = ();
1881   foreach (@$pops) {
1882     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1883     $popnum2pop{$_->{popnum}} = $_;
1884   }
1885
1886   my $text = <<END;
1887     <SCRIPT>
1888     function opt(what,href,text) {
1889       var optionName = new Option(text, href, false, false)
1890       var length = what.length;
1891       what.options[length] = optionName;
1892     }
1893 END
1894
1895   my $init_popstate = $param->{'init_popstate'};
1896   if ( $init_popstate ) {
1897     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1898              $init_popstate. '">';
1899   } else {
1900     $text .= <<END;
1901       function acstate_changed(what) {
1902         state = what.options[what.selectedIndex].text;
1903         what.form.popac.options.length = 0
1904         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1905 END
1906   } 
1907
1908   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1909   foreach my $state ( sort { $a cmp $b } @states ) {
1910     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1911
1912     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1913       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1914       if ($ac eq $param->{'popac'}) {
1915         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1916       }
1917     }
1918     $text .= "}\n" unless $init_popstate;
1919   }
1920   $text .= "popac_changed(what.form.popac)}\n";
1921
1922   $text .= <<END;
1923   function popac_changed(what) {
1924     ac = what.options[what.selectedIndex].text;
1925     what.form.popnum.options.length = 0;
1926     what.form.popnum.options[0] = new Option("City", "-1", false, true);
1927
1928 END
1929
1930   foreach my $state ( @states ) {
1931     foreach my $popac ( keys %{ $pop{$state} } ) {
1932       $text .= "\nif ( ac == \"$popac\" ) {\n";
1933
1934       foreach my $pop ( @{$pop{$state}->{$popac}}) {
1935         my $o_popnum = $pop->{popnum};
1936         my $poptext =  $pop->{city}. ', '. $pop->{state}.
1937                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1938
1939         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1940         if ($popnum == $o_popnum) {
1941           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1942         }
1943       }
1944       $text .= "}\n";
1945     }
1946   }
1947
1948
1949   $text .= "}\n</SCRIPT>\n";
1950
1951   $param->{'acstate'} = '' unless defined($param->{'acstate'});
1952
1953   $text .=
1954     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1955     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1956   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1957            ">$_" foreach sort { $a cmp $b } @states;
1958   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
1959
1960   $text .=
1961     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1962     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1963
1964   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1965
1966
1967   #comment this block to disable initial list polulation
1968   my @initial_select = ();
1969   if ( scalar( @$pops ) > 100 ) {
1970     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1971   } else {
1972     @initial_select = @$pops;
1973   }
1974   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1975     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1976              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1977              $pop->{city}. ', '. $pop->{state}.
1978                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1979   }
1980
1981   $text .= qq!</SELECT></TD></TR></TABLE>!;
1982
1983   $text;
1984
1985 }
1986
1987 =item domainselector HASHREF | LIST
1988
1989 Takes as input a hashref or list of key/value pairs with the following keys:
1990
1991 =over 4
1992
1993 =item pkgnum
1994
1995 Package number
1996
1997 =item domsvc
1998
1999 Service number of the selected item.
2000
2001 =back
2002
2003 Returns an HTML fragment for domain selection.
2004
2005 =cut
2006
2007 sub domainselector {
2008   my $param;
2009   if ( ref($_[0]) ) {
2010     $param = shift;
2011   } else {
2012     $param = { @_ };
2013   }
2014   my $domsvc= $param->{'domsvc'};
2015   my $rv = 
2016       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2017   my $domains = $rv->{'domains'};
2018   $domsvc = $rv->{'domsvc'} unless $domsvc;
2019
2020   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2021     unless scalar(keys %$domains);
2022
2023   if (scalar(keys %$domains) == 1) {
2024     my $key;
2025     foreach(keys %$domains) {
2026       $key = $_;
2027     }
2028     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2029            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2030   }
2031
2032   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2033
2034   $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2035
2036   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2037     $text .= qq!<OPTION VALUE="!. $domain. '"'.
2038              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2039              $domains->{$domain};
2040   }
2041
2042   $text .= qq!</SELECT></TD></TR>!;
2043
2044   $text;
2045
2046 }
2047
2048 =item didselector HASHREF | LIST
2049
2050 Takes as input a hashref or list of key/value pairs with the following keys:
2051
2052 =over 4
2053
2054 =item field
2055
2056 Field name for the returned HTML fragment.
2057
2058 =item svcpart
2059
2060 Service definition (see L<FS::part_svc>)
2061
2062 =back
2063
2064 Returns an HTML fragment for DID selection.
2065
2066 =cut
2067
2068 sub didselector {
2069   my $param;
2070   if ( ref($_[0]) ) {
2071     $param = shift;
2072   } else {
2073     $param = { @_ };
2074   }
2075
2076   my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2077                        'args'=>[ %$param ],
2078                      );
2079
2080   #hmm.
2081   $rv->{'error'} || $rv->{'output'};
2082
2083 }
2084
2085 =back
2086
2087 =head1 RESELLER FUNCTIONS
2088
2089 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2090 with their active session, and the B<customer_info> and B<order_pkg> functions
2091 with their active session and an additional I<custnum> parameter.
2092
2093 For the most part, development of the reseller web interface has been
2094 superceded by agent-virtualized access to the backend.
2095
2096 =over 4
2097
2098 =item agent_login
2099
2100 Agent login
2101
2102 =item agent_info
2103
2104 Agent info
2105
2106 =item agent_list_customers
2107
2108 List agent's customers.
2109
2110 =back
2111
2112 =head1 BUGS
2113
2114 =head1 SEE ALSO
2115
2116 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
2117
2118 =cut
2119
2120 1;
2121