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