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