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