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