80d9dbc8aa35dfe8b123d934241425f1d7b06c9b
[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 new_customer_minimal );
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 =item provision_acct 
1157
1158 Provisions an account (svc_acct).
1159
1160 Takes a hash references as parameter with the following keys:
1161
1162 =over 4
1163
1164 =item session_id
1165
1166 Session identifier
1167
1168 =item pkgnum
1169
1170 pkgnum of package into which this service is provisioned
1171
1172 =item svcpart
1173
1174 svcpart or service definition to provision
1175
1176 =item username
1177
1178 =item domsvc
1179
1180 =item _password
1181
1182 =back
1183
1184 =item provision_phone
1185
1186 Provisions a phone number (svc_phone).
1187
1188 Takes a hash references as parameter with the following keys:
1189
1190 =over 4
1191
1192 =item session_id
1193
1194 Session identifier
1195
1196 =item pkgnum
1197
1198 pkgnum of package into which this service is provisioned
1199
1200 =item svcpart
1201
1202 svcpart or service definition to provision
1203
1204 =item countrycode
1205
1206 =item phonenum
1207
1208 =item address1
1209
1210 =item address2
1211
1212 =item city
1213
1214 =item county
1215
1216 =item state
1217
1218 =item zip
1219
1220 =item country
1221
1222 E911 Address (optional)
1223
1224 =back
1225
1226 =item provision_external
1227
1228 Provisions an external service (svc_external).
1229
1230 Takes a hash references as parameter with the following keys:
1231
1232 =over 4
1233
1234 =item session_id
1235
1236 Session identifier
1237
1238 =item pkgnum
1239
1240 pkgnum of package into which this service is provisioned
1241
1242 =item svcpart
1243
1244 svcpart or service definition to provision
1245
1246 =item id
1247
1248 =item title
1249
1250 =back
1251
1252 =back
1253
1254 =head1 SIGNUP FUNCTIONS
1255
1256 =over 4
1257
1258 =item signup_info HASHREF
1259
1260 Takes a hash reference as parameter with the following keys:
1261
1262 =over 4
1263
1264 =item session_id - Optional agent/reseller interface session
1265
1266 =back
1267
1268 Returns a hash reference containing information that may be useful in
1269 displaying a signup page.  The hash reference contains the following keys:
1270
1271 =over 4
1272
1273 =item cust_main_county
1274
1275 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.
1276
1277 =item part_pkg
1278
1279 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
1280 an agentnum specified explicitly via reseller interface session_id in the
1281 options.
1282
1283 =item agent
1284
1285 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.
1286
1287 =item agentnum2part_pkg
1288
1289 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.
1290
1291 =item svc_acct_pop
1292
1293 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.
1294
1295 =item security_phrase
1296
1297 True if the "security_phrase" feature is enabled
1298
1299 =item payby
1300
1301 Array reference of acceptable payment types for signup
1302
1303 =over 4
1304
1305 =item CARD
1306
1307 credit card - automatic
1308
1309 =item DCRD
1310
1311 credit card - on-demand - version 1.5+ only
1312
1313 =item CHEK
1314
1315 electronic check - automatic
1316
1317 =item DCHK
1318
1319 electronic check - on-demand - version 1.5+ only
1320
1321 =item LECB
1322
1323 Phone bill billing
1324
1325 =item BILL
1326
1327 billing, not recommended for signups
1328
1329 =item COMP
1330
1331 free, definitely not recommended for signups
1332
1333 =item PREPAY
1334
1335 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1336
1337 =back
1338
1339 =item cvv_enabled
1340
1341 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1342
1343 =item msgcat
1344
1345 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".
1346
1347 =item statedefault
1348
1349 Default state
1350
1351 =item countrydefault
1352
1353 Default country
1354
1355 =back
1356
1357 =item new_customer_minimal HASHREF
1358
1359 Creates a new customer.
1360
1361 Current differences from new_customer: An address is not required.  promo_code
1362 and reg_code are not supported.  If invoicing_list and _password is passed, a
1363 contact will be created with self-service access (no pkgpart or username is
1364 necessary).  No initial billing is run (this may change in a future version).
1365
1366 Takes a hash reference as parameter with the following keys:
1367
1368 =over 4
1369
1370 =item first
1371
1372 first name (required)
1373
1374 =item last
1375
1376 last name (required)
1377
1378 =item ss
1379
1380 (not typically collected; mostly used for ACH transactions)
1381
1382 =item company
1383
1384 Company name
1385
1386 =item address1
1387
1388 Address line one
1389
1390 =item address2
1391
1392 Address line two
1393
1394 =item city
1395
1396 City
1397
1398 =item county
1399
1400 County
1401
1402 =item state
1403
1404 State
1405
1406 =item zip
1407
1408 Zip or postal code
1409
1410 =item daytime
1411
1412 Daytime phone number
1413
1414 =item night
1415
1416 Evening phone number
1417
1418 =item fax
1419
1420 Fax number
1421
1422 =item payby
1423
1424 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1425
1426 =item payinfo
1427
1428 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1429
1430 =item paycvv
1431
1432 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1433
1434 =item paydate
1435
1436 Expiration date for CARD/DCRD
1437
1438 =item payname
1439
1440 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1441
1442 =item invoicing_list
1443
1444 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),
1445
1446 =item referral_custnum
1447
1448 referring customer number
1449
1450 =item agentnum
1451
1452 Agent number
1453
1454 =item pkgpart
1455
1456 pkgpart of initial package
1457
1458 =item username
1459
1460 Username
1461
1462 =item _password
1463
1464 Password
1465
1466 =item sec_phrase
1467
1468 Security phrase
1469
1470 =item popnum
1471
1472 Access number (index, not the literal number)
1473
1474 =item countrycode
1475
1476 Country code (to be provisioned as a service)
1477
1478 =item phonenum
1479
1480 Phone number (to be provisioned as a service)
1481
1482 =item pin
1483
1484 Voicemail PIN
1485
1486 =back
1487
1488 Returns a hash reference with the following keys:
1489
1490 =over 4
1491
1492 =item error
1493
1494 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)
1495
1496 =back
1497
1498 =item new_customer HASHREF
1499
1500 Creates a new customer.  Takes a hash reference as parameter with the
1501 following keys:
1502
1503 =over 4
1504
1505 =item first
1506
1507 first name (required)
1508
1509 =item last
1510
1511 last name (required)
1512
1513 =item ss
1514
1515 (not typically collected; mostly used for ACH transactions)
1516
1517 =item company
1518
1519 Company name
1520
1521 =item address1 (required)
1522
1523 Address line one
1524
1525 =item address2
1526
1527 Address line two
1528
1529 =item city (required)
1530
1531 City
1532
1533 =item county
1534
1535 County
1536
1537 =item state (required)
1538
1539 State
1540
1541 =item zip (required)
1542
1543 Zip or postal code
1544
1545 =item daytime
1546
1547 Daytime phone number
1548
1549 =item night
1550
1551 Evening phone number
1552
1553 =item fax
1554
1555 Fax number
1556
1557 =item payby
1558
1559 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1560
1561 =item payinfo
1562
1563 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1564
1565 =item paycvv
1566
1567 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1568
1569 =item paydate
1570
1571 Expiration date for CARD/DCRD
1572
1573 =item payname
1574
1575 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1576
1577 =item invoicing_list
1578
1579 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),
1580
1581 =item referral_custnum
1582
1583 referring customer number
1584
1585 =item agentnum
1586
1587 Agent number
1588
1589 =item pkgpart
1590
1591 pkgpart of initial package
1592
1593 =item username
1594
1595 Username
1596
1597 =item _password
1598
1599 Password
1600
1601 =item sec_phrase
1602
1603 Security phrase
1604
1605 =item popnum
1606
1607 Access number (index, not the literal number)
1608
1609 =item countrycode
1610
1611 Country code (to be provisioned as a service)
1612
1613 =item phonenum
1614
1615 Phone number (to be provisioned as a service)
1616
1617 =item pin
1618
1619 Voicemail PIN
1620
1621 =back
1622
1623 Returns a hash reference with the following keys:
1624
1625 =over 4
1626
1627 =item error
1628
1629 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)
1630
1631 =back
1632
1633 =item regionselector HASHREF | LIST
1634
1635 Takes as input a hashref or list of key/value pairs with the following keys:
1636
1637 =over 4
1638
1639 =item selected_county
1640
1641 Currently selected county
1642
1643 =item selected_state
1644
1645 Currently selected state
1646
1647 =item selected_country
1648
1649 Currently selected country
1650
1651 =item prefix
1652
1653 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1654
1655 =item onchange
1656
1657 Specify a javascript subroutine to call on changes
1658
1659 =item default_state
1660
1661 Default state
1662
1663 =item default_country
1664
1665 Default country
1666
1667 =item locales
1668
1669 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>.
1670
1671 =back
1672
1673 Returns a list consisting of three HTML fragments for county selection,
1674 state selection and country selection, respectively.
1675
1676 =cut
1677
1678 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1679 sub regionselector {
1680   my $param;
1681   if ( ref($_[0]) ) {
1682     $param = shift;
1683   } else {
1684     $param = { @_ };
1685   }
1686   $param->{'selected_country'} ||= $param->{'default_country'};
1687   $param->{'selected_state'} ||= $param->{'default_state'};
1688
1689   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1690
1691   my $countyflag = 0;
1692
1693   my %cust_main_county;
1694
1695 #  unless ( @cust_main_county ) { #cache 
1696     #@cust_main_county = qsearch('cust_main_county', {} );
1697     #foreach my $c ( @cust_main_county ) {
1698     foreach my $c ( @{ $param->{'locales'} } ) {
1699       #$countyflag=1 if $c->county;
1700       $countyflag=1 if $c->{county};
1701       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1702       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1703       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1704     }
1705 #  }
1706   $countyflag=1 if $param->{selected_county};
1707
1708   my $script_html = <<END;
1709     <SCRIPT>
1710     function opt(what,value,text) {
1711       var optionName = new Option(text, value, false, false);
1712       var length = what.length;
1713       what.options[length] = optionName;
1714     }
1715     function ${prefix}country_changed(what) {
1716       country = what.options[what.selectedIndex].text;
1717       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1718           what.form.${prefix}state.options[i] = null;
1719 END
1720       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1721
1722   foreach my $country ( sort keys %cust_main_county ) {
1723     $script_html .= "\nif ( country == \"$country\" ) {\n";
1724     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1725       my $text = $state || '(n/a)';
1726       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1727     }
1728     $script_html .= "}\n";
1729   }
1730
1731   $script_html .= <<END;
1732     }
1733     function ${prefix}state_changed(what) {
1734 END
1735
1736   if ( $countyflag ) {
1737     $script_html .= <<END;
1738       state = what.options[what.selectedIndex].text;
1739       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1740       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1741           what.form.${prefix}county.options[i] = null;
1742 END
1743
1744     foreach my $country ( sort keys %cust_main_county ) {
1745       $script_html .= "\nif ( country == \"$country\" ) {\n";
1746       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1747         $script_html .= "\nif ( state == \"$state\" ) {\n";
1748           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1749           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1750             my $text = $county || '(n/a)';
1751             $script_html .=
1752               qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1753           }
1754         $script_html .= "}\n";
1755       }
1756       $script_html .= "}\n";
1757     }
1758   }
1759
1760   $script_html .= <<END;
1761     }
1762     </SCRIPT>
1763 END
1764
1765   my $county_html = $script_html;
1766   if ( $countyflag ) {
1767     $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1768     foreach my $county ( 
1769       sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1770     ) {
1771       my $text = $county || '(n/a)';
1772       $county_html .= qq!<OPTION VALUE="$county"!.
1773                       ($county eq $param->{'selected_county'} ? 
1774                         ' SELECTED>' : 
1775                         '>'
1776                       ).
1777                       $text.
1778                       '</OPTION>';
1779     }
1780     $county_html .= '</SELECT>';
1781   } else {
1782     $county_html .=
1783       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1784   }
1785
1786   my $state_html = qq!<SELECT NAME="${prefix}state" !.
1787                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1788   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1789     my $text = $state || '(n/a)';
1790     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1791     $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1792   }
1793   $state_html .= '</SELECT>';
1794
1795   my $country_html = '';
1796   if ( scalar( keys %cust_main_county ) > 1 )  {
1797
1798     $country_html = qq(<SELECT NAME="${prefix}country" ).
1799                     qq(onChange="${prefix}country_changed(this); ).
1800                                  $param->{'onchange'}.
1801                                '"'.
1802                       '>';
1803     my $countrydefault = $param->{default_country} || 'US';
1804     foreach my $country (
1805       sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1806         keys %cust_main_county
1807     ) {
1808       my $selected = $country eq $param->{'selected_country'}
1809                        ? ' SELECTED'
1810                        : '';
1811       $country_html .= "\n<OPTION$selected>$country</OPTION>"
1812     }
1813     $country_html .= '</SELECT>';
1814   } else {
1815
1816     $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1817                             ' VALUE="'. (keys %cust_main_county )[0]. '">';
1818
1819   }
1820
1821   ($county_html, $state_html, $country_html);
1822
1823 }
1824
1825 sub regionselector_hashref {
1826   my ($county_html, $state_html, $country_html) = regionselector(@_);
1827   {
1828     'county_html'  => $county_html,
1829     'state_html'   => $state_html,
1830     'country_html' => $country_html,
1831   };
1832 }
1833
1834 =item location_form HASHREF | LIST
1835
1836 Takes as input a hashref or list of key/value pairs with the following keys:
1837
1838 =over 4
1839
1840 =item session_id
1841
1842 Current customer session_id
1843
1844 =item no_asterisks
1845
1846 Omit red asterisks from required fields.
1847
1848 =item address1_label
1849
1850 Label for first address line.
1851
1852 =back
1853
1854 Returns an HTML fragment for a location form (address, city, state, zip,
1855 country)
1856
1857 =cut
1858
1859 sub location_form {
1860   my $param;
1861   if ( ref($_[0]) ) {
1862     $param = shift;
1863   } else {
1864     $param = { @_ };
1865   }
1866
1867   my $session_id = delete $param->{'session_id'};
1868
1869   my $rv = mason_comp( 'session_id' => $session_id,
1870                        'comp'       => '/elements/location.html',
1871                        'args'       => [ %$param ],
1872                      );
1873
1874   #hmm.
1875   $rv->{'error'} || $rv->{'output'};
1876
1877 }
1878
1879
1880 #=item expselect HASHREF | LIST
1881 #
1882 #Takes as input a hashref or list of key/value pairs with the following keys:
1883 #
1884 #=over 4
1885 #
1886 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
1887 #
1888 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1889 #
1890 #=back
1891
1892 =item expselect PREFIX [ DATE ]
1893
1894 Takes as input a unique prefix string and the current expiration date, in
1895 yyyy-mm-dd or m-d-yyyy format
1896
1897 Returns an HTML fragments for expiration date selection.
1898
1899 =cut
1900
1901 sub expselect {
1902   #my $param;
1903   #if ( ref($_[0]) ) {
1904   #  $param = shift;
1905   #} else {
1906   #  $param = { @_ };
1907   #my $prefix = $param->{'prefix'};
1908   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1909   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
1910   my $prefix = shift;
1911   my $date = scalar(@_) ? shift : '';
1912
1913   my( $m, $y ) = ( 0, 0 );
1914   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1915     ( $m, $y ) = ( $2, $1 );
1916   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1917     ( $m, $y ) = ( $1, $3 );
1918   }
1919   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1920   for ( 1 .. 12 ) {
1921     $return .= qq!<OPTION VALUE="$_"!;
1922     $return .= " SELECTED" if $_ == $m;
1923     $return .= ">$_";
1924   }
1925   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1926   my @t = localtime;
1927   my $thisYear = $t[5] + 1900;
1928   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1929     $return .= qq!<OPTION VALUE="$_"!;
1930     $return .= " SELECTED" if $_ == $y;
1931     $return .= ">$_";
1932   }
1933   $return .= "</SELECT>";
1934
1935   $return;
1936 }
1937
1938 =item popselector HASHREF | LIST
1939
1940 Takes as input a hashref or list of key/value pairs with the following keys:
1941
1942 =over 4
1943
1944 =item popnum
1945
1946 Access number number
1947
1948 =item pops
1949
1950 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>.
1951
1952 =back
1953
1954 Returns an HTML fragment for access number selection.
1955
1956 =cut
1957
1958 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1959 sub popselector {
1960   my $param;
1961   if ( ref($_[0]) ) {
1962     $param = shift;
1963   } else {
1964     $param = { @_ };
1965   }
1966   my $popnum = $param->{'popnum'};
1967   my $pops = $param->{'pops'};
1968
1969   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1970   return $pops->[0]{city}. ', '. $pops->[0]{state}.
1971          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1972          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1973     if scalar(@$pops) == 1;
1974
1975   my %pop = ();
1976   my %popnum2pop = ();
1977   foreach (@$pops) {
1978     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1979     $popnum2pop{$_->{popnum}} = $_;
1980   }
1981
1982   my $text = <<END;
1983     <SCRIPT>
1984     function opt(what,href,text) {
1985       var optionName = new Option(text, href, false, false)
1986       var length = what.length;
1987       what.options[length] = optionName;
1988     }
1989 END
1990
1991   my $init_popstate = $param->{'init_popstate'};
1992   if ( $init_popstate ) {
1993     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1994              $init_popstate. '">';
1995   } else {
1996     $text .= <<END;
1997       function acstate_changed(what) {
1998         state = what.options[what.selectedIndex].text;
1999         what.form.popac.options.length = 0
2000         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2001 END
2002   } 
2003
2004   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2005   foreach my $state ( sort { $a cmp $b } @states ) {
2006     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2007
2008     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2009       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2010       if ($ac eq $param->{'popac'}) {
2011         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2012       }
2013     }
2014     $text .= "}\n" unless $init_popstate;
2015   }
2016   $text .= "popac_changed(what.form.popac)}\n";
2017
2018   $text .= <<END;
2019   function popac_changed(what) {
2020     ac = what.options[what.selectedIndex].text;
2021     what.form.popnum.options.length = 0;
2022     what.form.popnum.options[0] = new Option("City", "-1", false, true);
2023
2024 END
2025
2026   foreach my $state ( @states ) {
2027     foreach my $popac ( keys %{ $pop{$state} } ) {
2028       $text .= "\nif ( ac == \"$popac\" ) {\n";
2029
2030       foreach my $pop ( @{$pop{$state}->{$popac}}) {
2031         my $o_popnum = $pop->{popnum};
2032         my $poptext =  $pop->{city}. ', '. $pop->{state}.
2033                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2034
2035         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2036         if ($popnum == $o_popnum) {
2037           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2038         }
2039       }
2040       $text .= "}\n";
2041     }
2042   }
2043
2044
2045   $text .= "}\n</SCRIPT>\n";
2046
2047   $param->{'acstate'} = '' unless defined($param->{'acstate'});
2048
2049   $text .=
2050     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2051     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2052   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2053            ">$_" foreach sort { $a cmp $b } @states;
2054   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
2055
2056   $text .=
2057     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2058     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2059
2060   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2061
2062
2063   #comment this block to disable initial list polulation
2064   my @initial_select = ();
2065   if ( scalar( @$pops ) > 100 ) {
2066     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2067   } else {
2068     @initial_select = @$pops;
2069   }
2070   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2071     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2072              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2073              $pop->{city}. ', '. $pop->{state}.
2074                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2075   }
2076
2077   $text .= qq!</SELECT></TD></TR></TABLE>!;
2078
2079   $text;
2080
2081 }
2082
2083 =item domainselector HASHREF | LIST
2084
2085 Takes as input a hashref or list of key/value pairs with the following keys:
2086
2087 =over 4
2088
2089 =item pkgnum
2090
2091 Package number
2092
2093 =item domsvc
2094
2095 Service number of the selected item.
2096
2097 =back
2098
2099 Returns an HTML fragment for domain selection.
2100
2101 =cut
2102
2103 sub domainselector {
2104   my $param;
2105   if ( ref($_[0]) ) {
2106     $param = shift;
2107   } else {
2108     $param = { @_ };
2109   }
2110   my $domsvc= $param->{'domsvc'};
2111   my $rv = 
2112       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2113   my $domains = $rv->{'domains'};
2114   $domsvc = $rv->{'domsvc'} unless $domsvc;
2115
2116   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2117     unless scalar(keys %$domains);
2118
2119   if (scalar(keys %$domains) == 1) {
2120     my $key;
2121     foreach(keys %$domains) {
2122       $key = $_;
2123     }
2124     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2125            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2126   }
2127
2128   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2129
2130   $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2131
2132   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2133     $text .= qq!<OPTION VALUE="!. $domain. '"'.
2134              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2135              $domains->{$domain};
2136   }
2137
2138   $text .= qq!</SELECT></TD></TR>!;
2139
2140   $text;
2141
2142 }
2143
2144 =item didselector HASHREF | LIST
2145
2146 Takes as input a hashref or list of key/value pairs with the following keys:
2147
2148 =over 4
2149
2150 =item field
2151
2152 Field name for the returned HTML fragment.
2153
2154 =item svcpart
2155
2156 Service definition (see L<FS::part_svc>)
2157
2158 =back
2159
2160 Returns an HTML fragment for DID selection.
2161
2162 =cut
2163
2164 sub didselector {
2165   my $param;
2166   if ( ref($_[0]) ) {
2167     $param = shift;
2168   } else {
2169     $param = { @_ };
2170   }
2171
2172   my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2173                        'args'=>[ %$param ],
2174                      );
2175
2176   #hmm.
2177   $rv->{'error'} || $rv->{'output'};
2178
2179 }
2180
2181 =back
2182
2183 =head1 RESELLER FUNCTIONS
2184
2185 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2186 with their active session, and the B<customer_info> and B<order_pkg> functions
2187 with their active session and an additional I<custnum> parameter.
2188
2189 For the most part, development of the reseller web interface has been
2190 superceded by agent-virtualized access to the backend.
2191
2192 =over 4
2193
2194 =item agent_login
2195
2196 Agent login
2197
2198 =item agent_info
2199
2200 Agent info
2201
2202 =item agent_list_customers
2203
2204 List agent's customers.
2205
2206 =back
2207
2208 =head1 BUGS
2209
2210 =head1 SEE ALSO
2211
2212 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
2213
2214 =cut
2215
2216 1;
2217