RT# 83365 - Added city select to work like back end
[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 use Time::HiRes;
13
14 $VERSION = '0.03';
15
16 @ISA = qw( Exporter );
17
18 $DEBUG = 0;
19
20 $dir = "/usr/local/freeside";
21 $socket =  "$dir/selfservice_socket";
22 $socket .= '.'.$tag if defined $tag && length($tag);
23
24 #maybe should ask ClientAPI for this list
25 %autoload = (
26   'passwd'                    => 'passwd/passwd',
27   'chfn'                      => 'passwd/passwd',
28   'chsh'                      => 'passwd/passwd',
29   'login_info'                => 'MyAccount/login_info',
30   'login_banner_image'        => 'MyAccount/login_banner_image',
31   'login'                     => 'MyAccount/login',
32   'logout'                    => 'MyAccount/logout',
33   'switch_acct'               => 'MyAccount/switch_acct',
34   'switch_cust'               => 'MyAccount/switch_cust',
35   'customer_info'             => 'MyAccount/customer_info',
36   'customer_info_short'       => 'MyAccount/customer_info_short',
37   'customer_recurring'        => 'MyAccount/customer_recurring',
38
39   'contact_passwd'            => 'MyAccount/contact/contact_passwd',
40   'list_contacts'             => 'MyAccount/contact/list_contacts',
41   'edit_contact'              => 'MyAccount/contact/edit_contact',
42   'delete_contact'            => 'MyAccount/contact/delete_contact',
43   'new_contact'               => 'MyAccount/contact/new_contact',
44
45   'billing_history'           => 'MyAccount/billing_history',
46   'edit_info'                 => 'MyAccount/edit_info',     #add to ss cgi!
47   'invoice'                   => 'MyAccount/invoice',
48   'invoice_pdf'               => 'MyAccount/invoice_pdf',
49   'legacy_invoice'            => 'MyAccount/legacy_invoice',
50   'legacy_invoice_pdf'        => 'MyAccount/legacy_invoice_pdf',
51   'invoice_logo'              => 'MyAccount/invoice_logo',
52   'list_invoices'             => 'MyAccount/list_invoices', #?
53   'list_payments'             => 'MyAccount/list_payments',
54   'payment_receipt'           => 'MyAccount/payment_receipt',
55   'list_payby'                => 'MyAccount/list_payby',
56   'insert_payby'              => 'MyAccount/insert_payby',
57   'update_payby'              => 'MyAccount/update_payby',
58   'delete_payby'              => 'MyAccount/delete_payby', 
59   'cancel'                    => 'MyAccount/cancel',        #add to ss cgi!
60   'payment_info'              => 'MyAccount/payment_info',
61   'payment_info_renew_info'   => 'MyAccount/payment_info_renew_info',
62   'process_payment'           => 'MyAccount/process_payment',
63   'store_payment'             => 'MyAccount/store_payment',
64   'process_stored_payment'    => 'MyAccount/process_stored_payment',
65   'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
66   'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
67   'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
68   'process_prepay'            => 'MyAccount/process_prepay',
69   'realtime_collect'          => 'MyAccount/realtime_collect',
70   'list_pkgs'                 => 'MyAccount/list_pkgs',     #add to ss (added?)
71   'pkg_info'                  => 'MyAccount/pkg_info',
72   'list_svcs'                 => 'MyAccount/list_svcs',     #add to ss (added?)
73   'list_svc_usage'            => 'MyAccount/list_svc_usage',   
74   'svc_status_html'           => 'MyAccount/svc_status_html',
75   'svc_status_hash'           => 'MyAccount/svc_status_hash',
76   'set_svc_status_hash'       => 'MyAccount/set_svc_status_hash',
77   'set_svc_status_listadd'    => 'MyAccount/set_svc_status_listadd',
78   'set_svc_status_listdel'    => 'MyAccount/set_svc_status_listdel',
79   'set_svc_status_vacationadd'=> 'MyAccount/set_svc_status_vacationadd',
80   'set_svc_status_vacationdel'=> 'MyAccount/set_svc_status_vacationdel',
81   'acct_forward_info'         => 'MyAccount/acct_forward_info',
82   'process_acct_forward'      => 'MyAccount/process_acct_forward',
83   'list_dsl_devices'          => 'MyAccount/list_dsl_devices',   
84   'add_dsl_device'            => 'MyAccount/add_dsl_device',   
85   'delete_dsl_device'         => 'MyAccount/delete_dsl_device',   
86   'port_graph'                => 'MyAccount/port_graph',   
87   'list_cdr_usage'            => 'MyAccount/list_cdr_usage',   
88   'list_support_usage'        => 'MyAccount/list_support_usage',   
89   'order_pkg'                 => 'MyAccount/order_pkg',     #add to ss cgi!
90   'change_pkg'                => 'MyAccount/change_pkg', 
91   'order_recharge'            => 'MyAccount/order_recharge',
92   'renew_info'                => 'MyAccount/renew_info',
93   'order_renew'               => 'MyAccount/order_renew',
94   'cancel_pkg'                => 'MyAccount/cancel_pkg',    #add to ss cgi!
95   'suspend_pkg'               => 'MyAccount/suspend_pkg',   #add to ss cgi!
96   'charge'                    => 'MyAccount/charge',        #?
97   'part_svc_info'             => 'MyAccount/part_svc_info',
98   'provision_acct'            => 'MyAccount/provision_acct',
99   'provision_phone'           => 'MyAccount/provision_phone',
100   'provision_pbx'             => 'MyAccount/provision_pbx',
101   'provision_external'        => 'MyAccount/provision_external',
102   'provision_forward'         => 'MyAccount/provision_forward',
103   'unprovision_svc'           => 'MyAccount/unprovision_svc',
104   'myaccount_passwd'          => 'MyAccount/myaccount_passwd',
105   'reset_passwd'              => 'MyAccount/reset_passwd',
106   'check_reset_passwd'        => 'MyAccount/check_reset_passwd',
107   'process_reset_passwd'      => 'MyAccount/process_reset_passwd',
108   'validate_passwd'           => 'MyAccount/validate_passwd',
109   'list_tickets'              => 'MyAccount/list_tickets',
110   'create_ticket'             => 'MyAccount/create_ticket',
111   'get_ticket'                => 'MyAccount/get_ticket',
112   'adjust_ticket_priority'    => 'MyAccount/adjust_ticket_priority',
113   'did_report'                => 'MyAccount/did_report',
114   'signup_info'               => 'Signup/signup_info',
115   'skin_info'                 => 'MyAccount/skin_info',
116   'access_info'               => 'MyAccount/access_info',
117   'domain_select_hash'        => 'Signup/domain_select_hash',  # expose?
118   'new_customer'              => 'Signup/new_customer',
119   'new_customer_minimal'      => 'Signup/new_customer_minimal',
120   'capture_payment'           => 'Signup/capture_payment',
121   'new_prospect'              => 'Signup/new_prospect',
122   #N/A 'clear_signup_cache'        => 'Signup/clear_cache',
123   'new_agent'                 => 'Agent/new_agent',
124   'agent_login'               => 'Agent/agent_login',
125   'agent_logout'              => 'Agent/agent_logout',
126   'agent_info'                => 'Agent/agent_info',
127   'agent_list_customers'      => 'Agent/agent_list_customers',
128   'check_username'            => 'Agent/check_username',
129   'suspend_username'          => 'Agent/suspend_username',
130   'unsuspend_username'        => 'Agent/unsuspend_username',
131   'mason_comp'                => 'MasonComponent/mason_comp',
132   'call_time'                 => 'PrepaidPhone/call_time',
133   'call_time_nanpa'           => 'PrepaidPhone/call_time_nanpa',
134   'phonenum_balance'          => 'PrepaidPhone/phonenum_balance',
135
136   'start_thirdparty'          => 'MyAccount/start_thirdparty',
137   'finish_thirdparty'         => 'MyAccount/finish_thirdparty',
138
139   'list_quotations'           => 'MyAccount/quotation/list_quotations',
140   'quotation_new'             => 'MyAccount/quotation/quotation_new',
141   'quotation_delete'          => 'MyAccount/quotation/quotation_delete',
142   'quotation_info'            => 'MyAccount/quotation/quotation_info',
143   'quotation_print'           => 'MyAccount/quotation/quotation_print',
144   'quotation_add_pkg'         => 'MyAccount/quotation/quotation_add_pkg',
145   'quotation_remove_pkg'      => 'MyAccount/quotation/quotation_remove_pkg',
146   'quotation_order'           => 'MyAccount/quotation/quotation_order',
147
148   'freesideinc_service'       => 'Freeside/freesideinc_service',
149
150 );
151 @EXPORT_OK = (
152   keys(%autoload),
153   qw( regionselector regionselector_hashref location_form
154       expselect popselector domainselector didselector
155     )
156 );
157
158 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
159 $ENV{'SHELL'} = '/bin/sh';
160 $ENV{'IFS'} = " \t\n";
161 $ENV{'CDPATH'} = '';
162 $ENV{'ENV'} = '';
163 $ENV{'BASH_ENV'} = '';
164
165 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; } 
166 #if you grant appropriate permissions to whatever user
167 my $freeside_uid = scalar(getpwnam('freeside'));
168 die "not running as the freeside user\n"
169   if $> != $freeside_uid && ! $skip_uid_check;
170
171 -e $dir or die "FATAL: $dir doesn't exist!";
172 -d $dir or die "FATAL: $dir isn't a directory!";
173 -r $dir or die "FATAL: Can't read $dir as freeside user!";
174 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
175
176 foreach my $autoload ( keys %autoload ) {
177
178   my $eval =
179   "sub $autoload { ". '
180                    my $param;
181                    if ( ref($_[0]) ) {
182                      $param = shift;
183                    } else {
184                      #warn scalar(@_). ": ". join(" / ", @_);
185                      $param = { @_ };
186                    }
187
188                    $param->{_packet} = \''. $autoload{$autoload}. '\';
189
190                    simple_packet($param);
191                  }';
192
193   eval $eval;
194   die $@ if $@;
195
196 }
197
198 sub simple_packet {
199   my $packet = shift;
200   warn "sending ". $packet->{_packet}. " to server"
201     if $DEBUG;
202
203   # Retry socket operation 5 times per second
204   # until successful or $max_retry
205   my $max_retry = 25;
206   my $sock_timeout = 5;
207   my $enable_sock_timeout = 0;
208
209   for my $try ( 1..$max_retry ) {
210     local $@;
211
212     my $return;
213
214     eval {
215       local $SIG{ALRM} = sub{die "socket $socket: timeout ${sock_timeout}s"};
216       alarm $sock_timeout
217         if $enable_sock_timeout;
218
219       socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
220       connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
221       nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
222       SOCK->flush;
223
224       # 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
225       # block until there is a message on socket
226       #  my $w = new IO::Select;
227       #  $w->add(\*SOCK);
228       #  my @wait = $w->can_read;
229
230       warn "reading message from server"
231         if $DEBUG;
232
233       $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
234       die $return->{'_error'} if defined $return->{_error} && $return->{_error};
235
236       warn "returning message to client"
237         if $DEBUG;
238
239       return $return;
240     };
241
242     return $return
243       unless $@;
244
245     die "(Attempt $try) $@"
246       if $try == $max_retry;
247
248     warn "(Attempt $try) $@"
249       if $DEBUG;
250
251     Time::HiRes::sleep(0.2);
252   }
253 }
254
255 =head1 NAME
256
257 FS::SelfService - Freeside self-service API
258
259 =head1 SYNOPSIS
260
261   # password and shell account changes
262   use FS::SelfService qw(passwd chfn chsh);
263
264   # "my account" functionality
265   use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
266
267   #new-style login with an email address and password
268   # can also be used for svc_acct login, set $emailaddress to username@domain
269   my $rv = login ( { 'email'    => $emailaddress,
270                      'password' => $password,
271                    },
272                  );
273   if ( $rv->{'error'} ) {
274     #handle login error...
275   } else {
276     #successful login
277     $session_id = $rv->{'session_id'};
278   }
279
280   #classic svc_acct-based login with separate username and password
281   my $rv = login( { 'username' => $username,
282                     'domain'   => $domain,
283                     'password' => $password,
284                   }
285                 );
286   if ( $rv->{'error'} ) {
287     #handle login error...
288   } else {
289     #successful login
290     $session_id = $rv->{'session_id'};
291   }
292
293   #svc_phone login with phone number and PIN
294   my $rv = login( { 'username' => $phone_number,
295                     'domain'   => 'svc_phone',
296                     'password' => $pin,
297                   }
298                 );
299   if ( $rv->{'error'} ) {
300     #handle login error...
301   } else {
302     #successful login
303     $session_id = $rv->{'session_id'};
304   }
305
306   my $customer_info = customer_info( { 'session_id' => $session_id } );
307
308   my $payment_info = payment_info( { 'session_id' => $session_id } );
309
310   #!!! process_payment example
311
312   #!!! list_pkgs example
313
314   #ordering a package with an svc_acct service
315   my $rv = order_pkg( { 'session_id' => $session_id,
316                         'pkgpart'    => $pkgpart,
317                         'svcpart'    => $svcpart,
318                         'username'   => $username,
319                         'domsvc'     => $domsvc, #svcnum of svc_domain
320                         '_password'  => $password,
321                       }
322                     );
323
324   #!!! ordering a package with an svc_domain service example
325
326   #!!! ordering a package with an svc_phone service example
327
328   #!!! ordering a package with an svc_external service example
329
330   #!!! ordering a package with an svc_pbx service
331
332   #ordering a package with no service
333   my $rv = order_pkg( { 'session_id' => $session_id,
334                         'pkgpart'    => $pkgpart,
335                         'svcpart'    => 'none',
336                       }
337                     );
338
339   #quoting a package, then ordering after confirmation
340
341   my $rv = quotation_new({ 'session_id' => $session_id });
342   my $qnum = $rv->{quotationnum};
343   #  add packages to the quotation
344   $rv = quotation_add_pkg({ 'session_id'   => $session_id,
345                             'quotationnum' => $qnum,
346                             'pkgpart'      => $pkgpart,
347                             'quantity'     => $quantity, # defaults to 1
348                           });
349   #  repeat until all packages are added
350   #  view the pricing information
351   $rv = quotation_info({ 'session_id'   => $session_id,
352                          'quotationnum' => $qnum,
353                       });
354   print "Total setup charges: ".$rv->{total_setup}."\n".
355         "Total recurring charges: ".$rv->{total_recur}."\n";
356   #  quotation_info also provides a detailed breakdown of charges, in
357   #  $rv->{sections}.
358
359   #  ask customer for confirmation, then:
360   $rv = quotation_order({ 'session_id'   => $session_id,
361                           'quotationnum' => $qnum,
362                         });
363
364   #!!! cancel_pkg example
365
366   # signup functionality
367   use FS::SelfService qw( signup_info new_customer new_customer_minimal );
368
369   my $signup_info = signup_info;
370
371   $rv = new_customer( {
372                         'first'            => $first,
373                         'last'             => $last,
374                         'company'          => $company,
375                         'address1'         => $address1,
376                         'address2'         => $address2,
377                         'city'             => $city,
378                         'state'            => $state,
379                         'zip'              => $zip,
380                         'country'          => $country,
381                         'daytime'          => $daytime,
382                         'night'            => $night,
383                         'fax'              => $fax,
384                         'payby'            => $payby,
385                         'payinfo'          => $payinfo,
386                         'paycvv'           => $paycvv,
387                         'paystart_month'   => $paystart_month
388                         'paystart_year'    => $paystart_year,
389                         'payissue'         => $payissue,
390                         'payip'            => $payip
391                         'paydate'          => $paydate,
392                         'payname'          => $payname,
393                         'invoicing_list'   => $invoicing_list,
394                         'referral_custnum' => $referral_custnum,
395                         'agentnum'         => $agentnum,
396                         'pkgpart'          => $pkgpart,
397
398                         'username'         => $username,
399                         '_password'        => $password,
400                         'popnum'           => $popnum,
401                         #OR
402                         'countrycode'      => 1,
403                         'phonenum'         => $phonenum,
404                         'pin'              => $pin,
405                       }
406                     );
407   
408   my $error = $rv->{'error'};
409   if ( $error eq '_decline' ) {
410     print_decline();
411   } elsif ( $error ) {
412     reprint_signup();
413   } else {
414     print_success();
415   }
416
417 =head1 DESCRIPTION
418
419 Use this API to implement your own client "self-service" module.
420
421 If you just want to customize the look of the existing "self-service" module,
422 see XXXX instead.
423
424 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
425
426 =over 4
427
428 =item passwd
429
430 Changes the password for an existing user in svc_acct.  Takes a hash
431 reference with the following keys:
432
433 =over 4
434
435 =item username
436
437 Username of the account (required)
438
439 =item domain
440
441 Domain of the account (required)
442
443 =item old_password
444
445 Old password (required)
446
447 =item new_password
448  
449 New password (required)
450
451 =item new_gecos
452
453 New gecos
454
455 =item new_shell
456
457 New Shell
458
459 =back 
460
461 =item chfn
462
463 =item chsh
464
465 =back
466
467 =head1 "MY ACCOUNT" FUNCTIONS
468
469 =over 4
470
471 =item login HASHREF
472
473 Creates a user session.  Takes a hash reference as parameter with the
474 following keys:
475
476 =over 4
477
478 =item email
479
480 Email address (username@domain), instead of username and domain.  Required for
481 contact-based self-service login, can also be used for svc_acct-based login.
482
483 =item username
484
485 Username
486
487 =item domain
488
489 Domain
490
491 =item password
492
493 Password
494
495 =back
496
497 Returns a hash reference with the following keys:
498
499 =over 4
500
501 =item error
502
503 Empty on success, or an error message on errors.
504
505 =item session_id
506
507 Session identifier for successful logins
508
509 =back
510
511 =item customer_info HASHREF
512
513 Returns general customer information.
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 name
522
523 Customer name
524
525 =item balance
526
527 Balance owed
528
529 =item open
530
531 Array reference of hash references of open inoices.  Each hash reference has
532 the following keys: invnum, date, owed
533
534 =item small_custview
535
536 An HTML fragment containing shipping and billing addresses.
537
538 =item The following fields are also returned
539
540 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
541
542 =back
543
544 =item customer_recurring HASHREF
545
546 Takes a hash reference as parameter with a single key B<session_id>
547 or keys B<agent_session_id> and B<custnum>.
548
549 Returns a hash reference with the keys error, custnum and display_recurring.
550
551 display_recurring is an arrayref of hashrefs with the following keys:
552
553 =over 4
554
555 =item freq
556
557 frequency of charge, in months unless units are specified
558
559 =item freq_pretty
560
561 frequency of charge, suitable for display
562
563 =item amount
564
565 amount charged at this frequency
566
567 =back
568
569 =item edit_info HASHREF
570
571 Takes a hash reference as parameter with any of the following keys:
572
573 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
574
575 If a field exists, the customer record is updated with the new value of that
576 field.  If a field does not exist, that field is not changed on the customer
577 record.
578
579 Returns a hash reference with a single key, B<error>, empty on success, or an
580 error message on errors
581
582 =item invoice HASHREF
583
584 Returns an invoice.  Takes a hash reference as parameter with two keys:
585 session_id and invnum
586
587 Returns a hash reference with the following keys:
588
589 =over 4
590
591 =item error
592
593 Empty on success, or an error message on errors
594
595 =item invnum
596
597 Invoice number
598
599 =item invoice_text
600
601 Invoice text
602
603 =back
604
605 =item list_invoices HASHREF
606
607 Returns a list of all customer invoices.  Takes a hash reference with a single
608 key, session_id.
609
610 Returns a hash reference with the following keys:
611
612 =over 4
613
614 =item error
615
616 Empty on success, or an error message on errors
617
618 =item invoices
619
620 Reference to array of hash references with the following keys:
621
622 =over 4
623
624 =item invnum
625
626 Invoice ID
627
628 =item _date
629
630 Invoice date, in UNIX epoch time
631
632 =back
633
634 =back
635
636 =item list_payments HASHREF
637
638 Returns a list of all customer payments.  Takes a hash reference with a single
639 key, session_id.
640
641 Returns a hash reference with the following keys:
642
643 =over 4
644
645 =item error
646
647 Empty on success, or an error message on errors
648
649 =item payments
650
651 Reference to array of hash references with the following keys:
652
653 =over 4
654
655 =item paynum
656
657 Payment #
658
659 =item _date
660
661 Payument date, in UNIX epoch time
662
663 =item date
664
665 Payment date, in a human-readable format
666
667 =item date_short
668
669 Payment date, in a shorter human-readable format
670
671 =item paid
672
673 Amount paid
674
675 =item payby
676
677 Payment method: CARD, CHEK (electronic check), or BILL (physical check).
678
679 =item paycardtype
680
681 Payment card type
682
683 =item paymask
684
685 Payment card mask
686
687 =item processor
688
689 Processor for cards and electronic checks
690
691 =item auth
692
693 Authorization number
694
695 =item order_number
696
697 Order number
698
699 =back
700
701 =back
702
703 =item list_payby HASHREF
704
705 Returns a list of all stored customer payment information (credit cards and
706 electronic check accounts).  Takes a hash reference with a single key,
707 session_id.
708
709 Returns a hash reference with the following keys:
710
711 =over 4
712
713 =item error
714
715 Empty on success, or an error message on errors
716
717 =item payby
718
719 Reference to array of hash references with the following keys:
720
721 =over 4
722
723 =item custpaybynum
724
725 =item weight
726
727 Numeric weighting.  Stored payment information with a lower weight is attempted
728 first.
729
730 =item payby
731
732 CARD (Automatic credit card), CHEK (Automatic electronic check), DCRD
733 (on-demand credit card) or DCHK (on-demand electronic check).
734
735 =item paymask
736
737 Masked credit card number (or, masked account and routing numbers)
738
739 =item paydate
740
741 Credit card expiration date
742
743 =item payname
744
745 Exact name on card (or bank name, for electronic checks)
746
747 =item paystate
748
749 For electronic checks, bank state
750
751 =item paytype
752
753 For electronic checks, account type (Personal/Business, Checking/Savings)
754
755 =back
756
757 =back
758
759 =item insert_payby HASHREF
760
761 Adds new stored payment information for this customer.  Takes a hash reference
762 with the following keys:
763
764 =over 4
765
766 =item session_id
767
768 =item weight
769
770 Numeric weighting.  Stored payment information with a lower weight is attempted
771 first.
772
773 =item payby
774
775 CARD (Automatic credit card), CHEK (Automatic electronic check), DCRD
776 (on-demand credit card) or DCHK (on-demand electronic check).
777
778 =item payinfo
779
780 Credit card number (or electronic check "account@routing")
781
782 =item paycvv
783
784 CVV2 number / security code
785
786 =item paydate
787
788 Credit card expiration date
789
790 =item payname
791
792 Exact name on card (or bank name, for electronic checks)
793
794 =item paystate
795
796 For electronic checks, bank state
797
798 =item paytype
799
800 For electronic checks, account type (i.e. "Personal Savings", "Personal Checking", "Business Checking")A
801
802 =item payip
803
804 Optional IP address from which payment was submitted
805
806 =back
807
808 If there is an error, returns a hash reference with a single key, B<error>,
809 otherwise returns a hash reference with a single key, B<custpaybynum>.
810
811 =item update_payby HASHREF
812
813 Updates stored payment information.  Takes a hash reference with the same
814 keys as insert_payby, as well as B<custpaybynum> to specify which record
815 to update.  All keys except B<session_id> and B<custpaybynum> are optional;
816 if omitted, the previous values in the record will be preserved.
817
818 If there is an error, returns a hash reference with a single key, B<error>,
819 otherwise returns a hash reference with a single key, B<custpaybynum>.
820
821 =item delete_payby HASHREF
822
823 Removes stored payment information.  Takes a hash reference with two keys,
824 B<session_id> and B<custpaybynum>.  Returns a hash reference with a single key,
825 B<error>, which is an error message or empty for successful removal.
826
827 =item cancel HASHREF
828
829 Cancels this customer.
830
831 Takes a hash reference as parameter with a single key: B<session_id>
832
833 Returns a hash reference with a single key, B<error>, which is empty on
834 success or an error message on errors.
835
836 =item payment_info HASHREF
837
838 Returns information that may be useful in displaying a payment page.
839
840 Takes a hash reference as parameter with the following keys:
841
842 =over 4
843
844 =item session_id
845
846 Required session ID
847
848 =item payment_payby
849
850 =item omit_cust_main_county
851
852 Optional, pass a true value to omit cust_main_county data for performance.
853
854 =back
855
856 Returns a hash reference with the following keys:
857
858 =over 4
859
860 =item error
861
862 Empty on success, or an error message on errors
863
864 =item balance
865
866 Balance owed
867
868 =item payname
869
870 Exact name on credit card (CARD/DCRD)
871
872 =item address1
873
874 Address line one
875
876 =item address2
877
878 Address line two
879
880 =item city
881
882 City
883
884 =item state
885
886 State
887
888 =item zip
889
890 Zip or postal code
891
892 =item payby
893
894 Customer's current default payment type.
895
896 =item card_type
897
898 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
899
900 =item payinfo
901
902 For CARD/DCRD payment types, the card number
903
904 =item month
905
906 For CARD/DCRD payment types, expiration month
907
908 =item year
909
910 For CARD/DCRD payment types, expiration year
911
912 =item cust_main_county
913
914 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.
915
916 =item states
917
918 Array reference of all states in the current default country.
919
920 =item card_types
921
922 Hash reference of card types; keys are card types, values are the exact strings
923 passed to the process_payment function
924
925 =cut
926
927 #this doesn't actually work yet
928 #
929 #=item paybatch
930 #
931 #Unique transaction identifier (prevents multiple charges), passed to the
932 #process_payment function
933
934 =back
935
936 =item process_payment HASHREF
937
938 Processes a payment and possible change of address or payment type.  Takes a
939 hash reference as parameter with the following keys:
940
941 =over 4
942
943 =item session_id
944
945 Session identifier
946
947 =item amount
948
949 Amount
950
951 =item save
952
953 If true, address and card information entered will be saved for subsequent
954 transactions.
955
956 =item auto
957
958 If true, future credit card payments will be done automatically (sets payby to
959 CARD).  If false, future credit card payments will be done on-demand (sets
960 payby to DCRD).  This option only has meaning if B<save> is set true.  
961
962 =item payname
963
964 Name on card
965
966 =item address1
967
968 Address line one
969
970 =item address2
971
972 Address line two
973
974 =item city
975
976 City
977
978 =item state
979
980 State
981
982 =item zip
983
984 Zip or postal code
985
986 =item country
987
988 Two-letter country code
989
990 =item payinfo
991
992 Card number
993
994 =item month
995
996 Card expiration month
997
998 =item year
999
1000 Card expiration year
1001
1002 =cut
1003
1004 #this doesn't actually work yet
1005 #
1006 #=item paybatch
1007 #
1008 #Unique transaction identifier, returned from the payment_info function.
1009 #Prevents multiple charges.
1010
1011 =back
1012
1013 Returns a hash reference with a single key, B<error>, empty on success, or an
1014 error message on errors.
1015
1016 =item process_payment_order_pkg
1017
1018 Combines the B<process_payment> and B<order_pkg> functions in one step.  If the
1019 payment processes sucessfully, the package is ordered.  Takes a hash reference
1020 as parameter with the keys of both methods.
1021
1022 Returns a hash reference with a single key, B<error>, empty on success, or an
1023 error message on errors.
1024
1025 =item process_payment_change_pkg
1026
1027 Combines the B<process_payment> and B<change_pkg> functions in one step.  If the
1028 payment processes sucessfully, the package is ordered.  Takes a hash reference
1029 as parameter with the keys of both methods.
1030
1031 Returns a hash reference with a single key, B<error>, empty on success, or an
1032 error message on errors.
1033
1034
1035 =item process_payment_order_renew
1036
1037 Combines the B<process_payment> and B<order_renew> functions in one step.  If
1038 the payment processes sucessfully, the renewal is processed.  Takes a hash
1039 reference as parameter with the keys of both methods.
1040
1041 Returns a hash reference with a single key, B<error>, empty on success, or an
1042 error message on errors.
1043
1044 =item list_pkgs
1045
1046 Returns package information for this customer.  For more detail on services,
1047 see L</list_svcs>.
1048
1049 Takes a hash reference as parameter with a single key: B<session_id>
1050
1051 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
1052
1053 =over 4
1054
1055 =item custnum
1056
1057 Customer number
1058
1059 =item error
1060
1061 Empty on success, or an error message on errors.
1062
1063 =item cust_pkg HASHREF
1064
1065 Array reference of hash references, each of which has the fields of a cust_pkg
1066 record (see L<FS::cust_pkg>) as well as the fields below.  Note these are not
1067 the internal FS:: objects, but hash references of columns and values.
1068
1069 =over 4
1070
1071 =item part_pkg fields
1072
1073 All fields of part_pkg for this specific cust_pkg (be careful with this
1074 information - it may reveal more about your available packages than you would
1075 like users to know in aggregate) 
1076
1077 =cut
1078
1079 #XXX pare part_pkg fields down to a more secure subset
1080
1081 =item part_svc
1082
1083 An array of hash references indicating information on unprovisioned services
1084 available for provisioning for this specific cust_pkg.  Each has the following
1085 keys:
1086
1087 =over 4
1088
1089 =item part_svc fields
1090
1091 All fields of part_svc (be careful with this information - it may reveal more
1092 about your available packages than you would like users to know in aggregate) 
1093
1094 =cut
1095
1096 #XXX pare part_svc fields down to a more secure subset
1097
1098 =back
1099
1100 =item cust_svc
1101
1102 An array of hash references indicating information on the customer services
1103 already provisioned for this specific cust_pkg.  Each has the following keys:
1104
1105 =over 4
1106
1107 =item label
1108
1109 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.
1110
1111 =back
1112
1113 =item svcnum
1114
1115 Primary key for this service
1116
1117 =item svcpart
1118
1119 Service definition (see L<FS::part_svc>)
1120
1121 =item pkgnum
1122
1123 Customer package (see L<FS::cust_pkg>)
1124
1125 =item overlimit
1126
1127 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
1128
1129 =back
1130
1131 =back
1132
1133 =item pkg_info
1134
1135 Returns package information for package.
1136
1137 Takes a hash reference as parameter with the following keys:
1138
1139 =over 4
1140
1141 =item session_id
1142
1143 Session identifier
1144
1145 =item pkgnum
1146
1147 Package Number
1148
1149 =back
1150
1151 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
1152
1153 =over 4
1154
1155 =item pkg_label
1156
1157 Name of this package
1158
1159 =item pkgpart
1160
1161 Part package primary key
1162
1163 =item classnum
1164
1165 Package class number
1166
1167 =item error
1168
1169 error message if errror.
1170
1171 =back
1172
1173 =item list_svcs
1174
1175 Returns service information for this customer.
1176
1177 Takes a hash reference as parameter with a single key: B<session_id>
1178
1179 Returns a hash reference containing customer package information.  The hash reference contains the following keys:
1180
1181 =over 4
1182
1183 =item custnum
1184
1185 Customer number
1186
1187 =item svcs
1188
1189 An array of hash references indicating information on all of this customer's
1190 services.  Each has the following keys:
1191
1192 =over 4
1193
1194 =item svcnum
1195
1196 Primary key for this service
1197
1198 =item label
1199
1200 Name of this service
1201
1202 =item value
1203
1204 Meaningful user-specific identifier for the service (i.e. username, domain, or
1205 mail alias).
1206
1207 =back
1208
1209 Account (svc_acct) services also have the following keys:
1210
1211 =over 4
1212
1213 =item username
1214
1215 Username
1216
1217 =item email
1218
1219 username@domain
1220
1221 =item seconds
1222
1223 Seconds remaining
1224
1225 =item upbytes
1226
1227 Upload bytes remaining
1228
1229 =item downbytes
1230
1231 Download bytes remaining
1232
1233 =item totalbytes
1234
1235 Total bytes remaining
1236
1237 =item recharge_amount
1238
1239 Cost of a recharge
1240
1241 =item recharge_seconds
1242
1243 Number of seconds gained by recharge
1244
1245 =item recharge_upbytes
1246
1247 Number of upload bytes gained by recharge
1248
1249 =item recharge_downbytes
1250
1251 Number of download bytes gained by recharge
1252
1253 =item recharge_totalbytes
1254
1255 Number of total bytes gained by recharge
1256
1257 =back
1258
1259 =back
1260
1261 =item order_pkg
1262
1263 Orders a package for this customer.
1264
1265 If signup_server-realtime is set, bills the new package, attemps to collect
1266 payment and (for auto-payment customers) cancels the package if the payment is
1267 declined.
1268
1269 Takes a hash reference as parameter with the following keys:
1270
1271 =over 4
1272
1273 =item session_id
1274
1275 Session identifier
1276
1277 =item pkgpart
1278
1279 Package to order (see L<FS::part_pkg>).
1280
1281 =item quantity
1282
1283 Quantity for this package order (default 1).
1284
1285 =item run_bill_events
1286
1287 If true, runs billing events for the customer after ordering and billing the
1288 package (signup_server-realtime must be set).
1289
1290 =item locationnum
1291
1292 Optional locationnum for this package order, for existing locations.
1293
1294 Or, for new locations, pass the following fields: address1*, address2, city*,
1295 county, state*, zip*, country.  (* = required in this case)
1296
1297 (None of this is required at all if you are just ordering a package
1298 at the customer's existing default service location.)
1299
1300 =item address1
1301
1302 =item address2
1303
1304 =item city
1305
1306 =item county
1307
1308 =item state
1309
1310 =item zip
1311
1312 =item country
1313
1314 =item svcpart
1315
1316 Service to order (see L<FS::part_svc>).
1317
1318 Normally optional; required only to provision a non-svc_acct service, or if the
1319 package definition does not contain one svc_acct service definition with
1320 quantity 1 (it may contain others with quantity >1).  A svcpart of "none" can
1321 also be specified to indicate that no initial service should be provisioned.
1322
1323 =back
1324
1325 Fields used when provisioning an svc_acct service:
1326
1327 =over 4
1328
1329 =item username
1330
1331 Username
1332
1333 =item _password
1334
1335 Password
1336
1337 =item sec_phrase
1338
1339 Optional security phrase
1340
1341 =item popnum
1342
1343 Optional Access number number
1344
1345 =back
1346
1347 Fields used when provisioning an svc_domain service:
1348
1349 =over 4
1350
1351 =item domain
1352
1353 Domain
1354
1355 =back
1356
1357 Fields used when provisioning an svc_phone service:
1358
1359 =over 4
1360
1361 =item phonenum
1362
1363 Phone number
1364
1365 =item pin
1366
1367 Voicemail PIN
1368
1369 =item sip_password
1370
1371 SIP password
1372
1373 =back
1374
1375 Fields used when provisioning an svc_external service:
1376
1377 =over 4
1378
1379 =item id
1380
1381 External numeric ID.
1382
1383 =item title
1384
1385 External text title.
1386
1387 =back
1388
1389 Fields used when provisioning an svc_pbx service:
1390
1391 =over 4
1392
1393 =item id
1394
1395 Numeric ID.
1396
1397 =item name
1398
1399 Text name.
1400
1401 =back
1402
1403 Returns a hash reference with a single key, B<error>, empty on success, or an
1404 error message on errors.  The special error '_decline' is returned for
1405 declined transactions.
1406
1407 =item change_pkg
1408
1409 Changes a package for this customer.
1410
1411 Takes a hash reference as parameter with the following keys:
1412
1413 =over 4
1414
1415 =item session_id
1416
1417 Session identifier
1418
1419 =item pkgnum
1420
1421 Existing customer package.
1422
1423 =item pkgpart
1424
1425 New package to order (see L<FS::part_pkg>).
1426
1427 =item quantity
1428
1429 Quantity for this package order (default 1).
1430
1431 =back
1432
1433 Returns a hash reference with the following keys:
1434
1435 =over 4
1436
1437 =item error
1438
1439 Empty on success, or an error message on errors.  
1440
1441 =item pkgnum
1442
1443 On success, the new pkgnum
1444
1445 =back
1446
1447
1448 =item renew_info
1449
1450 Provides useful info for early renewals.
1451
1452 Takes a hash reference as parameter with the following keys:
1453
1454 =over 4
1455
1456 =item session_id
1457
1458 Session identifier
1459
1460 =back
1461
1462 Returns a hash reference.  On errors, it contains a single key, B<error>, with
1463 the error message.  Otherwise, contains a single key, B<dates>, pointing to
1464 an array refernce of hash references.  Each hash reference contains the
1465 following keys:
1466
1467 =over 4
1468
1469 =item bill_date
1470
1471 (Future) Bill date.  Indicates a future date for which billing could be run.
1472 Specified as an integer UNIX timestamp.  Pass this value to the B<order_renew>
1473 function.
1474
1475 =item bill_date_pretty
1476
1477 (Future) Bill date as a human-readable string.  (Convenience for display;
1478 subject to change, so best not to parse for the date.)
1479
1480 =item amount
1481
1482 Base amount which will be charged if renewed early as of this date.
1483
1484 =item renew_date
1485
1486 Renewal date; i.e. even-futher future date at which the customer will be paid
1487 through if the early renewal is completed with the given B<bill-date>.
1488 Specified as an integer UNIX timestamp.
1489
1490 =item renew_date_pretty
1491
1492 Renewal date as a human-readable string.  (Convenience for display;
1493 subject to change, so best not to parse for the date.)
1494
1495 =item pkgnum
1496
1497 Package that will be renewed.
1498
1499 =item expire_date
1500
1501 Expiration date of the package that will be renewed.
1502
1503 =item expire_date_pretty
1504
1505 Expiration date of the package that will be renewed, as a human-readable
1506 string.  (Convenience for display; subject to change, so best not to parse for
1507 the date.)
1508
1509 =back
1510
1511 =item order_renew
1512
1513 Renews this customer early; i.e. runs billing for this customer in advance.
1514
1515 Takes a hash reference as parameter with the following keys:
1516
1517 =over 4
1518
1519 =item session_id
1520
1521 Session identifier
1522
1523 =item date
1524
1525 Integer date as returned by the B<renew_info> function, indicating the advance
1526 date for which to run billing.
1527
1528 =back
1529
1530 Returns a hash reference with a single key, B<error>, empty on success, or an
1531 error message on errors.
1532
1533 =item cancel_pkg
1534
1535 Cancels a package for this customer.
1536
1537 Takes a hash reference as parameter with the following keys:
1538
1539 =over 4
1540
1541 =item session_id
1542
1543 Session identifier
1544
1545 =item pkgpart
1546
1547 pkgpart of package to cancel
1548
1549 =item date
1550
1551 Optional date, for future cancellation (expiration) instead of immediate
1552 cancellation.  Specified as an integer UNIX timestamp ("epoch time").
1553
1554 =back
1555
1556 Returns a hash reference with a single key, B<error>, empty on success, or an
1557 error message on errors.
1558
1559 =item provision_acct 
1560
1561 Provisions an account (svc_acct).
1562
1563 Takes a hash reference as parameter with the following keys:
1564
1565 =over 4
1566
1567 =item session_id
1568
1569 Session identifier
1570
1571 =item pkgnum
1572
1573 pkgnum of package into which this service is provisioned
1574
1575 =item svcpart
1576
1577 svcpart or service definition to provision
1578
1579 =item username
1580
1581 =item domsvc
1582
1583 =item _password
1584
1585 =back
1586
1587 =item provision_phone
1588
1589 Provisions a phone number (svc_phone).
1590
1591 Takes a hash reference as parameter with the following keys:
1592
1593 =over 4
1594
1595 =item session_id
1596
1597 Session identifier
1598
1599 =item pkgnum
1600
1601 pkgnum of package into which this service is provisioned
1602
1603 =item svcpart
1604
1605 svcpart or service definition to provision
1606
1607 =item countrycode
1608
1609 =item phonenum
1610
1611 =item address1
1612
1613 =item address2
1614
1615 =item city
1616
1617 =item county
1618
1619 =item state
1620
1621 =item zip
1622
1623 =item country
1624
1625 E911 Address (optional)
1626
1627 =back
1628
1629 =item provision_pbx
1630
1631 Provisions a customer PBX (svc_pbx).
1632
1633 Takes a hash reference as parameter with the following keys:
1634
1635 =over 4
1636
1637 =item session_id
1638
1639 Session identifier
1640
1641 =item pkgnum
1642
1643 pkgnum of package into which this service is provisioned
1644
1645 =item svcpart
1646
1647 svcpart or service definition to provision
1648
1649 =item id
1650
1651 =item title
1652
1653 =item max_extensions
1654
1655 =item max_simultaneous
1656
1657 =item ip_addr
1658
1659 =back
1660
1661 =item provision_external
1662
1663 Provisions an external service (svc_external).
1664
1665 Takes a hash reference as parameter with the following keys:
1666
1667 =over 4
1668
1669 =item session_id
1670
1671 Session identifier
1672
1673 =item pkgnum
1674
1675 pkgnum of package into which this service is provisioned
1676
1677 =item svcpart
1678
1679 svcpart or service definition to provision
1680
1681 =item id
1682
1683 =item title
1684
1685 =back
1686
1687 =back
1688
1689 =head2 "MY ACCOUNT" CONTACT FUNCTIONS
1690
1691 =over 4
1692
1693 =item contact_passwd
1694
1695 Changes the password for the currently-logged in contact.
1696
1697 Takes a hash reference as parameter with the following keys:
1698
1699 =over 4
1700
1701 =item session_id
1702
1703 =item new_password
1704
1705 =back
1706
1707 Returns a hash reference with a single parameter, B<error>, which contains an
1708 error message, or empty on success.
1709
1710 =item list_contacts
1711
1712 Takes a hash reference as parameter with a single key, B<session_id>.
1713
1714 Returns a hash reference with two parameters: B<error>, which contains an error
1715 message, or empty on success, and B<contacts>, a list of contacts.
1716
1717 B<contacts> is an array reference of hash references (i.e. an array of structs,
1718  in XML-RPC).  Each hash reference (struct) has the following keys:
1719
1720 =over 4
1721
1722 =item contactnum
1723
1724 =item class
1725
1726 Contact class name (contact type).
1727
1728 =item first
1729
1730 First name
1731
1732 =item last
1733
1734 Last name
1735
1736 =item title
1737
1738 Position ("Director of Silly Walks"), NOT honorific ("Mr." or "Mrs.")
1739
1740 =item emailaddress
1741
1742 Comma-separated list of email addresses
1743
1744 =item comment
1745
1746 =item selfservice_access
1747
1748 Y when enabled
1749
1750 =back
1751
1752 =item edit_contact
1753
1754 Updates information for the currently-logged in contact, or (optionally) the
1755 specified contact.
1756
1757 Takes a hash reference as parameter with the following keys:
1758
1759 =over 4
1760
1761 =item session_id
1762
1763 =item contactnum
1764
1765 If already logged in as a contact, this is optional.
1766
1767 =item first
1768
1769 =item last
1770
1771 =item emailaddress
1772
1773 =back
1774
1775 Returns a hash reference with a single parameter, B<error>, which contains an
1776 error message, or empty on success.
1777
1778 =item new_contact
1779
1780 Creates a new contact.
1781
1782 Takes a hash reference as parameter with the following keys:
1783
1784 =over 4
1785
1786 =item session_id
1787
1788 =item first
1789
1790 =item last
1791
1792 =item emailaddress
1793
1794 =item classnum
1795
1796 Optional contact classnum (TODO: or name)
1797
1798 =item comment
1799
1800 =item selfservice_access
1801
1802 Y to enable self-service access
1803
1804 =item _password
1805
1806 =back
1807
1808 Returns a hash reference with a single parameter, B<error>, which contains an
1809 error message, or empty on success.
1810
1811 =item delete_contact
1812
1813 Deletes a contact.  (Note: Cannot at this time delete the currently-logged in
1814 contact.)
1815
1816 Takes a hash reference as parameter with the following keys:
1817
1818 =over 4
1819
1820 =item session_id
1821
1822 =item contactnum
1823
1824 =back
1825
1826 Returns a hash reference with a single parameter, B<error>, which contains an
1827 error message, or empty on success.
1828
1829 =back
1830
1831 =head2 "MY ACCOUNT" QUOTATION FUNCTIONS
1832
1833 All of these functions require the user to be logged in, and the 'session_id'
1834 key to be included in the argument hashref.`
1835
1836 =over 4
1837
1838 =item list_quotations HASHREF
1839
1840 Returns a hashref listing this customer's active self-service quotations.
1841 Contents are:
1842
1843 =over 4
1844
1845 =item quotations
1846
1847 an arrayref containing an element for each quotation.
1848
1849 =item quotationnum
1850
1851 the primary key
1852
1853 =item _date
1854
1855 the date it was started
1856
1857 =item num_pkgs
1858
1859 the number of packages
1860
1861 =item total_setup
1862
1863 the sum of setup fees
1864
1865 =item total_recur
1866
1867 the sum of recurring charges
1868
1869 =back
1870
1871 =item quotation_new HASHREF
1872
1873 Creates an empty quotation and returns a hashref containing 'quotationnum',
1874 the primary key of the new quotation.
1875
1876 =item quotation_delete HASHREF
1877
1878 Disables (does not really delete) a quotation. Takes the following arguments:
1879
1880 =over 4
1881
1882 =item session_id
1883
1884 =item quotationnum - the quotation to delete
1885
1886 =back
1887
1888 Returns 'error' => a string, which will be empty on success.
1889
1890 =item quotation_info HASHREF
1891
1892 Returns total and detailed pricing information on a quotation.
1893
1894 Takes the following arguments:
1895
1896 =over 4
1897
1898 =item session_id
1899
1900 =item quotationnum - the quotation to return
1901
1902 =back
1903
1904 Returns a hashref containing:
1905
1906 - total_setup, the total of setup fees (and their taxes)
1907 - total_recur, the total of all recurring charges (and their taxes)
1908 - sections, an arrayref containing an element for each quotation section.
1909   - description, a line of text describing the group of charges
1910   - subtotal, the total of charges in this group (if appropriate)
1911   - detail_items, an arrayref of line items
1912     - pkgnum, the reference number of the package
1913     - description, the package name (or tax name)
1914     - quantity
1915     - amount, the amount charged
1916     If the detail item represents a subtotal, it will instead contain:
1917     - total_item: description of the subtotal
1918     - total_amount: the subtotal amount
1919
1920
1921 =item quotation_print HASHREF
1922
1923 Renders the quotation as HTML or PDF. Takes the following arguments:
1924
1925 =over 4
1926
1927 =item session_id
1928
1929 =item quotationnum - the quotation to return
1930
1931 =item format - 'html' or 'pdf'
1932
1933 =back
1934
1935 Returns a hashref containing 'document', the contents of the file.
1936
1937 =item quotation_add_pkg HASHREF
1938
1939 Adds a package to a quotation. Takes the following arguments:
1940
1941 =over 4
1942
1943 =item session_id
1944
1945 =item pkgpart - the package to add
1946
1947 =item quotationnum - the quotation to add it to
1948
1949 =item quantity - the package quantity (defaults to 1)
1950
1951 =item address1, address2, city, state, zip, country - address fields to set
1952 the service location
1953
1954 =back
1955
1956 Returns 'error' => a string, which will be empty on success.
1957
1958 =item quotation_remove_pkg HASHREF
1959
1960 Removes a package from a quotation. Takes the following arguments:
1961
1962 =over 4
1963
1964 =item session_id
1965
1966 =item pkgnum - the primary key (quotationpkgnum) of the package to remove
1967
1968 =item quotationnum - the quotation to remove it from
1969
1970 =back
1971
1972 Returns 'error' => a string, which will be empty on success.
1973
1974 =item quotation_order HASHREF
1975
1976 Converts the packages in a quotation into real packages. Takes the following
1977 arguments:
1978
1979 Takes the following arguments:
1980
1981 =over 4
1982
1983 =item session_id
1984
1985 =item quotationnum - the quotation to order
1986
1987 =back
1988
1989 =back
1990
1991 =head1 SIGNUP FUNCTIONS
1992
1993 =over 4
1994
1995 =item signup_info HASHREF
1996
1997 Takes a hash reference as parameter with the following keys:
1998
1999 =over 4
2000
2001 =item session_id - Optional agent/reseller interface session
2002
2003 =back
2004
2005 Returns a hash reference containing information that may be useful in
2006 displaying a signup page.  The hash reference contains the following keys:
2007
2008 =over 4
2009
2010 =item cust_main_county
2011
2012 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.
2013
2014 =item part_pkg
2015
2016 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
2017 an agentnum specified explicitly via reseller interface session_id in the
2018 options.
2019
2020 =item agent
2021
2022 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.
2023
2024 =item agentnum2part_pkg
2025
2026 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.
2027
2028 =item svc_acct_pop
2029
2030 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.
2031
2032 =item security_phrase
2033
2034 True if the "security_phrase" feature is enabled
2035
2036 =item payby
2037
2038 Array reference of acceptable payment types for signup
2039
2040 =over 4
2041
2042 =item CARD
2043
2044 credit card - automatic
2045
2046 =item DCRD
2047
2048 credit card - on-demand - version 1.5+ only
2049
2050 =item CHEK
2051
2052 electronic check - automatic
2053
2054 =item DCHK
2055
2056 electronic check - on-demand - version 1.5+ only
2057
2058 =item LECB
2059
2060 Phone bill billing
2061
2062 =item BILL
2063
2064 billing, not recommended for signups
2065
2066 =item COMP
2067
2068 free, definitely not recommended for signups
2069
2070 =item PREPAY
2071
2072 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
2073
2074 =back
2075
2076 =item cvv_enabled
2077
2078 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
2079
2080 =item msgcat
2081
2082 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".
2083
2084 =item statedefault
2085
2086 Default state
2087
2088 =item countrydefault
2089
2090 Default country
2091
2092 =back
2093
2094 =item new_customer_minimal HASHREF
2095
2096 Creates a new customer.
2097
2098 Current differences from new_customer: An address is not required.  promo_code
2099 and reg_code are not supported.  If invoicing_list and _password is passed, a
2100 contact will be created with self-service access (no pkgpart or username is
2101 necessary).  No initial billing is run (this may change in a future version).
2102
2103 Takes a hash reference as parameter with the following keys:
2104
2105 =over 4
2106
2107 =item first
2108
2109 first name (required)
2110
2111 =item last
2112
2113 last name (required)
2114
2115 =item ss
2116
2117 (not typically collected; mostly used for ACH transactions)
2118
2119 =item company
2120
2121 Company name
2122
2123 =item address1
2124
2125 Address line one
2126
2127 =item address2
2128
2129 Address line two
2130
2131 =item city
2132
2133 City
2134
2135 =item county
2136
2137 County
2138
2139 =item state
2140
2141 State
2142
2143 =item zip
2144
2145 Zip or postal code
2146
2147 =item daytime
2148
2149 Daytime phone number
2150
2151 =item night
2152
2153 Evening phone number
2154
2155 =item fax
2156
2157 Fax number
2158
2159 =item payby
2160
2161 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
2162
2163 =item payinfo
2164
2165 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
2166
2167 =item paycvv
2168
2169 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
2170
2171 =item paydate
2172
2173 Expiration date for CARD/DCRD
2174
2175 =item payname
2176
2177 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
2178
2179 =item invoicing_list
2180
2181 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),
2182
2183 =item referral_custnum
2184
2185 referring customer number
2186
2187 =item agentnum
2188
2189 Agent number
2190
2191 =item pkgpart
2192
2193 pkgpart of initial package
2194
2195 =item username
2196
2197 Username
2198
2199 =item _password
2200
2201 Password
2202
2203 =item sec_phrase
2204
2205 Security phrase
2206
2207 =item popnum
2208
2209 Access number (index, not the literal number)
2210
2211 =item countrycode
2212
2213 Country code (to be provisioned as a service)
2214
2215 =item phonenum
2216
2217 Phone number (to be provisioned as a service)
2218
2219 =item pin
2220
2221 Voicemail PIN
2222
2223 =back
2224
2225 Returns a hash reference with the following keys:
2226
2227 =over 4
2228
2229 =item error
2230
2231 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)
2232
2233 =back
2234
2235 =item new_customer HASHREF
2236
2237 Creates a new customer.  Takes a hash reference as parameter with the
2238 following keys:
2239
2240 =over 4
2241
2242 =item first
2243
2244 first name (required)
2245
2246 =item last
2247
2248 last name (required)
2249
2250 =item ss
2251
2252 (not typically collected; mostly used for ACH transactions)
2253
2254 =item company
2255
2256 Company name
2257
2258 =item address1 (required)
2259
2260 Address line one
2261
2262 =item address2
2263
2264 Address line two
2265
2266 =item city (required)
2267
2268 City
2269
2270 =item county
2271
2272 County
2273
2274 =item state (required)
2275
2276 State
2277
2278 =item zip (required)
2279
2280 Zip or postal code
2281
2282 =item ship_address1
2283
2284 =item ship_address2
2285
2286 =item ship_city
2287
2288 =item ship_county
2289
2290 =item ship_state
2291
2292 =item ship_zip
2293
2294 Optional shipping address fields.  If sending an optional shipping address,
2295 ship_address1, ship_city, ship_state and ship_zip are required.
2296
2297 =item daytime
2298
2299 Daytime phone number
2300
2301 =item night
2302
2303 Evening phone number
2304
2305 =item fax
2306
2307 Fax number
2308
2309 =item payby
2310
2311 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
2312
2313 =item payinfo
2314
2315 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
2316
2317 =item paycvv
2318
2319 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
2320
2321 =item paydate
2322
2323 Expiration date for CARD/DCRD
2324
2325 =item payname
2326
2327 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
2328
2329 =item invoicing_list
2330
2331 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),
2332
2333 =item referral_custnum
2334
2335 referring customer number
2336
2337 =item agentnum
2338
2339 Agent number
2340
2341 =item pkgpart
2342
2343 pkgpart of initial package
2344
2345 =item username
2346
2347 Username
2348
2349 =item _password
2350
2351 Password
2352
2353 =item sec_phrase
2354
2355 Security phrase
2356
2357 =item popnum
2358
2359 Access number (index, not the literal number)
2360
2361 =item countrycode
2362
2363 Country code (to be provisioned as a service)
2364
2365 =item phonenum
2366
2367 Phone number (to be provisioned as a service)
2368
2369 =item pin
2370
2371 Voicemail PIN
2372
2373 =back
2374
2375 Returns a hash reference with the following keys:
2376
2377 =over 4
2378
2379 =item error
2380
2381 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)
2382
2383 =back
2384
2385 =item regionselector HASHREF | LIST
2386
2387 Takes as input a hashref or list of key/value pairs with the following keys:
2388
2389 =over 4
2390
2391 =item selected_county
2392
2393 Currently selected county
2394
2395 =item selected_state
2396
2397 Currently selected state
2398
2399 =item selected_country
2400
2401 Currently selected country
2402
2403 =item prefix
2404
2405 Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
2406
2407 =item onchange
2408
2409 Specify a javascript subroutine to call on changes
2410
2411 =item default_state
2412
2413 Default state
2414
2415 =item default_country
2416
2417 Default country
2418
2419 =item locales
2420
2421 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>.
2422
2423 =back
2424
2425 Returns a list consisting of three HTML fragments for county selection,
2426 state selection and country selection, respectively.
2427
2428 =cut
2429
2430 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
2431 sub regionselector {
2432   my $param;
2433   if ( ref($_[0]) ) {
2434     $param = shift;
2435   } else {
2436     $param = { @_ };
2437   }
2438   $param->{'selected_country'} ||= $param->{'default_country'};
2439   $param->{'selected_state'} ||= $param->{'default_state'};
2440
2441   my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2442
2443   my $disabled = $param->{'disabled'};
2444
2445   my $countyflag = $param->{selected_county} ? 1 : 0;
2446   my $cityflag = $param->{selected_city} ? 1 : 0;
2447
2448   my %cust_main_county;
2449
2450 #  unless ( @cust_main_county ) { #cache 
2451     #@cust_main_county = qsearch('cust_main_county', {} );
2452     #foreach my $c ( @cust_main_county ) {
2453     foreach my $c ( @{ $param->{'locales'} } ) {
2454       #$countyflag=1 if $c->county;
2455       $countyflag=1 if $c->{county};
2456       $cityflag=1 if ($c->{city} && $cityflag);
2457       #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
2458       #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
2459       $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}}{$c->{city}} = 1;
2460     }
2461 #  }
2462
2463   my $script_html = <<END;
2464     <SCRIPT>
2465     function opt(what,value,text,selected) {
2466       var optionName = new Option(text, value, false, selected);
2467       var length = what.length;
2468       what.options[length] = optionName;
2469     }
2470     function ${prefix}country_changed(what) {
2471       country = what.options[what.selectedIndex].text;
2472       for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
2473           what.form.${prefix}state.options[i] = null;
2474 END
2475       #what.form.${prefix}state.options[0] = new Option('', '', false, true);
2476
2477   foreach my $country ( sort keys %cust_main_county ) {
2478     $script_html .= "\nif ( country == \"$country\" ) {\n";
2479     foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
2480       my $text = $state || '(n/a)';
2481       $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
2482     }
2483     $script_html .= "}\n";
2484   }
2485
2486   $script_html .= <<END;
2487     }
2488     function ${prefix}state_changed(what) {
2489 END
2490
2491   if ( $countyflag ) {
2492     $script_html .= <<END;
2493       state = what.options[what.selectedIndex].text;
2494       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
2495       for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
2496           what.form.${prefix}county.options[i] = null;
2497 END
2498
2499     foreach my $country ( sort keys %cust_main_county ) {
2500       $script_html .= "\nif ( country == \"$country\" ) {\n";
2501       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
2502         $script_html .= "\nif ( state == \"$state\" ) {\n";
2503           #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
2504           foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
2505             my $text = $county || '(n/a)';
2506             if (!$county) {
2507               if ( $cityflag) {
2508                 $script_html .= qq!what.form.${prefix}city.style.display='';\n
2509                                 what.form.${prefix}city_select.style.display='none';\n!
2510               }
2511               $script_html .= qq!opt(what.form.${prefix}county, "$county", "$text");\n!
2512               #$script_html .= qq!what.form.${prefix}county.style.display='none';\n!
2513             }
2514             else {
2515               $script_html .= qq!var countySelected = false; if ("$param->{selected_county}" == "$text") { countySelected = true; }\n
2516                               opt(what.form.${prefix}county, "$county", "$text", countySelected);\n
2517                               what.form.${prefix}county.style.display='';\n
2518                               county = what.form.${prefix}county.options[what.form.${prefix}county.selectedIndex].text;\n!;
2519               if ( $cityflag) {
2520                 $script_html .= qq!\nif ( county == \"$county\" ) {\n!;
2521                 foreach my $city ( sort keys %{$cust_main_county{$country}{$state}{$county}} ) {
2522                   my $text = $city || '(n/a)';
2523                   if (!$city) {
2524                     $script_html .= qq!what.form.${prefix}city.style.display='';\n
2525                                     what.form.${prefix}city_select.style.display='none';\n!
2526                   }
2527                   else {
2528                     $script_html .= qq!var citySelected = false; if ("$param->{selected_city}" == "$text") { citySelected = true; }\n
2529                                     opt(what.form.${prefix}city_select, "$city", "$text", citySelected);\n
2530                                     what.form.${prefix}city.style.display='none';\n
2531                                     what.form.${prefix}city_select.style.display='';\n!
2532                   }
2533                 }
2534                 $script_html .= "}\n";
2535               }
2536             }
2537           }
2538         $script_html .= "}\n";
2539       }
2540       $script_html .= "}\n";
2541     }
2542   }
2543
2544   $script_html .= <<END;
2545     }
2546     function ${prefix}county_changed(what) {
2547 END
2548
2549   if ( $cityflag) {
2550     $script_html .= <<END;
2551       saved_city = "$param->{selected_city}";
2552       county = what.options[what.selectedIndex].text;
2553       state = what.form.${prefix}state.options[what.form.${prefix}state.selectedIndex].text;
2554       country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
2555       for ( var i = what.form.${prefix}city_select.length; i >= 0; i-- )
2556           what.form.${prefix}city_select.options[i] = null;
2557 END
2558
2559     foreach my $country ( sort keys %cust_main_county ) {
2560       $script_html .= "\nif ( country == \"$country\" ) {\n";
2561       foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
2562         $script_html .= "\nif ( state == \"$state\" ) {\n";
2563         #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
2564         foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
2565           $script_html .= "\nif ( county == \"$county\" ) {\n";
2566             foreach my $city ( sort keys %{$cust_main_county{$country}{$state}{$county}} ) {
2567               my $text = $city || '(n/a)';
2568               if (!$city) {
2569                 $script_html .= qq!what.form.${prefix}city.style.display='';\n
2570                                 what.form.${prefix}city_select.style.display='none';\n!
2571               }
2572               else {
2573                 $script_html .= qq!var citySelected = false; if (saved_city == "$text") { citySelected = true; }\n
2574                                 opt(what.form.${prefix}city_select, "$city", "$text", citySelected);\n
2575                                 what.form.${prefix}city.style.display='none';\n
2576                                 what.form.${prefix}city_select.style.display='';\n!
2577               }
2578             }
2579           $script_html .= "}\n";
2580         }
2581         $script_html .= "}\n";
2582       }
2583       $script_html .= "}\n";
2584     }
2585   }
2586
2587   $script_html .= <<END;
2588     }
2589     function ${prefix}city_select_changed(what) {
2590 END
2591
2592   if ( $cityflag ) {
2593     $script_html .= <<END;
2594       what.form.${prefix}city.value = what.options[what.selectedIndex].value;
2595 END
2596   }
2597
2598   $script_html .= <<END;
2599     }
2600     </SCRIPT>
2601 END
2602
2603   my $city_html = '';
2604   if ( $cityflag ) {
2605     if ( scalar (keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}}{$param->{'selected_county'}} }) > 1 ) {
2606       $city_html .= qq!<SELECT NAME="${prefix}city_select" onChange="${prefix}city_select_changed(this); $param->{'onchange'}">!;
2607       foreach my $city (
2608         sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}}{$param->{'selected_county'}} }
2609       ) {
2610         my $text = $city || '(n/a)';
2611         $city_html .= qq!<OPTION VALUE="$city"!.
2612                       ($city eq $param->{'selected_city'} ?
2613                         ' SELECTED>' :
2614                         '>'
2615                       ).
2616                       $text;
2617       }
2618       $city_html .= qq!</OPTION><INPUT TYPE="text" ID="${prefix}city" NAME="${prefix}city" VALUE="$param->{'selected_city'}" style="display:none">!;
2619     } else {
2620       $city_html .= qq!<SELECT NAME="${prefix}city_select" onChange="${prefix}city_select_changed(this); $param->{'onchange'}" style="display:none"></SELECT>
2621                     <INPUT TYPE="text" ID="${prefix}city" NAME="${prefix}city" VALUE="$param->{'selected_city'}" style="display:''">!;
2622     }
2623   }
2624
2625   my $county_html = $script_html;
2626   if ( $countyflag ) {
2627     $county_html .= qq!<SELECT NAME="${prefix}county" !.
2628                     qq!onChange="${prefix}county_changed(this); $param->{'onchange'}">!;
2629     foreach my $county ( 
2630       sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
2631     ) {
2632       my $text = $county || '(n/a)';
2633       $county_html .= qq!<OPTION VALUE="$county"!.
2634                       ($county eq $param->{'selected_county'} ? 
2635                         ' SELECTED>' : 
2636                         '>'
2637                       ).
2638                       $text.
2639                       '</OPTION>';
2640     }
2641     $county_html .= '</SELECT>';
2642   } else {
2643     $county_html .=
2644       qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
2645   }
2646
2647   my $state_html = qq!<SELECT NAME="${prefix}state" !.
2648                    qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
2649   foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
2650     my $text = $state || '(n/a)';
2651     my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
2652     $state_html .= "\n<OPTION $selected VALUE=\"$state\">$text</OPTION>"
2653   }
2654   $state_html .= '</SELECT>';
2655
2656   my $country_html = '';
2657   if ( scalar( keys %cust_main_county ) > 1 )  {
2658
2659     $country_html = qq(<SELECT NAME="${prefix}country" ).
2660                     qq(onChange="${prefix}country_changed(this); ).
2661                                  $param->{'onchange'}.
2662                                '"'.
2663                       '>';
2664     my $countrydefault = $param->{default_country} || 'US';
2665     foreach my $country (
2666       sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
2667         keys %cust_main_county
2668     ) {
2669       my $selected = $country eq $param->{'selected_country'}
2670                        ? ' SELECTED'
2671                        : '';
2672       $country_html .= "\n<OPTION $selected>$country</OPTION>"
2673     }
2674     $country_html .= '</SELECT>';
2675   } else {
2676
2677     $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
2678                             ' VALUE="'. (keys %cust_main_county )[0]. '">';
2679
2680   }
2681
2682   ($county_html, $state_html, $country_html, $city_html);
2683
2684 }
2685
2686 sub regionselector_hashref {
2687   my ($county_html, $state_html, $country_html) = regionselector(@_);
2688   {
2689     'county_html'  => $county_html,
2690     'state_html'   => $state_html,
2691     'country_html' => $country_html,
2692   };
2693 }
2694
2695 =item location_form HASHREF | LIST
2696
2697 Takes as input a hashref or list of key/value pairs with the following keys:
2698
2699 =over 4
2700
2701 =item session_id
2702
2703 Current customer session_id
2704
2705 =item no_asterisks
2706
2707 Omit red asterisks from required fields.
2708
2709 =item address1_label
2710
2711 Label for first address line.
2712
2713 =back
2714
2715 Returns an HTML fragment for a location form (address, city, state, zip,
2716 country)
2717
2718 =cut
2719
2720 sub location_form {
2721   my $param;
2722   if ( ref($_[0]) ) {
2723     $param = shift;
2724   } else {
2725     $param = { @_ };
2726   }
2727
2728   my $session_id = delete $param->{'session_id'};
2729
2730   my $rv = mason_comp( 'session_id' => $session_id,
2731                        'comp'       => '/elements/location.html',
2732                        'args'       => [ %$param ],
2733                      );
2734
2735   #hmm.
2736   $rv->{'error'} || $rv->{'output'};
2737
2738 }
2739
2740
2741 #=item expselect HASHREF | LIST
2742 #
2743 #Takes as input a hashref or list of key/value pairs with the following keys:
2744 #
2745 #=over 4
2746 #
2747 #=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
2748 #
2749 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
2750 #
2751 #=back
2752
2753 =item expselect PREFIX [ DATE ]
2754
2755 Takes as input a unique prefix string and the current expiration date, in
2756 yyyy-mm-dd or m-d-yyyy format
2757
2758 Returns an HTML fragments for expiration date selection.
2759
2760 =cut
2761
2762 sub expselect {
2763   #my $param;
2764   #if ( ref($_[0]) ) {
2765   #  $param = shift;
2766   #} else {
2767   #  $param = { @_ };
2768   #my $prefix = $param->{'prefix'};
2769   #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2770   #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
2771   my $prefix = shift;
2772   my $date = scalar(@_) ? shift : '';
2773
2774   my( $m, $y ) = ( 0, 0 );
2775   if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
2776     ( $m, $y ) = ( $2, $1 );
2777   } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
2778     ( $m, $y ) = ( $1, $3 );
2779   }
2780   my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
2781   for ( 1 .. 12 ) {
2782     $return .= qq!<OPTION VALUE="$_"!;
2783     $return .= " SELECTED" if $_ == $m;
2784     $return .= ">$_";
2785   }
2786   $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
2787   my @t = localtime;
2788   my $thisYear = $t[5] + 1900;
2789   for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
2790     $return .= qq!<OPTION VALUE="$_"!;
2791     $return .= " SELECTED" if $_ == $y;
2792     $return .= ">$_";
2793   }
2794   $return .= "</SELECT>";
2795
2796   $return;
2797 }
2798
2799 =item popselector HASHREF | LIST
2800
2801 Takes as input a hashref or list of key/value pairs with the following keys:
2802
2803 =over 4
2804
2805 =item popnum
2806
2807 Access number number
2808
2809 =item pops
2810
2811 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>.
2812
2813 =back
2814
2815 Returns an HTML fragment for access number selection.
2816
2817 =cut
2818
2819 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
2820 sub popselector {
2821   my $param;
2822   if ( ref($_[0]) ) {
2823     $param = shift;
2824   } else {
2825     $param = { @_ };
2826   }
2827   my $popnum = $param->{'popnum'};
2828   my $pops = $param->{'pops'};
2829
2830   return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2831   return $pops->[0]{city}. ', '. $pops->[0]{state}.
2832          ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2833          '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2834     if scalar(@$pops) == 1;
2835
2836   my %pop = ();
2837   my %popnum2pop = ();
2838   foreach (@$pops) {
2839     push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2840     $popnum2pop{$_->{popnum}} = $_;
2841   }
2842
2843   my $text = <<END;
2844     <SCRIPT>
2845     function opt(what,href,text) {
2846       var optionName = new Option(text, href, false, false)
2847       var length = what.length;
2848       what.options[length] = optionName;
2849     }
2850 END
2851
2852   my $init_popstate = $param->{'init_popstate'};
2853   if ( $init_popstate ) {
2854     $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2855              $init_popstate. '">';
2856   } else {
2857     $text .= <<END;
2858       function acstate_changed(what) {
2859         state = what.options[what.selectedIndex].text;
2860         what.form.popac.options.length = 0
2861         what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2862 END
2863   } 
2864
2865   my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2866   foreach my $state ( sort { $a cmp $b } @states ) {
2867     $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2868
2869     foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2870       $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2871       if ($ac eq $param->{'popac'}) {
2872         $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2873       }
2874     }
2875     $text .= "}\n" unless $init_popstate;
2876   }
2877   $text .= "popac_changed(what.form.popac)}\n";
2878
2879   $text .= <<END;
2880   function popac_changed(what) {
2881     ac = what.options[what.selectedIndex].text;
2882     what.form.popnum.options.length = 0;
2883     what.form.popnum.options[0] = new Option("City", "-1", false, true);
2884
2885 END
2886
2887   foreach my $state ( @states ) {
2888     foreach my $popac ( keys %{ $pop{$state} } ) {
2889       $text .= "\nif ( ac == \"$popac\" ) {\n";
2890
2891       foreach my $pop ( @{$pop{$state}->{$popac}}) {
2892         my $o_popnum = $pop->{popnum};
2893         my $poptext =  $pop->{city}. ', '. $pop->{state}.
2894                        ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2895
2896         $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2897         if ($popnum == $o_popnum) {
2898           $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2899         }
2900       }
2901       $text .= "}\n";
2902     }
2903   }
2904
2905
2906   $text .= "}\n</SCRIPT>\n";
2907
2908   $param->{'acstate'} = '' unless defined($param->{'acstate'});
2909
2910   $text .=
2911     qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2912     qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2913   $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2914            ">$_" foreach sort { $a cmp $b } @states;
2915   $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';
2916
2917   $text .=
2918     qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2919     qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2920
2921   $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2922
2923
2924   #comment this block to disable initial list polulation
2925   my @initial_select = ();
2926   if ( scalar( @$pops ) > 100 ) {
2927     push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2928   } else {
2929     @initial_select = @$pops;
2930   }
2931   foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2932     $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2933              ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2934              $pop->{city}. ', '. $pop->{state}.
2935                ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2936   }
2937
2938   $text .= qq!</SELECT></TD></TR></TABLE>!;
2939
2940   $text;
2941
2942 }
2943
2944 =item domainselector HASHREF | LIST
2945
2946 Takes as input a hashref or list of key/value pairs with the following keys:
2947
2948 =over 4
2949
2950 =item pkgnum
2951
2952 Package number
2953
2954 =item domsvc
2955
2956 Service number of the selected item.
2957
2958 =back
2959
2960 Returns an HTML fragment for domain selection.
2961
2962 =cut
2963
2964 sub domainselector {
2965   my $param;
2966   if ( ref($_[0]) ) {
2967     $param = shift;
2968   } else {
2969     $param = { @_ };
2970   }
2971   my $domsvc= $param->{'domsvc'};
2972   my $rv = 
2973       domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2974   my $domains = $rv->{'domains'};
2975   $domsvc = $rv->{'domsvc'} unless $domsvc;
2976
2977   return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2978     unless scalar(keys %$domains);
2979
2980   if (scalar(keys %$domains) == 1) {
2981     my $key;
2982     foreach(keys %$domains) {
2983       $key = $_;
2984     }
2985     return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2986            '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2987   }
2988
2989   my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2990
2991   $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2992
2993   foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2994     $text .= qq!<OPTION VALUE="!. $domain. '"'.
2995              ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2996              $domains->{$domain};
2997   }
2998
2999   $text .= qq!</SELECT></TD></TR>!;
3000
3001   $text;
3002
3003 }
3004
3005 =item didselector HASHREF | LIST
3006
3007 Takes as input a hashref or list of key/value pairs with the following keys:
3008
3009 =over 4
3010
3011 =item field
3012
3013 Field name for the returned HTML fragment.
3014
3015 =item svcpart
3016
3017 Service definition (see L<FS::part_svc>)
3018
3019 =back
3020
3021 Returns an HTML fragment for DID selection.
3022
3023 =cut
3024
3025 sub didselector {
3026   my $param;
3027   if ( ref($_[0]) ) {
3028     $param = shift;
3029   } else {
3030     $param = { @_ };
3031   }
3032
3033   my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
3034                        'args'=>[ %$param ],
3035                      );
3036
3037   #hmm.
3038   $rv->{'error'} || $rv->{'output'};
3039
3040 }
3041
3042 =back
3043
3044 =head1 RESELLER FUNCTIONS
3045
3046 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
3047 with their active session, and the B<customer_info> and B<order_pkg> functions
3048 with their active session and an additional I<custnum> parameter.
3049
3050 For the most part, development of the reseller web interface has been
3051 superceded by agent-virtualized access to the backend.
3052
3053 =over 4
3054
3055 =item agent_login
3056
3057 Agent login
3058
3059 =item agent_info
3060
3061 Agent info
3062
3063 =item agent_list_customers
3064
3065 List agent's customers.
3066
3067 =back
3068
3069 =head1 BUGS
3070
3071 =head1 SEE ALSO
3072
3073 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
3074
3075 =cut
3076
3077 1;
3078