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