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