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