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