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