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