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