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