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