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