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' => 'MyAccount/login',
29 'logout' => 'MyAccount/logout',
30 'customer_info' => 'MyAccount/customer_info',
31 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
32 'invoice' => 'MyAccount/invoice',
33 'invoice_logo' => 'MyAccount/invoice_logo',
34 'list_invoices' => 'MyAccount/list_invoices', #?
35 'cancel' => 'MyAccount/cancel', #add to ss cgi!
36 'payment_info' => 'MyAccount/payment_info',
37 'process_payment' => 'MyAccount/process_payment',
38 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
39 'process_prepay' => 'MyAccount/process_prepay',
40 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
41 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
42 'list_svc_usage' => 'MyAccount/list_svc_usage',
43 'list_support_usage' => 'MyAccount/list_support_usage',
44 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
45 'change_pkg' => 'MyAccount/change_pkg',
46 'order_recharge' => 'MyAccount/order_recharge',
47 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
48 'charge' => 'MyAccount/charge', #?
49 'part_svc_info' => 'MyAccount/part_svc_info',
50 'provision_acct' => 'MyAccount/provision_acct',
51 'provision_external' => 'MyAccount/provision_external',
52 'unprovision_svc' => 'MyAccount/unprovision_svc',
53 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
54 'signup_info' => 'Signup/signup_info',
55 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
56 'new_customer' => 'Signup/new_customer',
57 'agent_login' => 'Agent/agent_login',
58 'agent_logout' => 'Agent/agent_logout',
59 'agent_info' => 'Agent/agent_info',
60 'agent_list_customers' => 'Agent/agent_list_customers',
61 'mason_comp' => 'MasonComponent/mason_comp',
62 'call_time' => 'PrepaidPhone/call_time',
63 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
64 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
66 @EXPORT_OK = ( keys(%autoload), qw( regionselector expselect popselector domainselector didselector) );
68 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
69 $ENV{'SHELL'} = '/bin/sh';
70 $ENV{'IFS'} = " \t\n";
73 $ENV{'BASH_ENV'} = '';
75 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
76 #if you grant appropriate permissions to whatever user
77 my $freeside_uid = scalar(getpwnam('freeside'));
78 die "not running as the freeside user\n"
79 if $> != $freeside_uid && ! $skip_uid_check;
81 -e $dir or die "FATAL: $dir doesn't exist!";
82 -d $dir or die "FATAL: $dir isn't a directory!";
83 -r $dir or die "FATAL: Can't read $dir as freeside user!";
84 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
86 foreach my $autoload ( keys %autoload ) {
94 #warn scalar(@_). ": ". join(" / ", @_);
98 $param->{_packet} = \''. $autoload{$autoload}. '\';
100 simple_packet($param);
110 warn "sending ". $packet->{_packet}. " to server"
112 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
113 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
114 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
117 #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
119 #block until there is a message on socket
120 # my $w = new IO::Select;
122 # my @wait = $w->can_read;
124 warn "reading message from server"
127 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
128 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
130 warn "returning message to client"
138 FS::SelfService - Freeside self-service API
142 # password and shell account changes
143 use FS::SelfService qw(passwd chfn chsh);
145 # "my account" functionality
146 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
148 my $rv = login( { 'username' => $username,
150 'password' => $password,
154 if ( $rv->{'error'} ) {
155 #handle login error...
158 my $session_id = $rv->{'session_id'};
161 my $customer_info = customer_info( { 'session_id' => $session_id } );
163 #payment_info and process_payment are available in 1.5+ only
164 my $payment_info = payment_info( { 'session_id' => $session_id } );
166 #!!! process_payment example
168 #!!! list_pkgs example
170 #!!! order_pkg example
172 #!!! cancel_pkg example
174 # signup functionality
175 use FS::SelfService qw( signup_info new_customer );
177 my $signup_info = signup_info;
179 $rv = new_customer( {
182 'company' => $company,
183 'address1' => $address1,
184 'address2' => $address2,
188 'country' => $country,
189 'daytime' => $daytime,
193 'payinfo' => $payinfo,
195 'paystart_month' => $paystart_month
196 'paystart_year' => $paystart_year,
197 'payissue' => $payissue,
199 'paydate' => $paydate,
200 'payname' => $payname,
201 'invoicing_list' => $invoicing_list,
202 'referral_custnum' => $referral_custnum,
203 'agentnum' => $agentnum,
204 'pkgpart' => $pkgpart,
206 'username' => $username,
207 '_password' => $password,
211 'phonenum' => $phonenum,
216 my $error = $rv->{'error'};
217 if ( $error eq '_decline' ) {
227 Use this API to implement your own client "self-service" module.
229 If you just want to customize the look of the existing "self-service" module,
232 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
244 =head1 "MY ACCOUNT" FUNCTIONS
250 Creates a user session. Takes a hash reference as parameter with the
269 Returns a hash reference with the following keys:
275 Empty on success, or an error message on errors.
279 Session identifier for successful logins
283 =item customer_info HASHREF
285 Returns general customer information.
287 Takes a hash reference as parameter with a single key: B<session_id>
289 Returns a hash reference with the following keys:
303 Array reference of hash references of open inoices. Each hash reference has
304 the following keys: invnum, date, owed
308 An HTML fragment containing shipping and billing addresses.
310 =item The following fields are also returned
312 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
316 =item edit_info HASHREF
318 Takes a hash reference as parameter with any of the following keys:
320 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
322 If a field exists, the customer record is updated with the new value of that
323 field. If a field does not exist, that field is not changed on the customer
326 Returns a hash reference with a single key, B<error>, empty on success, or an
327 error message on errors
329 =item invoice HASHREF
331 Returns an invoice. Takes a hash reference as parameter with two keys:
332 session_id and invnum
334 Returns a hash reference with the following keys:
340 Empty on success, or an error message on errors
352 =item list_invoices HASHREF
354 Returns a list of all customer invoices. Takes a hash references with a single
357 Returns a hash reference with the following keys:
363 Empty on success, or an error message on errors
367 Reference to array of hash references with the following keys:
377 Invoice date, in UNIX epoch time
385 Cancels this customer.
387 Takes a hash reference as parameter with a single key: B<session_id>
389 Returns a hash reference with a single key, B<error>, which is empty on
390 success or an error message on errors.
392 =item payment_info HASHREF
394 Returns information that may be useful in displaying a payment page.
396 Takes a hash reference as parameter with a single key: B<session_id>.
398 Returns a hash reference with the following keys:
404 Empty on success, or an error message on errors
412 Exact name on credit card (CARD/DCRD)
436 Customer's current default payment type.
440 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
444 For CARD/DCRD payment types, the card number
448 For CARD/DCRD payment types, expiration month
452 For CARD/DCRD payment types, expiration year
454 =item cust_main_county
456 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.
460 Array reference of all states in the current default country.
464 Hash reference of card types; keys are card types, values are the exact strings
465 passed to the process_payment function
469 Unique transaction identifier (prevents multiple charges), passed to the
470 process_payment function
474 =item process_payment HASHREF
476 Processes a payment and possible change of address or payment type. Takes a
477 hash reference as parameter with the following keys:
491 If true, address and card information entered will be saved for subsequent
496 If true, future credit card payments will be done automatically (sets payby to
497 CARD). If false, future credit card payments will be done on-demand (sets
498 payby to DCRD). This option only has meaning if B<save> is set true.
530 Card expiration month
538 Unique transaction identifier, returned from the payment_info function.
539 Prevents multiple charges.
543 Returns a hash reference with a single key, B<error>, empty on success, or an
544 error message on errors
548 Returns package information for this customer. For more detail on services,
551 Takes a hash reference as parameter with a single key: B<session_id>
553 Returns a hash reference containing customer package information. The hash reference contains the following keys:
561 =item cust_pkg HASHREF
563 Array reference of hash references, each of which has the fields of a cust_pkg
564 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
565 the internal FS:: objects, but hash references of columns and values.
569 =item part_pkg fields
571 All fields of part_pkg for this specific cust_pkg (be careful with this
572 information - it may reveal more about your available packages than you would
573 like users to know in aggregate)
577 #XXX pare part_pkg fields down to a more secure subset
581 An array of hash references indicating information on unprovisioned services
582 available for provisioning for this specific cust_pkg. Each has the following
587 =item part_svc fields
589 All fields of part_svc (be careful with this information - it may reveal more
590 about your available packages than you would like users to know in aggregate)
594 #XXX pare part_svc fields down to a more secure subset
600 An array of hash references indicating information on the customer services
601 already provisioned for this specific cust_pkg. Each has the following keys:
607 Array reference with three elements:
617 Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)
621 Table name of this service
627 Primary key for this service
631 Service definition (part_pkg)
635 Customer package (cust_pkg)
639 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
647 Empty on success, or an error message on errors.
653 Returns service information for this customer.
655 Takes a hash reference as parameter with a single key: B<session_id>
657 Returns a hash reference containing customer package information. The hash reference contains the following keys:
667 An array of hash references indicating information on all of this customer's
668 services. Each has the following keys:
674 Primary key for this service
682 Meaningful user-specific identifier for the service (i.e. username, domain, or
687 Account (svc_acct) services also have the following keys:
703 Upload bytes remaining
707 Download bytes remaining
711 Total bytes remaining
713 =item recharge_amount
717 =item recharge_seconds
719 Number of seconds gained by recharge
721 =item recharge_upbytes
723 Number of upload bytes gained by recharge
725 =item recharge_downbytes
727 Number of download bytes gained by recharge
729 =item recharge_totalbytes
731 Number of total bytes gained by recharge
739 Orders a package for this customer.
741 Takes a hash reference as parameter with the following keys:
751 pkgpart of package to order
755 optional svcpart, required only if the package definition does not contain
756 one svc_acct service definition with quantity 1 (it may contain others with
769 Optional security phrase
773 Optional Access number number
777 Returns a hash reference with a single key, B<error>, empty on success, or an
778 error message on errors. The special error '_decline' is returned for
779 declined transactions.
783 Cancels a package for this customer.
785 Takes a hash reference as parameter with the following keys:
795 pkgpart of package to cancel
799 Returns a hash reference with a single key, B<error>, empty on success, or an
800 error message on errors.
804 =head1 SIGNUP FUNCTIONS
808 =item signup_info HASHREF
810 Takes a hash reference as parameter with the following keys:
814 =item session_id - Optional agent/reseller interface session
818 Returns a hash reference containing information that may be useful in
819 displaying a signup page. The hash reference contains the following keys:
823 =item cust_main_county
825 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.
829 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
830 an agentnum specified explicitly via reseller interface session_id in the
835 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.
837 =item agentnum2part_pkg
839 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.
843 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.
845 =item security_phrase
847 True if the "security_phrase" feature is enabled
851 Array reference of acceptable payment types for signup
857 credit card - automatic
861 credit card - on-demand - version 1.5+ only
865 electronic check - automatic
869 electronic check - on-demand - version 1.5+ only
877 billing, not recommended for signups
881 free, definitely not recommended for signups
885 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
891 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
895 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".
907 =item new_customer HASHREF
909 Creates a new customer. Takes a hash reference as parameter with the
916 first name (required)
924 (not typically collected; mostly used for ACH transactions)
930 =item address1 (required)
938 =item city (required)
946 =item state (required)
968 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
972 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
976 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
980 Expiration date for CARD/DCRD
984 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
988 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),
990 =item referral_custnum
992 referring customer number
1000 pkgpart of initial package
1016 Access number (index, not the literal number)
1020 Country code (to be provisioned as a service)
1024 Phone number (to be provisioned as a service)
1032 Returns a hash reference with the following keys:
1038 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)
1042 =item regionselector HASHREF | LIST
1044 Takes as input a hashref or list of key/value pairs with the following keys:
1048 =item selected_county
1050 Currently selected county
1052 =item selected_state
1054 Currently selected state
1056 =item selected_country
1058 Currently selected country
1062 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1066 Specify a javascript subroutine to call on changes
1072 =item default_country
1078 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>.
1082 Returns a list consisting of three HTML fragments for county selection,
1083 state selection and country selection, respectively.
1087 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1088 sub regionselector {
1095 $param->{'selected_country'} ||= $param->{'default_country'};
1096 $param->{'selected_state'} ||= $param->{'default_state'};
1098 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1102 my %cust_main_county;
1104 # unless ( @cust_main_county ) { #cache
1105 #@cust_main_county = qsearch('cust_main_county', {} );
1106 #foreach my $c ( @cust_main_county ) {
1107 foreach my $c ( @{ $param->{'locales'} } ) {
1108 #$countyflag=1 if $c->county;
1109 $countyflag=1 if $c->{county};
1110 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1111 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1112 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1115 $countyflag=1 if $param->{selected_county};
1117 my $script_html = <<END;
1119 function opt(what,value,text) {
1120 var optionName = new Option(text, value, false, false);
1121 var length = what.length;
1122 what.options[length] = optionName;
1124 function ${prefix}country_changed(what) {
1125 country = what.options[what.selectedIndex].text;
1126 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1127 what.form.${prefix}state.options[i] = null;
1129 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1131 foreach my $country ( sort keys %cust_main_county ) {
1132 $script_html .= "\nif ( country == \"$country\" ) {\n";
1133 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1134 my $text = $state || '(n/a)';
1135 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1137 $script_html .= "}\n";
1140 $script_html .= <<END;
1142 function ${prefix}state_changed(what) {
1145 if ( $countyflag ) {
1146 $script_html .= <<END;
1147 state = what.options[what.selectedIndex].text;
1148 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1149 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1150 what.form.${prefix}county.options[i] = null;
1153 foreach my $country ( sort keys %cust_main_county ) {
1154 $script_html .= "\nif ( country == \"$country\" ) {\n";
1155 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1156 $script_html .= "\nif ( state == \"$state\" ) {\n";
1157 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1158 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1159 my $text = $county || '(n/a)';
1161 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1163 $script_html .= "}\n";
1165 $script_html .= "}\n";
1169 $script_html .= <<END;
1174 my $county_html = $script_html;
1175 if ( $countyflag ) {
1176 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1177 $county_html .= '</SELECT>';
1180 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1183 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1184 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1185 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1186 my $text = $state || '(n/a)';
1187 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1188 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1190 $state_html .= '</SELECT>';
1192 $state_html .= '</SELECT>';
1194 my $country_html = qq!<SELECT NAME="${prefix}country" !.
1195 qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
1196 my $countrydefault = $param->{default_country} || 'US';
1197 foreach my $country (
1198 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1199 keys %cust_main_county
1201 my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
1202 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1204 $country_html .= '</SELECT>';
1206 ($county_html, $state_html, $country_html);
1210 #=item expselect HASHREF | LIST
1212 #Takes as input a hashref or list of key/value pairs with the following keys:
1216 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1218 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1222 =item expselect PREFIX [ DATE ]
1224 Takes as input a unique prefix string and the current expiration date, in
1225 yyyy-mm-dd or m-d-yyyy format
1227 Returns an HTML fragments for expiration date selection.
1233 #if ( ref($_[0]) ) {
1237 #my $prefix = $param->{'prefix'};
1238 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1239 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
1241 my $date = scalar(@_) ? shift : '';
1243 my( $m, $y ) = ( 0, 0 );
1244 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1245 ( $m, $y ) = ( $2, $1 );
1246 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1247 ( $m, $y ) = ( $1, $3 );
1249 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1251 $return .= qq!<OPTION VALUE="$_"!;
1252 $return .= " SELECTED" if $_ == $m;
1255 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1257 my $thisYear = $t[5] + 1900;
1258 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1259 $return .= qq!<OPTION VALUE="$_"!;
1260 $return .= " SELECTED" if $_ == $y;
1263 $return .= "</SELECT>";
1268 =item popselector HASHREF | LIST
1270 Takes as input a hashref or list of key/value pairs with the following keys:
1276 Access number number
1280 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>.
1284 Returns an HTML fragment for access number selection.
1288 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1296 my $popnum = $param->{'popnum'};
1297 my $pops = $param->{'pops'};
1299 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1300 return $pops->[0]{city}. ', '. $pops->[0]{state}.
1301 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1302 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1303 if scalar(@$pops) == 1;
1306 my %popnum2pop = ();
1308 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1309 $popnum2pop{$_->{popnum}} = $_;
1314 function opt(what,href,text) {
1315 var optionName = new Option(text, href, false, false)
1316 var length = what.length;
1317 what.options[length] = optionName;
1321 my $init_popstate = $param->{'init_popstate'};
1322 if ( $init_popstate ) {
1323 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1324 $init_popstate. '">';
1327 function acstate_changed(what) {
1328 state = what.options[what.selectedIndex].text;
1329 what.form.popac.options.length = 0
1330 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1334 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1335 foreach my $state ( sort { $a cmp $b } @states ) {
1336 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1338 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1339 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1340 if ($ac eq $param->{'popac'}) {
1341 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1344 $text .= "}\n" unless $init_popstate;
1346 $text .= "popac_changed(what.form.popac)}\n";
1349 function popac_changed(what) {
1350 ac = what.options[what.selectedIndex].text;
1351 what.form.popnum.options.length = 0;
1352 what.form.popnum.options[0] = new Option("City", "-1", false, true);
1356 foreach my $state ( @states ) {
1357 foreach my $popac ( keys %{ $pop{$state} } ) {
1358 $text .= "\nif ( ac == \"$popac\" ) {\n";
1360 foreach my $pop ( @{$pop{$state}->{$popac}}) {
1361 my $o_popnum = $pop->{popnum};
1362 my $poptext = $pop->{city}. ', '. $pop->{state}.
1363 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1365 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1366 if ($popnum == $o_popnum) {
1367 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1375 $text .= "}\n</SCRIPT>\n";
1378 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1379 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1380 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1381 ">$_" foreach sort { $a cmp $b } @states;
1382 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
1385 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1386 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1388 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1391 #comment this block to disable initial list polulation
1392 my @initial_select = ();
1393 if ( scalar( @$pops ) > 100 ) {
1394 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1396 @initial_select = @$pops;
1398 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1399 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1400 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1401 $pop->{city}. ', '. $pop->{state}.
1402 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1405 $text .= qq!</SELECT></TD></TR></TABLE>!;
1411 =item domainselector HASHREF | LIST
1413 Takes as input a hashref or list of key/value pairs with the following keys:
1423 Service number of the selected item.
1427 Returns an HTML fragment for domain selection.
1431 sub domainselector {
1438 my $domsvc= $param->{'domsvc'};
1440 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1441 my $domains = $rv->{'domains'};
1442 $domsvc = $rv->{'domsvc'} unless $domsvc;
1444 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1445 unless scalar(keys %$domains);
1447 if (scalar(keys %$domains) == 1) {
1449 foreach(keys %$domains) {
1452 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1453 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1456 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1459 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1460 $text .= qq!<OPTION VALUE="!. $domain. '"'.
1461 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1462 $domains->{$domain};
1465 $text .= qq!</SELECT></TD></TR>!;
1471 =item didselector HASHREF | LIST
1473 Takes as input a hashref or list of key/value pairs with the following keys:
1483 Returns an HTML fragment for DID selection.
1495 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1496 'args'=>[ %$param ],
1500 $rv->{'error'} || $rv->{'output'};
1506 =head1 RESELLER FUNCTIONS
1508 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1509 with their active session, and the B<customer_info> and B<order_pkg> functions
1510 with their active session and an additional I<custnum> parameter.
1518 =item agent_list_customers
1526 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>