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