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