script to insert CDR header strings into msgcat, #27276
[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   'create_ticket'             => 'MyAccount/create_ticket',
90   'get_ticket'                => 'MyAccount/get_ticket',
91   'adjust_ticket_priority'    => 'MyAccount/adjust_ticket_priority',
92   'did_report'                => 'MyAccount/did_report',
93   'signup_info'               => 'Signup/signup_info',
94   'skin_info'                 => 'MyAccount/skin_info',
95   'access_info'               => 'MyAccount/access_info',
96   'domain_select_hash'        => 'Signup/domain_select_hash',  # expose?
97   'new_customer'              => 'Signup/new_customer',
98   'capture_payment'           => 'Signup/capture_payment',
99   #N/A 'clear_signup_cache'        => 'Signup/clear_cache',
100   'new_agent'                 => 'Agent/new_agent',
101   'agent_login'               => 'Agent/agent_login',
102   'agent_logout'              => 'Agent/agent_logout',
103   'agent_info'                => 'Agent/agent_info',
104   'agent_list_customers'      => 'Agent/agent_list_customers',
105   'check_username'            => 'Agent/check_username',
106   'suspend_username'          => 'Agent/suspend_username',
107   'unsuspend_username'        => 'Agent/unsuspend_username',
108   'mason_comp'                => 'MasonComponent/mason_comp',
109   'call_time'                 => 'PrepaidPhone/call_time',
110   'call_time_nanpa'           => 'PrepaidPhone/call_time_nanpa',
111   'phonenum_balance'          => 'PrepaidPhone/phonenum_balance',
112   #izoom
113   #'bulk_processrow'           => 'Bulk/processrow',
114   #conflicts w/Agent one# 'check_username'            => 'Bulk/check_username',
115   #sg
116   'ping'                      => 'SGNG/ping',
117   'decompify_pkgs'            => 'SGNG/decompify_pkgs',
118   'previous_payment_info'     => 'SGNG/previous_payment_info',
119   'previous_payment_info_renew_info'
120                               => 'SGNG/previous_payment_info_renew_info',
121   'previous_process_payment'  => 'SGNG/previous_process_payment',
122   'previous_process_payment_order_pkg'
123                               => 'SGNG/previous_process_payment_order_pkg',
124   'previous_process_payment_change_pkg'
125                               => 'SGNG/previous_process_payment_change_pkg',
126   'previous_process_payment_order_renew'
127                               => 'SGNG/previous_process_payment_order_renew',
128 );
129 @EXPORT_OK = (
130   keys(%autoload),
131   qw( regionselector regionselector_hashref location_form
132       expselect popselector domainselector didselector
133     )
134 );
135
136 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
137 $ENV{'SHELL'} = '/bin/sh';
138 $ENV{'IFS'} = " \t\n";
139 $ENV{'CDPATH'} = '';
140 $ENV{'ENV'} = '';
141 $ENV{'BASH_ENV'} = '';
142
143 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; } 
144 #if you grant appropriate permissions to whatever user
145 my $freeside_uid = scalar(getpwnam('freeside'));
146 die "not running as the freeside user\n"
147   if $> != $freeside_uid && ! $skip_uid_check;
148
149 -e $dir or die "FATAL: $dir doesn't exist!";
150 -d $dir or die "FATAL: $dir isn't a directory!";
151 -r $dir or die "FATAL: Can't read $dir as freeside user!";
152 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
153
154 foreach my $autoload ( keys %autoload ) {
155
156   my $eval =
157   "sub $autoload { ". '
158                    my $param;
159                    if ( ref($_[0]) ) {
160                      $param = shift;
161                    } else {
162                      #warn scalar(@_). ": ". join(" / ", @_);
163                      $param = { @_ };
164                    }
165
166                    $param->{_packet} = \''. $autoload{$autoload}. '\';
167
168                    simple_packet($param);
169                  }';
170
171   eval $eval;
172   die $@ if $@;
173
174 }
175
176 sub simple_packet {
177   my $packet = shift;
178   warn "sending ". $packet->{_packet}. " to server"
179     if $DEBUG;
180   socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
181   connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
182   nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
183   SOCK->flush;
184
185   #shoudl trap: Magic number checking on storable file failed at blib/lib/Storable.pm (autosplit into blib/lib/auto/Storable/fd_retrieve.al) line 337, at /usr/local/share/perl/5.6.1/FS/SelfService.pm line 71
186
187   #block until there is a message on socket
188 #  my $w = new IO::Select;
189 #  $w->add(\*SOCK);
190 #  my @wait = $w->can_read;
191
192   warn "reading message from server"
193     if $DEBUG;
194
195   my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
196   die $return->{'_error'} if defined $return->{_error} && $return->{_error};
197
198   warn "returning message to client"
199     if $DEBUG;
200
201   $return;
202 }
203
204 =head1 NAME
205
206 FS::SelfService - Freeside self-service API
207
208 =head1 SYNOPSIS
209
210   # password and shell account changes
211   use FS::SelfService qw(passwd chfn chsh);
212
213   # "my account" functionality
214   use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
215
216   my $rv = login( { 'username' => $username,
217                     'domain'   => $domain,
218                     'password' => $password,
219                   }
220                 );
221
222   if ( $rv->{'error'} ) {
223     #handle login error...
224   } else {
225     #successful login
226     my $session_id = $rv->{'session_id'};
227   }
228
229   my $customer_info = customer_info( { 'session_id' => $session_id } );
230
231   #payment_info and process_payment are available in 1.5+ only
232   my $payment_info = payment_info( { 'session_id' => $session_id } );
233
234   #!!! process_payment example
235
236   #!!! list_pkgs example
237
238   #!!! order_pkg example
239
240   #!!! cancel_pkg example
241
242   # signup functionality
243   use FS::SelfService qw( signup_info new_customer );
244
245   my $signup_info = signup_info;
246
247   $rv = new_customer( {
248                         'first'            => $first,
249                         'last'             => $last,
250                         'company'          => $company,
251                         'address1'         => $address1,
252                         'address2'         => $address2,
253                         'city'             => $city,
254                         'state'            => $state,
255                         'zip'              => $zip,
256                         'country'          => $country,
257                         'daytime'          => $daytime,
258                         'night'            => $night,
259                         'fax'              => $fax,
260                         'payby'            => $payby,
261                         'payinfo'          => $payinfo,
262                         'paycvv'           => $paycvv,
263                         'paystart_month'   => $paystart_month
264                         'paystart_year'    => $paystart_year,
265                         'payissue'         => $payissue,
266                         'payip'            => $payip
267                         'paydate'          => $paydate,
268                         'payname'          => $payname,
269                         'invoicing_list'   => $invoicing_list,
270                         'referral_custnum' => $referral_custnum,
271                         'agentnum'         => $agentnum,
272                         'pkgpart'          => $pkgpart,
273
274                         'username'         => $username,
275                         '_password'        => $password,
276                         'popnum'           => $popnum,
277                         #OR
278                         'countrycode'      => 1,
279                         'phonenum'         => $phonenum,
280                         'pin'              => $pin,
281                       }
282                     );
283   
284   my $error = $rv->{'error'};
285   if ( $error eq '_decline' ) {
286     print_decline();
287   } elsif ( $error ) {
288     reprint_signup();
289   } else {
290     print_success();
291   }
292
293 =head1 DESCRIPTION
294
295 Use this API to implement your own client "self-service" module.
296
297 If you just want to customize the look of the existing "self-service" module,
298 see XXXX instead.
299
300 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
301
302 =over 4
303
304 =item passwd
305
306 =item chfn
307
308 =item chsh
309
310 =back
311
312 =head1 "MY ACCOUNT" FUNCTIONS
313
314 =over 4
315
316 =item login HASHREF
317
318 Creates a user session.  Takes a hash reference as parameter with the
319 following keys:
320
321 =over 4
322
323 =item username
324
325 Username
326
327 =item domain
328
329 Domain
330
331 =item password
332
333 Password
334
335 =back
336
337 Returns a hash reference with the following keys:
338
339 =over 4
340
341 =item error
342
343 Empty on success, or an error message on errors.
344
345 =item session_id
346
347 Session identifier for successful logins
348
349 =back
350
351 =item customer_info HASHREF
352
353 Returns general customer information.
354
355 Takes a hash reference as parameter with a single key: B<session_id>
356
357 Returns a hash reference with the following keys:
358
359 =over 4
360
361 =item name
362
363 Customer name
364
365 =item balance
366
367 Balance owed
368
369 =item open
370
371 Array reference of hash references of open inoices.  Each hash reference has
372 the following keys: invnum, date, owed
373
374 =item small_custview
375
376 An HTML fragment containing shipping and billing addresses.
377
378 =item The following fields are also returned
379
380 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
381
382 =back
383
384 =item edit_info HASHREF
385
386 Takes a hash reference as parameter with any of the following keys:
387
388 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
389
390 If a field exists, the customer record is updated with the new value of that
391 field.  If a field does not exist, that field is not changed on the customer
392 record.
393
394 Returns a hash reference with a single key, B<error>, empty on success, or an
395 error message on errors
396
397 =item invoice HASHREF
398
399 Returns an invoice.  Takes a hash reference as parameter with two keys:
400 session_id and invnum
401
402 Returns a hash reference with the following keys:
403
404 =over 4
405
406 =item error
407
408 Empty on success, or an error message on errors
409
410 =item invnum
411
412 Invoice number
413
414 =item invoice_text
415
416 Invoice text
417
418 =back
419
420 =item list_invoices HASHREF
421
422 Returns a list of all customer invoices.  Takes a hash references with a single
423 key, session_id.
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 invoices
434
435 Reference to array of hash references with the following keys:
436
437 =over 4
438
439 =item invnum
440
441 Invoice ID
442
443 =item _date
444
445 Invoice date, in UNIX epoch time
446
447 =back
448
449 =back
450
451 =item cancel HASHREF
452
453 Cancels this customer.
454
455 Takes a hash reference as parameter with a single key: B<session_id>
456
457 Returns a hash reference with a single key, B<error>, which is empty on
458 success or an error message on errors.
459
460 =item payment_info HASHREF
461
462 Returns information that may be useful in displaying a payment page.
463
464 Takes a hash reference as parameter with a single key: B<session_id>.
465
466 Returns a hash reference with the following keys:
467
468 =over 4
469
470 =item error
471
472 Empty on success, or an error message on errors
473
474 =item balance
475
476 Balance owed
477
478 =item payname
479
480 Exact name on credit card (CARD/DCRD)
481
482 =item address1
483
484 Address line one
485
486 =item address2
487
488 Address line two
489
490 =item city
491
492 City
493
494 =item state
495
496 State
497
498 =item zip
499
500 Zip or postal code
501
502 =item payby
503
504 Customer's current default payment type.
505
506 =item card_type
507
508 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
509
510 =item payinfo
511
512 For CARD/DCRD payment types, the card number
513
514 =item month
515
516 For CARD/DCRD payment types, expiration month
517
518 =item year
519
520 For CARD/DCRD payment types, expiration year
521
522 =item cust_main_county
523
524 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.
525
526 =item states
527
528 Array reference of all states in the current default country.
529
530 =item card_types
531
532 Hash reference of card types; keys are card types, values are the exact strings
533 passed to the process_payment function
534
535 =cut
536
537 #this doesn't actually work yet
538 #
539 #=item paybatch
540 #
541 #Unique transaction identifier (prevents multiple charges), passed to the
542 #process_payment function
543
544 =back
545
546 =item process_payment HASHREF
547
548 Processes a payment and possible change of address or payment type.  Takes a
549 hash reference as parameter with the following keys:
550
551 =over 4
552
553 =item session_id
554
555 Session identifier
556
557 =item amount
558
559 Amount
560
561 =item save
562
563 If true, address and card information entered will be saved for subsequent
564 transactions.
565
566 =item auto
567
568 If true, future credit card payments will be done automatically (sets payby to
569 CARD).  If false, future credit card payments will be done on-demand (sets
570 payby to DCRD).  This option only has meaning if B<save> is set true.  
571
572 =item payname
573
574 Name on card
575
576 =item address1
577
578 Address line one
579
580 =item address2
581
582 Address line two
583
584 =item city
585
586 City
587
588 =item state
589
590 State
591
592 =item zip
593
594 Zip or postal code
595
596 =item country
597
598 Two-letter country code
599
600 =item payinfo
601
602 Card number
603
604 =item month
605
606 Card expiration month
607
608 =item year
609
610 Card expiration year
611
612 =cut
613
614 #this doesn't actually work yet
615 #
616 #=item paybatch
617 #
618 #Unique transaction identifier, returned from the payment_info function.
619 #Prevents multiple charges.
620
621 =back
622
623 Returns a hash reference with a single key, B<error>, empty on success, or an
624 error message on errors.
625
626 =item process_payment_order_pkg
627
628 Combines the B<process_payment> and B<order_pkg> functions in one step.  If the
629 payment processes sucessfully, the package is ordered.  Takes a hash reference
630 as parameter with the keys of both methods.
631
632 Returns a hash reference with a single key, B<error>, empty on success, or an
633 error message on errors.
634
635 =item process_payment_change_pkg
636
637 Combines the B<process_payment> and B<change_pkg> functions in one step.  If the
638 payment processes sucessfully, the package is ordered.  Takes a hash reference
639 as parameter with the keys of both methods.
640
641 Returns a hash reference with a single key, B<error>, empty on success, or an
642 error message on errors.
643
644
645 =item process_payment_order_renew
646
647 Combines the B<process_payment> and B<order_renew> functions in one step.  If
648 the payment processes sucessfully, the renewal is processed.  Takes a hash
649 reference as parameter with the keys of both methods.
650
651 Returns a hash reference with a single key, B<error>, empty on success, or an
652 error message on errors.
653
654 =item list_pkgs
655
656 Returns package information for this customer.  For more detail on services,
657 see L</list_svcs>.
658
659 Takes a hash reference as parameter with a single key: B<session_id>
660
661 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
662
663 =over 4
664
665 =item custnum
666
667 Customer number
668
669 =item error
670
671 Empty on success, or an error message on errors.
672
673 =item cust_pkg HASHREF
674
675 Array reference of hash references, each of which has the fields of a cust_pkg
676 record (see L<FS::cust_pkg>) as well as the fields below.  Note these are not
677 the internal FS:: objects, but hash references of columns and values.
678
679 =over 4
680
681 =item part_pkg fields
682
683 All fields of part_pkg for this specific cust_pkg (be careful with this
684 information - it may reveal more about your available packages than you would
685 like users to know in aggregate) 
686
687 =cut
688
689 #XXX pare part_pkg fields down to a more secure subset
690
691 =item part_svc
692
693 An array of hash references indicating information on unprovisioned services
694 available for provisioning for this specific cust_pkg.  Each has the following
695 keys:
696
697 =over 4
698
699 =item part_svc fields
700
701 All fields of part_svc (be careful with this information - it may reveal more
702 about your available packages than you would like users to know in aggregate) 
703
704 =cut
705
706 #XXX pare part_svc fields down to a more secure subset
707
708 =back
709
710 =item cust_svc
711
712 An array of hash references indicating information on the customer services
713 already provisioned for this specific cust_pkg.  Each has the following keys:
714
715 =over 4
716
717 =item label
718
719 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.
720
721 =back
722
723 =item svcnum
724
725 Primary key for this service
726
727 =item svcpart
728
729 Service definition (see L<FS::part_svc>)
730
731 =item pkgnum
732
733 Customer package (see L<FS::cust_pkg>)
734
735 =item overlimit
736
737 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
738
739 =back
740
741 =back
742
743 =item list_svcs
744
745 Returns service information for this customer.
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 svcs
758
759 An array of hash references indicating information on all of this customer's
760 services.  Each has the following keys:
761
762 =over 4
763
764 =item svcnum
765
766 Primary key for this service
767
768 =item label
769
770 Name of this service
771
772 =item value
773
774 Meaningful user-specific identifier for the service (i.e. username, domain, or
775 mail alias).
776
777 =back
778
779 Account (svc_acct) services also have the following keys:
780
781 =over 4
782
783 =item username
784
785 Username
786
787 =item email
788
789 username@domain
790
791 =item seconds
792
793 Seconds remaining
794
795 =item upbytes
796
797 Upload bytes remaining
798
799 =item downbytes
800
801 Download bytes remaining
802
803 =item totalbytes
804
805 Total bytes remaining
806
807 =item recharge_amount
808
809 Cost of a recharge
810
811 =item recharge_seconds
812
813 Number of seconds gained by recharge
814
815 =item recharge_upbytes
816
817 Number of upload bytes gained by recharge
818
819 =item recharge_downbytes
820
821 Number of download bytes gained by recharge
822
823 =item recharge_totalbytes
824
825 Number of total bytes gained by recharge
826
827 =back
828
829 =back
830
831 =item order_pkg
832
833 Orders a package for this customer.
834
835 Takes a hash reference as parameter with the following keys:
836
837 =over 4
838
839 =item session_id
840
841 Session identifier
842
843 =item pkgpart
844
845 Package to order (see L<FS::part_pkg>).
846
847 =item svcpart
848
849 Service to order (see L<FS::part_svc>).
850
851 Normally optional; required only to provision a non-svc_acct service, or if the
852 package definition does not contain one svc_acct service definition with
853 quantity 1 (it may contain others with quantity >1).  A svcpart of "none" can
854 also be specified to indicate that no initial service should be provisioned.
855
856 =back
857
858 Fields used when provisioning an svc_acct service:
859
860 =over 4
861
862 =item username
863
864 Username
865
866 =item _password
867
868 Password
869
870 =item sec_phrase
871
872 Optional security phrase
873
874 =item popnum
875
876 Optional Access number number
877
878 =back
879
880 Fields used when provisioning an svc_domain service:
881
882 =over 4
883
884 =item domain
885
886 Domain
887
888 =back
889
890 Fields used when provisioning an svc_phone service:
891
892 =over 4
893
894 =item phonenum
895
896 Phone number
897
898 =item pin
899
900 Voicemail PIN
901
902 =item sip_password
903
904 SIP password
905
906 =back
907
908 Fields used when provisioning an svc_external service:
909
910 =over 4
911
912 =item id
913
914 External numeric ID.
915
916 =item title
917
918 External text title.
919
920 =back
921
922 Fields used when provisioning an svc_pbx service:
923
924 =over 4
925
926 =item id
927
928 Numeric ID.
929
930 =item name
931
932 Text name.
933
934 =back
935
936 Returns a hash reference with a single key, B<error>, empty on success, or an
937 error message on errors.  The special error '_decline' is returned for
938 declined transactions.
939
940 =item change_pkg
941
942 Changes a package for this customer.
943
944 Takes a hash reference as parameter with the following keys:
945
946 =over 4
947
948 =item session_id
949
950 Session identifier
951
952 =item pkgnum
953
954 Existing customer package.
955
956 =item pkgpart
957
958 New package to order (see L<FS::part_pkg>).
959
960 =back
961
962 Returns a hash reference with the following keys:
963
964 =over 4
965
966 =item error
967
968 Empty on success, or an error message on errors.  
969
970 =item pkgnum
971
972 On success, the new pkgnum
973
974 =back
975
976
977 =item renew_info
978
979 Provides useful info for early renewals.
980
981 Takes a hash reference as parameter with the following keys:
982
983 =over 4
984
985 =item session_id
986
987 Session identifier
988
989 =back
990
991 Returns a hash reference.  On errors, it contains a single key, B<error>, with
992 the error message.  Otherwise, contains a single key, B<dates>, pointing to
993 an array refernce of hash references.  Each hash reference contains the
994 following keys:
995
996 =over 4
997
998 =item bill_date
999
1000 (Future) Bill date.  Indicates a future date for which billing could be run.
1001 Specified as a integer UNIX timestamp.  Pass this value to the B<order_renew>
1002 function.
1003
1004 =item bill_date_pretty
1005
1006 (Future) Bill date as a human-readable string.  (Convenience for display;
1007 subject to change, so best not to parse for the date.)
1008
1009 =item amount
1010
1011 Base amount which will be charged if renewed early as of this date.
1012
1013 =item renew_date
1014
1015 Renewal date; i.e. even-futher future date at which the customer will be paid
1016 through if the early renewal is completed with the given B<bill-date>.
1017 Specified as a integer UNIX timestamp.
1018
1019 =item renew_date_pretty
1020
1021 Renewal date as a human-readable string.  (Convenience for display;
1022 subject to change, so best not to parse for the date.)
1023
1024 =item pkgnum
1025
1026 Package that will be renewed.
1027
1028 =item expire_date
1029
1030 Expiration date of the package that will be renewed.
1031
1032 =item expire_date_pretty
1033
1034 Expiration date of the package that will be renewed, as a human-readable
1035 string.  (Convenience for display; subject to change, so best not to parse for
1036 the date.)
1037
1038 =back
1039
1040 =item order_renew
1041
1042 Renews this customer early; i.e. runs billing for this customer in advance.
1043
1044 Takes a hash reference as parameter with the following keys:
1045
1046 =over 4
1047
1048 =item session_id
1049
1050 Session identifier
1051
1052 =item date
1053
1054 Integer date as returned by the B<renew_info> function, indicating the advance
1055 date for which to run billing.
1056
1057 =back
1058
1059 Returns a hash reference with a single key, B<error>, empty on success, or an
1060 error message on errors.
1061
1062 =item cancel_pkg
1063
1064 Cancels a package for this customer.
1065
1066 Takes a hash reference as parameter with the following keys:
1067
1068 =over 4
1069
1070 =item session_id
1071
1072 Session identifier
1073
1074 =item pkgpart
1075
1076 pkgpart of package to cancel
1077
1078 =back
1079
1080 Returns a hash reference with a single key, B<error>, empty on success, or an
1081 error message on errors.
1082
1083 =back
1084
1085 =head1 SIGNUP FUNCTIONS
1086
1087 =over 4
1088
1089 =item signup_info HASHREF
1090
1091 Takes a hash reference as parameter with the following keys:
1092
1093 =over 4
1094
1095 =item session_id - Optional agent/reseller interface session
1096
1097 =back
1098
1099 Returns a hash reference containing information that may be useful in
1100 displaying a signup page.  The hash reference contains the following keys:
1101
1102 =over 4
1103
1104 =item cust_main_county
1105
1106 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.
1107
1108 =item part_pkg
1109
1110 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
1111 an agentnum specified explicitly via reseller interface session_id in the
1112 options.
1113
1114 =item agent
1115
1116 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.
1117
1118 =item agentnum2part_pkg
1119
1120 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.
1121
1122 =item svc_acct_pop
1123
1124 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.
1125
1126 =item security_phrase
1127
1128 True if the "security_phrase" feature is enabled
1129
1130 =item payby
1131
1132 Array reference of acceptable payment types for signup
1133
1134 =over 4
1135
1136 =item CARD
1137
1138 credit card - automatic
1139
1140 =item DCRD
1141
1142 credit card - on-demand - version 1.5+ only
1143
1144 =item CHEK
1145
1146 electronic check - automatic
1147
1148 =item DCHK
1149
1150 electronic check - on-demand - version 1.5+ only
1151
1152 =item LECB
1153
1154 Phone bill billing
1155
1156 =item BILL
1157
1158 billing, not recommended for signups
1159
1160 =item COMP
1161
1162 free, definitely not recommended for signups
1163
1164 =item PREPAY
1165
1166 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1167
1168 =back
1169
1170 =item cvv_enabled
1171
1172 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1173
1174 =item msgcat
1175
1176 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".
1177
1178 =item statedefault
1179
1180 Default state
1181
1182 =item countrydefault
1183
1184 Default country
1185
1186 =back
1187
1188 =item new_customer HASHREF
1189
1190 Creates a new customer.  Takes a hash reference as parameter with the
1191 following keys:
1192
1193 =over 4
1194
1195 =item first
1196
1197 first name (required)
1198
1199 =item last
1200
1201 last name (required)
1202
1203 =item ss
1204
1205 (not typically collected; mostly used for ACH transactions)
1206
1207 =item company
1208
1209 Company name
1210
1211 =item address1 (required)
1212
1213 Address line one
1214
1215 =item address2
1216
1217 Address line two
1218
1219 =item city (required)
1220
1221 City
1222
1223 =item county
1224
1225 County
1226
1227 =item state (required)
1228
1229 State
1230
1231 =item zip (required)
1232
1233 Zip or postal code
1234
1235 =item daytime
1236
1237 Daytime phone number
1238
1239 =item night
1240
1241 Evening phone number
1242
1243 =item fax
1244
1245 Fax number
1246
1247 =item payby
1248
1249 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1250
1251 =item payinfo
1252
1253 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1254
1255 =item paycvv
1256
1257 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1258
1259 =item paydate
1260
1261 Expiration date for CARD/DCRD
1262
1263 =item payname
1264
1265 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1266
1267 =item invoicing_list
1268
1269 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),
1270
1271 =item referral_custnum
1272
1273 referring customer number
1274
1275 =item agentnum
1276
1277 Agent number
1278
1279 =item pkgpart
1280
1281 pkgpart of initial package
1282
1283 =item username
1284
1285 Username
1286
1287 =item _password
1288
1289 Password
1290
1291 =item sec_phrase
1292
1293 Security phrase
1294
1295 =item popnum
1296
1297 Access number (index, not the literal number)
1298
1299 =item countrycode
1300
1301 Country code (to be provisioned as a service)
1302
1303 =item phonenum
1304
1305 Phone number (to be provisioned as a service)
1306
1307 =item pin
1308
1309 Voicemail PIN
1310
1311 =back
1312
1313 Returns a hash reference with the following keys:
1314
1315 =over 4
1316
1317 =item error
1318
1319 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)
1320
1321 =back
1322
1323 =item regionselector HASHREF | LIST
1324
1325 Takes as input a hashref or list of key/value pairs with the following keys:
1326
1327 =over 4
1328
1329 =item selected_county
1330
1331 Currently selected county
1332
1333 =item selected_state
1334
1335 Currently selected state
1336
1337 =item selected_country
1338
1339 Currently selected country
1340
1341 =item prefix
1342
1343 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1344
1345 =item onchange
1346
1347 Specify a javascript subroutine to call on changes
1348
1349 =item default_state
1350
1351 Default state
1352
1353 =item default_country
1354
1355 Default country
1356
1357 =item locales
1358
1359 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>.
1360
1361 =back
1362
1363 Returns a list consisting of three HTML fragments for county selection,
1364 state selection and country selection, respectively.
1365
1366 =cut
1367
1368 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1369 sub regionselector {
1370   my $param;
1371   if ( ref($_[0]) ) {
1372     $param = shift;
1373   } else {
1374     $param = { @_ };
1375   }
1376   $param->{'selected_country'} ||= $param->{'default_country'};
1377   $param->{'selected_state'} ||= $param->{'default_state'};
1378
1379   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1380
1381   my $countyflag = 0;
1382
1383   my %cust_main_county;
1384
1385 #  unless ( @cust_main_county ) { #cache 
1386     #@cust_main_county = qsearch('cust_main_county', {} );
1387     #foreach my $c ( @cust_main_county ) {
1388     foreach my $c ( @{ $param->{'locales'} } ) {
1389       #$countyflag=1 if $c->county;
1390       $countyflag=1 if $c->{county};
1391       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1392       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1393       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1394     }
1395 #  }
1396   $countyflag=1 if $param->{selected_county};
1397
1398   my $script_html = <<END;
1399     <SCRIPT>
1400     function opt(what,value,text) {
1401       var optionName = new Option(text, value, false, false);
1402       var length = what.length;
1403       what.options[length] = optionName;
1404     }
1405     function ${prefix}country_changed(what) {
1406       country = what.options[what.selectedIndex].text;
1407       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1408           what.form.${prefix}state.options[i] = null;
1409 END
1410       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1411
1412   foreach my $country ( sort keys %cust_main_county ) {
1413     $script_html .= "\nif ( country == \"$country\" ) {\n";
1414     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1415       my $text = $state || '(n/a)';
1416       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1417     }
1418     $script_html .= "}\n";
1419   }
1420
1421   $script_html .= <<END;
1422     }
1423     function ${prefix}state_changed(what) {
1424 END
1425
1426   if ( $countyflag ) {
1427     $script_html .= <<END;
1428       state = what.options[what.selectedIndex].text;
1429       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1430       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1431           what.form.${prefix}county.options[i] = null;
1432 END
1433
1434     foreach my $country ( sort keys %cust_main_county ) {
1435       $script_html .= "\nif ( country == \"$country\" ) {\n";
1436       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1437         $script_html .= "\nif ( state == \"$state\" ) {\n";
1438           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1439           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1440             my $text = $county || '(n/a)';
1441             $script_html .=
1442               qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1443           }
1444         $script_html .= "}\n";
1445       }
1446       $script_html .= "}\n";
1447     }
1448   }
1449
1450   $script_html .= <<END;
1451     }
1452     </SCRIPT>
1453 END
1454
1455   my $county_html = $script_html;
1456   if ( $countyflag ) {
1457     $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1458     foreach my $county ( 
1459       sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1460     ) {
1461       my $text = $county || '(n/a)';
1462       $county_html .= qq!<OPTION VALUE="$county"!.
1463                       ($county eq $param->{'selected_county'} ? 
1464                         ' SELECTED>' : 
1465                         '>'
1466                       ).
1467                       $text.
1468                       '</OPTION>';
1469     }
1470     $county_html .= '</SELECT>';
1471   } else {
1472     $county_html .=
1473       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1474   }
1475
1476   my $state_html = qq!<SELECT NAME="${prefix}state" !.
1477                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1478   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1479     my $text = $state || '(n/a)';
1480     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1481     $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1482   }
1483   $state_html .= '</SELECT>';
1484
1485   my $country_html = '';
1486   if ( scalar( keys %cust_main_county ) > 1 )  {
1487
1488     $country_html = qq(<SELECT NAME="${prefix}country" ).
1489                     qq(onChange="${prefix}country_changed(this); ).
1490                                  $param->{'onchange'}.
1491                                '"'.
1492                       '>';
1493     my $countrydefault = $param->{default_country} || 'US';
1494     foreach my $country (
1495       sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1496         keys %cust_main_county
1497     ) {
1498       my $selected = $country eq $param->{'selected_country'}
1499                        ? ' SELECTED'
1500                        : '';
1501       $country_html .= "\n<OPTION$selected>$country</OPTION>"
1502     }
1503     $country_html .= '</SELECT>';
1504   } else {
1505
1506     $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1507                             ' VALUE="'. (keys %cust_main_county )[0]. '">';
1508
1509   }
1510
1511   ($county_html, $state_html, $country_html);
1512
1513 }
1514
1515 sub regionselector_hashref {
1516   my ($county_html, $state_html, $country_html) = regionselector(@_);
1517   {
1518     'county_html'  => $county_html,
1519     'state_html'   => $state_html,
1520     'country_html' => $country_html,
1521   };
1522 }
1523
1524 =item location_form HASHREF | LIST
1525
1526 Takes as input a hashref or list of key/value pairs with the following keys:
1527
1528 =over 4
1529
1530 =item session_id
1531
1532 Current customer session_id
1533
1534 =item no_asterisks
1535
1536 Omit red asterisks from required fields.
1537
1538 =item address1_label
1539
1540 Label for first address line.
1541
1542 =back
1543
1544 Returns an HTML fragment for a location form (address, city, state, zip,
1545 country)
1546
1547 =cut
1548
1549 sub location_form {
1550   my $param;
1551   if ( ref($_[0]) ) {
1552     $param = shift;
1553   } else {
1554     $param = { @_ };
1555   }
1556
1557   my $session_id = delete $param->{'session_id'};
1558
1559   my $rv = mason_comp( 'session_id' => $session_id,
1560                        'comp'       => '/elements/location.html',
1561                        'args'       => [ %$param ],
1562                      );
1563
1564   #hmm.
1565   $rv->{'error'} || $rv->{'output'};
1566
1567 }
1568
1569
1570 #=item expselect HASHREF | LIST
1571 #
1572 #Takes as input a hashref or list of key/value pairs with the following keys:
1573 #
1574 #=over 4
1575 #
1576 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1577 #
1578 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1579 #
1580 #=back
1581
1582 =item expselect PREFIX [ DATE ]
1583
1584 Takes as input a unique prefix string and the current expiration date, in
1585 yyyy-mm-dd or m-d-yyyy format
1586
1587 Returns an HTML fragments for expiration date selection.
1588
1589 =cut
1590
1591 sub expselect {
1592   #my $param;
1593   #if ( ref($_[0]) ) {
1594   #  $param = shift;
1595   #} else {
1596   #  $param = { @_ };
1597   #my $prefix = $param->{'prefix'};
1598   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1599   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
1600   my $prefix = shift;
1601   my $date = scalar(@_) ? shift : '';
1602
1603   my( $m, $y ) = ( 0, 0 );
1604   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1605     ( $m, $y ) = ( $2, $1 );
1606   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1607     ( $m, $y ) = ( $1, $3 );
1608   }
1609   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1610   for ( 1 .. 12 ) {
1611     $return .= qq!<OPTION VALUE="$_"!;
1612     $return .= " SELECTED" if $_ == $m;
1613     $return .= ">$_";
1614   }
1615   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1616   my @t = localtime;
1617   my $thisYear = $t[5] + 1900;
1618   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1619     $return .= qq!<OPTION VALUE="$_"!;
1620     $return .= " SELECTED" if $_ == $y;
1621     $return .= ">$_";
1622   }
1623   $return .= "</SELECT>";
1624
1625   $return;
1626 }
1627
1628 =item popselector HASHREF | LIST
1629
1630 Takes as input a hashref or list of key/value pairs with the following keys:
1631
1632 =over 4
1633
1634 =item popnum
1635
1636 Access number number
1637
1638 =item pops
1639
1640 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>.
1641
1642 =back
1643
1644 Returns an HTML fragment for access number selection.
1645
1646 =cut
1647
1648 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1649 sub popselector {
1650   my $param;
1651   if ( ref($_[0]) ) {
1652     $param = shift;
1653   } else {
1654     $param = { @_ };
1655   }
1656   my $popnum = $param->{'popnum'};
1657   my $pops = $param->{'pops'};
1658
1659   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1660   return $pops->[0]{city}. ', '. $pops->[0]{state}.
1661          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1662          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1663     if scalar(@$pops) == 1;
1664
1665   my %pop = ();
1666   my %popnum2pop = ();
1667   foreach (@$pops) {
1668     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1669     $popnum2pop{$_->{popnum}} = $_;
1670   }
1671
1672   my $text = <<END;
1673     <SCRIPT>
1674     function opt(what,href,text) {
1675       var optionName = new Option(text, href, false, false)
1676       var length = what.length;
1677       what.options[length] = optionName;
1678     }
1679 END
1680
1681   my $init_popstate = $param->{'init_popstate'};
1682   if ( $init_popstate ) {
1683     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1684              $init_popstate. '">';
1685   } else {
1686     $text .= <<END;
1687       function acstate_changed(what) {
1688         state = what.options[what.selectedIndex].text;
1689         what.form.popac.options.length = 0
1690         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1691 END
1692   } 
1693
1694   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1695   foreach my $state ( sort { $a cmp $b } @states ) {
1696     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1697
1698     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1699       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1700       if ($ac eq $param->{'popac'}) {
1701         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1702       }
1703     }
1704     $text .= "}\n" unless $init_popstate;
1705   }
1706   $text .= "popac_changed(what.form.popac)}\n";
1707
1708   $text .= <<END;
1709   function popac_changed(what) {
1710     ac = what.options[what.selectedIndex].text;
1711     what.form.popnum.options.length = 0;
1712     what.form.popnum.options[0] = new Option("City", "-1", false, true);
1713
1714 END
1715
1716   foreach my $state ( @states ) {
1717     foreach my $popac ( keys %{ $pop{$state} } ) {
1718       $text .= "\nif ( ac == \"$popac\" ) {\n";
1719
1720       foreach my $pop ( @{$pop{$state}->{$popac}}) {
1721         my $o_popnum = $pop->{popnum};
1722         my $poptext =  $pop->{city}. ', '. $pop->{state}.
1723                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1724
1725         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1726         if ($popnum == $o_popnum) {
1727           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1728         }
1729       }
1730       $text .= "}\n";
1731     }
1732   }
1733
1734
1735   $text .= "}\n</SCRIPT>\n";
1736
1737   $param->{'acstate'} = '' unless defined($param->{'acstate'});
1738
1739   $text .=
1740     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1741     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1742   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1743            ">$_" foreach sort { $a cmp $b } @states;
1744   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
1745
1746   $text .=
1747     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1748     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1749
1750   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1751
1752
1753   #comment this block to disable initial list polulation
1754   my @initial_select = ();
1755   if ( scalar( @$pops ) > 100 ) {
1756     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1757   } else {
1758     @initial_select = @$pops;
1759   }
1760   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1761     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1762              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1763              $pop->{city}. ', '. $pop->{state}.
1764                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1765   }
1766
1767   $text .= qq!</SELECT></TD></TR></TABLE>!;
1768
1769   $text;
1770
1771 }
1772
1773 =item domainselector HASHREF | LIST
1774
1775 Takes as input a hashref or list of key/value pairs with the following keys:
1776
1777 =over 4
1778
1779 =item pkgnum
1780
1781 Package number
1782
1783 =item domsvc
1784
1785 Service number of the selected item.
1786
1787 =back
1788
1789 Returns an HTML fragment for domain selection.
1790
1791 =cut
1792
1793 sub domainselector {
1794   my $param;
1795   if ( ref($_[0]) ) {
1796     $param = shift;
1797   } else {
1798     $param = { @_ };
1799   }
1800   my $domsvc= $param->{'domsvc'};
1801   my $rv = 
1802       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1803   my $domains = $rv->{'domains'};
1804   $domsvc = $rv->{'domsvc'} unless $domsvc;
1805
1806   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1807     unless scalar(keys %$domains);
1808
1809   if (scalar(keys %$domains) == 1) {
1810     my $key;
1811     foreach(keys %$domains) {
1812       $key = $_;
1813     }
1814     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1815            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1816   }
1817
1818   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1819
1820
1821   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1822     $text .= qq!<OPTION VALUE="!. $domain. '"'.
1823              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1824              $domains->{$domain};
1825   }
1826
1827   $text .= qq!</SELECT></TD></TR>!;
1828
1829   $text;
1830
1831 }
1832
1833 =item didselector HASHREF | LIST
1834
1835 Takes as input a hashref or list of key/value pairs with the following keys:
1836
1837 =over 4
1838
1839 =item field
1840
1841 Field name for the returned HTML fragment.
1842
1843 =item svcpart
1844
1845 Service definition (see L<FS::part_svc>)
1846
1847 =back
1848
1849 Returns an HTML fragment for DID selection.
1850
1851 =cut
1852
1853 sub didselector {
1854   my $param;
1855   if ( ref($_[0]) ) {
1856     $param = shift;
1857   } else {
1858     $param = { @_ };
1859   }
1860
1861   my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1862                        'args'=>[ %$param ],
1863                      );
1864
1865   #hmm.
1866   $rv->{'error'} || $rv->{'output'};
1867
1868 }
1869
1870 =back
1871
1872 =head1 RESELLER FUNCTIONS
1873
1874 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1875 with their active session, and the B<customer_info> and B<order_pkg> functions
1876 with their active session and an additional I<custnum> parameter.
1877
1878 For the most part, development of the reseller web interface has been
1879 superceded by agent-virtualized access to the backend.
1880
1881 =over 4
1882
1883 =item agent_login
1884
1885 Agent login
1886
1887 =item agent_info
1888
1889 Agent info
1890
1891 =item agent_list_customers
1892
1893 List agent's customers.
1894
1895 =back
1896
1897 =head1 BUGS
1898
1899 =head1 SEE ALSO
1900
1901 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
1902
1903 =cut
1904
1905 1;
1906