1 package FS::SelfService;
4 use vars qw( $VERSION @ISA @EXPORT_OK $DEBUG
5 $skip_uid_check $dir $socket %autoload $tag );
11 use Storable 2.09 qw(nstore_fd fd_retrieve);
15 @ISA = qw( Exporter );
19 $dir = "/usr/local/freeside";
20 $socket = "$dir/selfservice_socket";
21 $socket .= '.'.$tag if defined $tag && length($tag);
23 #maybe should ask ClientAPI for this list
25 'passwd' => 'passwd/passwd',
26 'chfn' => 'passwd/passwd',
27 'chsh' => 'passwd/passwd',
28 'login_info' => 'MyAccount/login_info',
29 'login' => 'MyAccount/login',
30 'logout' => 'MyAccount/logout',
31 'customer_info' => 'MyAccount/customer_info',
32 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
33 'invoice' => 'MyAccount/invoice',
34 'invoice_pdf' => 'MyAccount/invoice_pdf',
35 'invoice_logo' => 'MyAccount/invoice_logo',
36 'list_invoices' => 'MyAccount/list_invoices', #?
37 'cancel' => 'MyAccount/cancel', #add to ss cgi!
38 'payment_info' => 'MyAccount/payment_info',
39 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
40 'process_payment' => 'MyAccount/process_payment',
41 'store_payment' => 'MyAccount/store_payment',
42 'process_stored_payment' => 'MyAccount/process_stored_payment',
43 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
44 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
45 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
46 'process_prepay' => 'MyAccount/process_prepay',
47 'realtime_collect' => 'MyAccount/realtime_collect',
48 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
49 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
50 'list_svc_usage' => 'MyAccount/list_svc_usage',
51 'port_graph' => 'MyAccount/port_graph',
52 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
53 'list_support_usage' => 'MyAccount/list_support_usage',
54 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
55 'change_pkg' => 'MyAccount/change_pkg',
56 'order_recharge' => 'MyAccount/order_recharge',
57 'renew_info' => 'MyAccount/renew_info',
58 'order_renew' => 'MyAccount/order_renew',
59 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
60 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi!
61 'charge' => 'MyAccount/charge', #?
62 'part_svc_info' => 'MyAccount/part_svc_info',
63 'provision_acct' => 'MyAccount/provision_acct',
64 'provision_phone' => 'MyAccount/provision_phone',
65 'provision_external' => 'MyAccount/provision_external',
66 'unprovision_svc' => 'MyAccount/unprovision_svc',
67 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
68 'create_ticket' => 'MyAccount/create_ticket',
69 'get_ticket' => 'MyAccount/get_ticket',
70 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
71 'did_report' => 'MyAccount/did_report',
72 'signup_info' => 'Signup/signup_info',
73 'skin_info' => 'MyAccount/skin_info',
74 'access_info' => 'MyAccount/access_info',
75 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
76 'new_customer' => 'Signup/new_customer',
77 'capture_payment' => 'Signup/capture_payment',
78 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
79 'new_agent' => 'Agent/new_agent',
80 'agent_login' => 'Agent/agent_login',
81 'agent_logout' => 'Agent/agent_logout',
82 'agent_info' => 'Agent/agent_info',
83 'agent_list_customers' => 'Agent/agent_list_customers',
84 'check_username' => 'Agent/check_username',
85 'suspend_username' => 'Agent/suspend_username',
86 'unsuspend_username' => 'Agent/unsuspend_username',
87 'mason_comp' => 'MasonComponent/mason_comp',
88 'call_time' => 'PrepaidPhone/call_time',
89 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
90 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
92 #'bulk_processrow' => 'Bulk/processrow',
93 #conflicts w/Agent one# 'check_username' => 'Bulk/check_username',
95 'ping' => 'SGNG/ping',
96 'decompify_pkgs' => 'SGNG/decompify_pkgs',
97 'previous_payment_info' => 'SGNG/previous_payment_info',
98 'previous_payment_info_renew_info'
99 => 'SGNG/previous_payment_info_renew_info',
100 'previous_process_payment' => 'SGNG/previous_process_payment',
101 'previous_process_payment_order_pkg'
102 => 'SGNG/previous_process_payment_order_pkg',
103 'previous_process_payment_change_pkg'
104 => 'SGNG/previous_process_payment_change_pkg',
105 'previous_process_payment_order_renew'
106 => 'SGNG/previous_process_payment_order_renew',
110 qw( regionselector regionselector_hashref location_form
111 expselect popselector domainselector didselector
115 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
116 $ENV{'SHELL'} = '/bin/sh';
117 $ENV{'IFS'} = " \t\n";
120 $ENV{'BASH_ENV'} = '';
122 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
123 #if you grant appropriate permissions to whatever user
124 my $freeside_uid = scalar(getpwnam('freeside'));
125 die "not running as the freeside user\n"
126 if $> != $freeside_uid && ! $skip_uid_check;
128 -e $dir or die "FATAL: $dir doesn't exist!";
129 -d $dir or die "FATAL: $dir isn't a directory!";
130 -r $dir or die "FATAL: Can't read $dir as freeside user!";
131 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
133 foreach my $autoload ( keys %autoload ) {
136 "sub $autoload { ". '
141 #warn scalar(@_). ": ". join(" / ", @_);
145 $param->{_packet} = \''. $autoload{$autoload}. '\';
147 simple_packet($param);
157 warn "sending ". $packet->{_packet}. " to server"
159 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
160 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
161 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
164 #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
166 #block until there is a message on socket
167 # my $w = new IO::Select;
169 # my @wait = $w->can_read;
171 warn "reading message from server"
174 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
175 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
177 warn "returning message to client"
185 FS::SelfService - Freeside self-service API
189 # password and shell account changes
190 use FS::SelfService qw(passwd chfn chsh);
192 # "my account" functionality
193 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
195 my $rv = login( { 'username' => $username,
197 'password' => $password,
201 if ( $rv->{'error'} ) {
202 #handle login error...
205 my $session_id = $rv->{'session_id'};
208 my $customer_info = customer_info( { 'session_id' => $session_id } );
210 #payment_info and process_payment are available in 1.5+ only
211 my $payment_info = payment_info( { 'session_id' => $session_id } );
213 #!!! process_payment example
215 #!!! list_pkgs example
217 #!!! order_pkg example
219 #!!! cancel_pkg example
221 # signup functionality
222 use FS::SelfService qw( signup_info new_customer );
224 my $signup_info = signup_info;
226 $rv = new_customer( {
229 'company' => $company,
230 'address1' => $address1,
231 'address2' => $address2,
235 'country' => $country,
236 'daytime' => $daytime,
240 'payinfo' => $payinfo,
242 'paystart_month' => $paystart_month
243 'paystart_year' => $paystart_year,
244 'payissue' => $payissue,
246 'paydate' => $paydate,
247 'payname' => $payname,
248 'invoicing_list' => $invoicing_list,
249 'referral_custnum' => $referral_custnum,
250 'agentnum' => $agentnum,
251 'pkgpart' => $pkgpart,
253 'username' => $username,
254 '_password' => $password,
258 'phonenum' => $phonenum,
263 my $error = $rv->{'error'};
264 if ( $error eq '_decline' ) {
274 Use this API to implement your own client "self-service" module.
276 If you just want to customize the look of the existing "self-service" module,
279 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
291 =head1 "MY ACCOUNT" FUNCTIONS
297 Creates a user session. Takes a hash reference as parameter with the
316 Returns a hash reference with the following keys:
322 Empty on success, or an error message on errors.
326 Session identifier for successful logins
330 =item customer_info HASHREF
332 Returns general customer information.
334 Takes a hash reference as parameter with a single key: B<session_id>
336 Returns a hash reference with the following keys:
350 Array reference of hash references of open inoices. Each hash reference has
351 the following keys: invnum, date, owed
355 An HTML fragment containing shipping and billing addresses.
357 =item The following fields are also returned
359 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
363 =item edit_info HASHREF
365 Takes a hash reference as parameter with any of the following keys:
367 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
369 If a field exists, the customer record is updated with the new value of that
370 field. If a field does not exist, that field is not changed on the customer
373 Returns a hash reference with a single key, B<error>, empty on success, or an
374 error message on errors
376 =item invoice HASHREF
378 Returns an invoice. Takes a hash reference as parameter with two keys:
379 session_id and invnum
381 Returns a hash reference with the following keys:
387 Empty on success, or an error message on errors
399 =item list_invoices HASHREF
401 Returns a list of all customer invoices. Takes a hash references with a single
404 Returns a hash reference with the following keys:
410 Empty on success, or an error message on errors
414 Reference to array of hash references with the following keys:
424 Invoice date, in UNIX epoch time
432 Cancels this customer.
434 Takes a hash reference as parameter with a single key: B<session_id>
436 Returns a hash reference with a single key, B<error>, which is empty on
437 success or an error message on errors.
439 =item payment_info HASHREF
441 Returns information that may be useful in displaying a payment page.
443 Takes a hash reference as parameter with a single key: B<session_id>.
445 Returns a hash reference with the following keys:
451 Empty on success, or an error message on errors
459 Exact name on credit card (CARD/DCRD)
483 Customer's current default payment type.
487 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
491 For CARD/DCRD payment types, the card number
495 For CARD/DCRD payment types, expiration month
499 For CARD/DCRD payment types, expiration year
501 =item cust_main_county
503 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.
507 Array reference of all states in the current default country.
511 Hash reference of card types; keys are card types, values are the exact strings
512 passed to the process_payment function
516 #this doesn't actually work yet
520 #Unique transaction identifier (prevents multiple charges), passed to the
521 #process_payment function
525 =item process_payment HASHREF
527 Processes a payment and possible change of address or payment type. Takes a
528 hash reference as parameter with the following keys:
542 If true, address and card information entered will be saved for subsequent
547 If true, future credit card payments will be done automatically (sets payby to
548 CARD). If false, future credit card payments will be done on-demand (sets
549 payby to DCRD). This option only has meaning if B<save> is set true.
577 Two-letter country code
585 Card expiration month
593 #this doesn't actually work yet
597 #Unique transaction identifier, returned from the payment_info function.
598 #Prevents multiple charges.
602 Returns a hash reference with a single key, B<error>, empty on success, or an
603 error message on errors.
605 =item process_payment_order_pkg
607 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
608 payment processes sucessfully, the package is ordered. Takes a hash reference
609 as parameter with the keys of both methods.
611 Returns a hash reference with a single key, B<error>, empty on success, or an
612 error message on errors.
614 =item process_payment_change_pkg
616 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
617 payment processes sucessfully, the package is ordered. Takes a hash reference
618 as parameter with the keys of both methods.
620 Returns a hash reference with a single key, B<error>, empty on success, or an
621 error message on errors.
624 =item process_payment_order_renew
626 Combines the B<process_payment> and B<order_renew> functions in one step. If
627 the payment processes sucessfully, the renewal is processed. Takes a hash
628 reference as parameter with the keys of both methods.
630 Returns a hash reference with a single key, B<error>, empty on success, or an
631 error message on errors.
635 Returns package information for this customer. For more detail on services,
638 Takes a hash reference as parameter with a single key: B<session_id>
640 Returns a hash reference containing customer package information. The hash reference contains the following keys:
650 Empty on success, or an error message on errors.
652 =item cust_pkg HASHREF
654 Array reference of hash references, each of which has the fields of a cust_pkg
655 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
656 the internal FS:: objects, but hash references of columns and values.
660 =item part_pkg fields
662 All fields of part_pkg for this specific cust_pkg (be careful with this
663 information - it may reveal more about your available packages than you would
664 like users to know in aggregate)
668 #XXX pare part_pkg fields down to a more secure subset
672 An array of hash references indicating information on unprovisioned services
673 available for provisioning for this specific cust_pkg. Each has the following
678 =item part_svc fields
680 All fields of part_svc (be careful with this information - it may reveal more
681 about your available packages than you would like users to know in aggregate)
685 #XXX pare part_svc fields down to a more secure subset
691 An array of hash references indicating information on the customer services
692 already provisioned for this specific cust_pkg. Each has the following keys:
698 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.
704 Primary key for this service
708 Service definition (see L<FS::part_svc>)
712 Customer package (see L<FS::cust_pkg>)
716 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
724 Returns service information for this customer.
726 Takes a hash reference as parameter with a single key: B<session_id>
728 Returns a hash reference containing customer package information. The hash reference contains the following keys:
738 An array of hash references indicating information on all of this customer's
739 services. Each has the following keys:
745 Primary key for this service
753 Meaningful user-specific identifier for the service (i.e. username, domain, or
758 Account (svc_acct) services also have the following keys:
776 Upload bytes remaining
780 Download bytes remaining
784 Total bytes remaining
786 =item recharge_amount
790 =item recharge_seconds
792 Number of seconds gained by recharge
794 =item recharge_upbytes
796 Number of upload bytes gained by recharge
798 =item recharge_downbytes
800 Number of download bytes gained by recharge
802 =item recharge_totalbytes
804 Number of total bytes gained by recharge
812 Orders a package for this customer.
814 Takes a hash reference as parameter with the following keys:
824 Package to order (see L<FS::part_pkg>).
828 Service to order (see L<FS::part_svc>).
830 Normally optional; required only to provision a non-svc_acct service, or if the
831 package definition does not contain one svc_acct service definition with
832 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
833 also be specified to indicate that no initial service should be provisioned.
837 Fields used when provisioning an svc_acct service:
851 Optional security phrase
855 Optional Access number number
859 Fields used when provisioning an svc_domain service:
869 Fields used when provisioning an svc_phone service:
887 Fields used when provisioning an svc_external service:
901 Fields used when provisioning an svc_pbx service:
915 Returns a hash reference with a single key, B<error>, empty on success, or an
916 error message on errors. The special error '_decline' is returned for
917 declined transactions.
921 Changes a package for this customer.
923 Takes a hash reference as parameter with the following keys:
933 Existing customer package.
937 New package to order (see L<FS::part_pkg>).
941 Returns a hash reference with the following keys:
947 Empty on success, or an error message on errors.
951 On success, the new pkgnum
958 Provides useful info for early renewals.
960 Takes a hash reference as parameter with the following keys:
970 Returns a hash reference. On errors, it contains a single key, B<error>, with
971 the error message. Otherwise, contains a single key, B<dates>, pointing to
972 an array refernce of hash references. Each hash reference contains the
979 (Future) Bill date. Indicates a future date for which billing could be run.
980 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
983 =item bill_date_pretty
985 (Future) Bill date as a human-readable string. (Convenience for display;
986 subject to change, so best not to parse for the date.)
990 Base amount which will be charged if renewed early as of this date.
994 Renewal date; i.e. even-futher future date at which the customer will be paid
995 through if the early renewal is completed with the given B<bill-date>.
996 Specified as a integer UNIX timestamp.
998 =item renew_date_pretty
1000 Renewal date as a human-readable string. (Convenience for display;
1001 subject to change, so best not to parse for the date.)
1005 Package that will be renewed.
1009 Expiration date of the package that will be renewed.
1011 =item expire_date_pretty
1013 Expiration date of the package that will be renewed, as a human-readable
1014 string. (Convenience for display; subject to change, so best not to parse for
1021 Renews this customer early; i.e. runs billing for this customer in advance.
1023 Takes a hash reference as parameter with the following keys:
1033 Integer date as returned by the B<renew_info> function, indicating the advance
1034 date for which to run billing.
1038 Returns a hash reference with a single key, B<error>, empty on success, or an
1039 error message on errors.
1043 Cancels a package for this customer.
1045 Takes a hash reference as parameter with the following keys:
1055 pkgpart of package to cancel
1059 Returns a hash reference with a single key, B<error>, empty on success, or an
1060 error message on errors.
1064 =head1 SIGNUP FUNCTIONS
1068 =item signup_info HASHREF
1070 Takes a hash reference as parameter with the following keys:
1074 =item session_id - Optional agent/reseller interface session
1078 Returns a hash reference containing information that may be useful in
1079 displaying a signup page. The hash reference contains the following keys:
1083 =item cust_main_county
1085 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.
1089 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
1090 an agentnum specified explicitly via reseller interface session_id in the
1095 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.
1097 =item agentnum2part_pkg
1099 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.
1103 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.
1105 =item security_phrase
1107 True if the "security_phrase" feature is enabled
1111 Array reference of acceptable payment types for signup
1117 credit card - automatic
1121 credit card - on-demand - version 1.5+ only
1125 electronic check - automatic
1129 electronic check - on-demand - version 1.5+ only
1137 billing, not recommended for signups
1141 free, definitely not recommended for signups
1145 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1151 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1155 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".
1161 =item countrydefault
1167 =item new_customer HASHREF
1169 Creates a new customer. Takes a hash reference as parameter with the
1176 first name (required)
1180 last name (required)
1184 (not typically collected; mostly used for ACH transactions)
1190 =item address1 (required)
1198 =item city (required)
1206 =item state (required)
1210 =item zip (required)
1216 Daytime phone number
1220 Evening phone number
1228 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1232 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1236 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1240 Expiration date for CARD/DCRD
1244 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1246 =item invoicing_list
1248 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),
1250 =item referral_custnum
1252 referring customer number
1260 pkgpart of initial package
1276 Access number (index, not the literal number)
1280 Country code (to be provisioned as a service)
1284 Phone number (to be provisioned as a service)
1292 Returns a hash reference with the following keys:
1298 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)
1302 =item regionselector HASHREF | LIST
1304 Takes as input a hashref or list of key/value pairs with the following keys:
1308 =item selected_county
1310 Currently selected county
1312 =item selected_state
1314 Currently selected state
1316 =item selected_country
1318 Currently selected country
1322 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1326 Specify a javascript subroutine to call on changes
1332 =item default_country
1338 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>.
1342 Returns a list consisting of three HTML fragments for county selection,
1343 state selection and country selection, respectively.
1347 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1348 sub regionselector {
1355 $param->{'selected_country'} ||= $param->{'default_country'};
1356 $param->{'selected_state'} ||= $param->{'default_state'};
1358 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1362 my %cust_main_county;
1364 # unless ( @cust_main_county ) { #cache
1365 #@cust_main_county = qsearch('cust_main_county', {} );
1366 #foreach my $c ( @cust_main_county ) {
1367 foreach my $c ( @{ $param->{'locales'} } ) {
1368 #$countyflag=1 if $c->county;
1369 $countyflag=1 if $c->{county};
1370 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1371 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1372 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1375 $countyflag=1 if $param->{selected_county};
1377 my $script_html = <<END;
1379 function opt(what,value,text) {
1380 var optionName = new Option(text, value, false, false);
1381 var length = what.length;
1382 what.options[length] = optionName;
1384 function ${prefix}country_changed(what) {
1385 country = what.options[what.selectedIndex].text;
1386 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1387 what.form.${prefix}state.options[i] = null;
1389 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1391 foreach my $country ( sort keys %cust_main_county ) {
1392 $script_html .= "\nif ( country == \"$country\" ) {\n";
1393 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1394 my $text = $state || '(n/a)';
1395 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1397 $script_html .= "}\n";
1400 $script_html .= <<END;
1402 function ${prefix}state_changed(what) {
1405 if ( $countyflag ) {
1406 $script_html .= <<END;
1407 state = what.options[what.selectedIndex].text;
1408 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1409 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1410 what.form.${prefix}county.options[i] = null;
1413 foreach my $country ( sort keys %cust_main_county ) {
1414 $script_html .= "\nif ( country == \"$country\" ) {\n";
1415 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1416 $script_html .= "\nif ( state == \"$state\" ) {\n";
1417 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1418 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1419 my $text = $county || '(n/a)';
1421 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1423 $script_html .= "}\n";
1425 $script_html .= "}\n";
1429 $script_html .= <<END;
1434 my $county_html = $script_html;
1435 if ( $countyflag ) {
1436 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1437 foreach my $county (
1438 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1440 my $text = $county || '(n/a)';
1441 $county_html .= qq!<OPTION VALUE="$county"!.
1442 ($county eq $param->{'selected_county'} ?
1449 $county_html .= '</SELECT>';
1452 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1455 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1456 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1457 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1458 my $text = $state || '(n/a)';
1459 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1460 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1462 $state_html .= '</SELECT>';
1464 my $country_html = '';
1465 if ( scalar( keys %cust_main_county ) > 1 ) {
1467 $country_html = qq(<SELECT NAME="${prefix}country" ).
1468 qq(onChange="${prefix}country_changed(this); ).
1469 $param->{'onchange'}.
1472 my $countrydefault = $param->{default_country} || 'US';
1473 foreach my $country (
1474 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1475 keys %cust_main_county
1477 my $selected = $country eq $param->{'selected_country'}
1480 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1482 $country_html .= '</SELECT>';
1485 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1486 ' VALUE="'. (keys %cust_main_county )[0]. '">';
1490 ($county_html, $state_html, $country_html);
1494 sub regionselector_hashref {
1495 my ($county_html, $state_html, $country_html) = regionselector(@_);
1497 'county_html' => $county_html,
1498 'state_html' => $state_html,
1499 'country_html' => $country_html,
1503 =item location_form HASHREF | LIST
1505 Takes as input a hashref or list of key/value pairs with the following keys:
1511 Current customer session_id
1515 Omit red asterisks from required fields.
1517 =item address1_label
1519 Label for first address line.
1523 Returns an HTML fragment for a location form (address, city, state, zip,
1536 my $session_id = delete $param->{'session_id'};
1538 my $rv = mason_comp( 'session_id' => $session_id,
1539 'comp' => '/elements/location.html',
1540 'args' => [ %$param ],
1544 $rv->{'error'} || $rv->{'output'};
1549 #=item expselect HASHREF | LIST
1551 #Takes as input a hashref or list of key/value pairs with the following keys:
1555 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1557 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1561 =item expselect PREFIX [ DATE ]
1563 Takes as input a unique prefix string and the current expiration date, in
1564 yyyy-mm-dd or m-d-yyyy format
1566 Returns an HTML fragments for expiration date selection.
1572 #if ( ref($_[0]) ) {
1576 #my $prefix = $param->{'prefix'};
1577 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1578 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
1580 my $date = scalar(@_) ? shift : '';
1582 my( $m, $y ) = ( 0, 0 );
1583 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1584 ( $m, $y ) = ( $2, $1 );
1585 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1586 ( $m, $y ) = ( $1, $3 );
1588 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1590 $return .= qq!<OPTION VALUE="$_"!;
1591 $return .= " SELECTED" if $_ == $m;
1594 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1596 my $thisYear = $t[5] + 1900;
1597 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1598 $return .= qq!<OPTION VALUE="$_"!;
1599 $return .= " SELECTED" if $_ == $y;
1602 $return .= "</SELECT>";
1607 =item popselector HASHREF | LIST
1609 Takes as input a hashref or list of key/value pairs with the following keys:
1615 Access number number
1619 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>.
1623 Returns an HTML fragment for access number selection.
1627 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1635 my $popnum = $param->{'popnum'};
1636 my $pops = $param->{'pops'};
1638 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1639 return $pops->[0]{city}. ', '. $pops->[0]{state}.
1640 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1641 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1642 if scalar(@$pops) == 1;
1645 my %popnum2pop = ();
1647 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1648 $popnum2pop{$_->{popnum}} = $_;
1653 function opt(what,href,text) {
1654 var optionName = new Option(text, href, false, false)
1655 var length = what.length;
1656 what.options[length] = optionName;
1660 my $init_popstate = $param->{'init_popstate'};
1661 if ( $init_popstate ) {
1662 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1663 $init_popstate. '">';
1666 function acstate_changed(what) {
1667 state = what.options[what.selectedIndex].text;
1668 what.form.popac.options.length = 0
1669 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1673 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1674 foreach my $state ( sort { $a cmp $b } @states ) {
1675 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1677 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1678 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1679 if ($ac eq $param->{'popac'}) {
1680 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1683 $text .= "}\n" unless $init_popstate;
1685 $text .= "popac_changed(what.form.popac)}\n";
1688 function popac_changed(what) {
1689 ac = what.options[what.selectedIndex].text;
1690 what.form.popnum.options.length = 0;
1691 what.form.popnum.options[0] = new Option("City", "-1", false, true);
1695 foreach my $state ( @states ) {
1696 foreach my $popac ( keys %{ $pop{$state} } ) {
1697 $text .= "\nif ( ac == \"$popac\" ) {\n";
1699 foreach my $pop ( @{$pop{$state}->{$popac}}) {
1700 my $o_popnum = $pop->{popnum};
1701 my $poptext = $pop->{city}. ', '. $pop->{state}.
1702 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1704 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1705 if ($popnum == $o_popnum) {
1706 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1714 $text .= "}\n</SCRIPT>\n";
1716 $param->{'acstate'} = '' unless defined($param->{'acstate'});
1719 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1720 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1721 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1722 ">$_" foreach sort { $a cmp $b } @states;
1723 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
1726 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1727 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1729 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1732 #comment this block to disable initial list polulation
1733 my @initial_select = ();
1734 if ( scalar( @$pops ) > 100 ) {
1735 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1737 @initial_select = @$pops;
1739 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1740 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1741 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1742 $pop->{city}. ', '. $pop->{state}.
1743 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1746 $text .= qq!</SELECT></TD></TR></TABLE>!;
1752 =item domainselector HASHREF | LIST
1754 Takes as input a hashref or list of key/value pairs with the following keys:
1764 Service number of the selected item.
1768 Returns an HTML fragment for domain selection.
1772 sub domainselector {
1779 my $domsvc= $param->{'domsvc'};
1781 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1782 my $domains = $rv->{'domains'};
1783 $domsvc = $rv->{'domsvc'} unless $domsvc;
1785 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1786 unless scalar(keys %$domains);
1788 if (scalar(keys %$domains) == 1) {
1790 foreach(keys %$domains) {
1793 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1794 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1797 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1800 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1801 $text .= qq!<OPTION VALUE="!. $domain. '"'.
1802 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1803 $domains->{$domain};
1806 $text .= qq!</SELECT></TD></TR>!;
1812 =item didselector HASHREF | LIST
1814 Takes as input a hashref or list of key/value pairs with the following keys:
1820 Field name for the returned HTML fragment.
1824 Service definition (see L<FS::part_svc>)
1828 Returns an HTML fragment for DID selection.
1840 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1841 'args'=>[ %$param ],
1845 $rv->{'error'} || $rv->{'output'};
1851 =head1 RESELLER FUNCTIONS
1853 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1854 with their active session, and the B<customer_info> and B<order_pkg> functions
1855 with their active session and an additional I<custnum> parameter.
1857 For the most part, development of the reseller web interface has been
1858 superceded by agent-virtualized access to the backend.
1870 =item agent_list_customers
1872 List agent's customers.
1880 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>