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