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