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