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 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
20 #if you grant appropriate permissions to whatever user
23 $dir = "/usr/local/freeside";
24 $socket = "$dir/selfservice_socket";
25 $socket .= '.'.$tag if defined $tag && length($tag);
27 #maybe should ask ClientAPI for this list
29 'passwd' => 'passwd/passwd',
30 'chfn' => 'passwd/passwd',
31 'chsh' => 'passwd/passwd',
32 'login' => 'MyAccount/login',
33 'logout' => 'MyAccount/logout',
34 'customer_info' => 'MyAccount/customer_info',
35 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
36 'invoice' => 'MyAccount/invoice',
37 'invoice_logo' => 'MyAccount/invoice_logo',
38 'list_invoices' => 'MyAccount/list_invoices', #?
39 'cancel' => 'MyAccount/cancel', #add to ss cgi!
40 'payment_info' => 'MyAccount/payment_info',
41 'process_payment' => 'MyAccount/process_payment',
42 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
43 'process_prepay' => 'MyAccount/process_prepay',
44 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
45 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
46 'list_svc_usage' => 'MyAccount/list_svc_usage',
47 'list_support_usage' => 'MyAccount/list_support_usage',
48 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
49 'change_pkg' => 'MyAccount/change_pkg',
50 'order_recharge' => 'MyAccount/order_recharge',
51 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
52 'charge' => 'MyAccount/charge', #?
53 'part_svc_info' => 'MyAccount/part_svc_info',
54 'provision_acct' => 'MyAccount/provision_acct',
55 'provision_external' => 'MyAccount/provision_external',
56 'unprovision_svc' => 'MyAccount/unprovision_svc',
57 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
58 'signup_info' => 'Signup/signup_info',
59 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
60 'new_customer' => 'Signup/new_customer',
61 'agent_login' => 'Agent/agent_login',
62 'agent_logout' => 'Agent/agent_logout',
63 'agent_info' => 'Agent/agent_info',
64 'agent_list_customers' => 'Agent/agent_list_customers',
65 'mason_comp' => 'MasonComponent/mason_comp',
66 'call_time' => 'PrepaidPhone/call_time',
67 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
68 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
70 @EXPORT_OK = ( keys(%autoload), qw( regionselector expselect popselector domainselector didselector) );
72 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
73 $ENV{'SHELL'} = '/bin/sh';
74 $ENV{'IFS'} = " \t\n";
77 $ENV{'BASH_ENV'} = '';
79 my $freeside_uid = scalar(getpwnam('freeside'));
80 die "not running as the freeside user\n"
81 if $> != $freeside_uid && ! $skip_uid_check;
83 -e $dir or die "FATAL: $dir doesn't exist!";
84 -d $dir or die "FATAL: $dir isn't a directory!";
85 -r $dir or die "FATAL: Can't read $dir as freeside user!";
86 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
88 foreach my $autoload ( keys %autoload ) {
96 #warn scalar(@_). ": ". join(" / ", @_);
100 $param->{_packet} = \''. $autoload{$autoload}. '\';
102 simple_packet($param);
112 warn "sending ". $packet->{_packet}. " to server"
114 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
115 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
116 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
119 #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
121 #block until there is a message on socket
122 # my $w = new IO::Select;
124 # my @wait = $w->can_read;
126 warn "reading message from server"
129 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
130 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
132 warn "returning message to client"
140 FS::SelfService - Freeside self-service API
144 # password and shell account changes
145 use FS::SelfService qw(passwd chfn chsh);
147 # "my account" functionality
148 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
150 my $rv = login( { 'username' => $username,
152 'password' => $password,
156 if ( $rv->{'error'} ) {
157 #handle login error...
160 my $session_id = $rv->{'session_id'};
163 my $customer_info = customer_info( { 'session_id' => $session_id } );
165 #payment_info and process_payment are available in 1.5+ only
166 my $payment_info = payment_info( { 'session_id' => $session_id } );
168 #!!! process_payment example
170 #!!! list_pkgs example
172 #!!! order_pkg example
174 #!!! cancel_pkg example
176 # signup functionality
177 use FS::SelfService qw( signup_info new_customer );
179 my $signup_info = signup_info;
181 $rv = new_customer( {
184 'company' => $company,
185 'address1' => $address1,
186 'address2' => $address2,
190 'country' => $country,
191 'daytime' => $daytime,
195 'payinfo' => $payinfo,
197 'paystart_month' => $paystart_month
198 'paystart_year' => $paystart_year,
199 'payissue' => $payissue,
201 'paydate' => $paydate,
202 'payname' => $payname,
203 'invoicing_list' => $invoicing_list,
204 'referral_custnum' => $referral_custnum,
205 'agentnum' => $agentnum,
206 'pkgpart' => $pkgpart,
208 'username' => $username,
209 '_password' => $password,
213 'phonenum' => $phonenum,
218 my $error = $rv->{'error'};
219 if ( $error eq '_decline' ) {
229 Use this API to implement your own client "self-service" module.
231 If you just want to customize the look of the existing "self-service" module,
234 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
246 =head1 "MY ACCOUNT" FUNCTIONS
252 Creates a user session. Takes a hash reference as parameter with the
271 Returns a hash reference with the following keys:
277 Empty on success, or an error message on errors.
281 Session identifier for successful logins
285 =item customer_info HASHREF
287 Returns general customer information.
289 Takes a hash reference as parameter with a single key: B<session_id>
291 Returns a hash reference with the following keys:
305 Array reference of hash references of open inoices. Each hash reference has
306 the following keys: invnum, date, owed
310 An HTML fragment containing shipping and billing addresses.
312 =item The following fields are also returned
314 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
318 =item edit_info HASHREF
320 Takes a hash reference as parameter with any of the following keys:
322 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
324 If a field exists, the customer record is updated with the new value of that
325 field. If a field does not exist, that field is not changed on the customer
328 Returns a hash reference with a single key, B<error>, empty on success, or an
329 error message on errors
331 =item invoice HASHREF
333 Returns an invoice. Takes a hash reference as parameter with two keys:
334 session_id and invnum
336 Returns a hash reference with the following keys:
342 Empty on success, or an error message on errors
354 =item list_invoices HASHREF
356 Returns a list of all customer invoices. Takes a hash references with a single
359 Returns a hash reference with the following keys:
365 Empty on success, or an error message on errors
369 Reference to array of hash references with the following keys:
379 Invoice date, in UNIX epoch time
387 Cancels this customer.
389 Takes a hash reference as parameter with a single key: B<session_id>
391 Returns a hash reference with a single key, B<error>, which is empty on
392 success or an error message on errors.
394 =item payment_info HASHREF
396 Returns information that may be useful in displaying a payment page.
398 Takes a hash reference as parameter with a single key: B<session_id>.
400 Returns a hash reference with the following keys:
406 Empty on success, or an error message on errors
414 Exact name on credit card (CARD/DCRD)
438 Customer's current default payment type.
442 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
446 For CARD/DCRD payment types, the card number
450 For CARD/DCRD payment types, expiration month
454 For CARD/DCRD payment types, expiration year
456 =item cust_main_county
458 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.
462 Array reference of all states in the current default country.
466 Hash reference of card types; keys are card types, values are the exact strings
467 passed to the process_payment function
471 Unique transaction identifier (prevents multiple charges), passed to the
472 process_payment function
476 =item process_payment HASHREF
478 Processes a payment and possible change of address or payment type. Takes a
479 hash reference as parameter with the following keys:
493 If true, address and card information entered will be saved for subsequent
498 If true, future credit card payments will be done automatically (sets payby to
499 CARD). If false, future credit card payments will be done on-demand (sets
500 payby to DCRD). This option only has meaning if B<save> is set true.
532 Card expiration month
540 Unique transaction identifier, returned from the payment_info function.
541 Prevents multiple charges.
545 Returns a hash reference with a single key, B<error>, empty on success, or an
546 error message on errors
550 Returns package information for this customer. For more detail on services,
553 Takes a hash reference as parameter with a single key: B<session_id>
555 Returns a hash reference containing customer package information. The hash reference contains the following keys:
563 =item cust_pkg HASHREF
565 Array reference of hash references, each of which has the fields of a cust_pkg
566 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
567 the internal FS:: objects, but hash references of columns and values.
571 =item part_pkg fields
573 All fields of part_pkg for this specific cust_pkg (be careful with this
574 information - it may reveal more about your available packages than you would
575 like users to know in aggregate)
579 #XXX pare part_pkg fields down to a more secure subset
583 An array of hash references indicating information on unprovisioned services
584 available for provisioning for this specific cust_pkg. Each has the following
589 =item part_svc fields
591 All fields of part_svc (be careful with this information - it may reveal more
592 about your available packages than you would like users to know in aggregate)
596 #XXX pare part_svc fields down to a more secure subset
602 An array of hash references indicating information on the customer services
603 already provisioned for this specific cust_pkg. Each has the following keys:
609 Array reference with three elements:
619 Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)
623 Table name of this service
629 Primary key for this service
633 Service definition (part_pkg)
637 Customer package (cust_pkg)
641 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
649 Empty on success, or an error message on errors.
655 Returns service information for this customer.
657 Takes a hash reference as parameter with a single key: B<session_id>
659 Returns a hash reference containing customer package information. The hash reference contains the following keys:
669 An array of hash references indicating information on all of this customer's
670 services. Each has the following keys:
676 Primary key for this service
684 Meaningful user-specific identifier for the service (i.e. username, domain, or
689 Account (svc_acct) services also have the following keys:
705 Upload bytes remaining
709 Download bytes remaining
713 Total bytes remaining
715 =item recharge_amount
719 =item recharge_seconds
721 Number of seconds gained by recharge
723 =item recharge_upbytes
725 Number of upload bytes gained by recharge
727 =item recharge_downbytes
729 Number of download bytes gained by recharge
731 =item recharge_totalbytes
733 Number of total bytes gained by recharge
741 Orders a package for this customer.
743 Takes a hash reference as parameter with the following keys:
753 pkgpart of package to order
757 optional svcpart, required only if the package definition does not contain
758 one svc_acct service definition with quantity 1 (it may contain others with
771 Optional security phrase
775 Optional Access number number
779 Returns a hash reference with a single key, B<error>, empty on success, or an
780 error message on errors. The special error '_decline' is returned for
781 declined transactions.
785 Cancels a package for this customer.
787 Takes a hash reference as parameter with the following keys:
797 pkgpart of package to cancel
801 Returns a hash reference with a single key, B<error>, empty on success, or an
802 error message on errors.
806 =head1 SIGNUP FUNCTIONS
810 =item signup_info HASHREF
812 Takes a hash reference as parameter with the following keys:
816 =item session_id - Optional agent/reseller interface session
820 Returns a hash reference containing information that may be useful in
821 displaying a signup page. The hash reference contains the following keys:
825 =item cust_main_county
827 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.
831 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
832 an agentnum specified explicitly via reseller interface session_id in the
837 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.
839 =item agentnum2part_pkg
841 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.
845 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.
847 =item security_phrase
849 True if the "security_phrase" feature is enabled
853 Array reference of acceptable payment types for signup
859 credit card - automatic
863 credit card - on-demand - version 1.5+ only
867 electronic check - automatic
871 electronic check - on-demand - version 1.5+ only
879 billing, not recommended for signups
883 free, definitely not recommended for signups
887 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
893 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
897 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".
909 =item new_customer HASHREF
911 Creates a new customer. Takes a hash reference as parameter with the
918 first name (required)
926 (not typically collected; mostly used for ACH transactions)
932 =item address1 (required)
940 =item city (required)
948 =item state (required)
970 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
974 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
978 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
982 Expiration date for CARD/DCRD
986 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
990 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),
992 =item referral_custnum
994 referring customer number
1002 pkgpart of initial package
1018 Access number (index, not the literal number)
1022 Country code (to be provisioned as a service)
1026 Phone number (to be provisioned as a service)
1034 Returns a hash reference with the following keys:
1040 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)
1044 =item regionselector HASHREF | LIST
1046 Takes as input a hashref or list of key/value pairs with the following keys:
1050 =item selected_county
1052 Currently selected county
1054 =item selected_state
1056 Currently selected state
1058 =item selected_country
1060 Currently selected country
1064 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1068 Specify a javascript subroutine to call on changes
1074 =item default_country
1080 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>.
1084 Returns a list consisting of three HTML fragments for county selection,
1085 state selection and country selection, respectively.
1089 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1090 sub regionselector {
1097 $param->{'selected_country'} ||= $param->{'default_country'};
1098 $param->{'selected_state'} ||= $param->{'default_state'};
1100 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1104 my %cust_main_county;
1106 # unless ( @cust_main_county ) { #cache
1107 #@cust_main_county = qsearch('cust_main_county', {} );
1108 #foreach my $c ( @cust_main_county ) {
1109 foreach my $c ( @{ $param->{'locales'} } ) {
1110 #$countyflag=1 if $c->county;
1111 $countyflag=1 if $c->{county};
1112 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1113 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1114 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1117 $countyflag=1 if $param->{selected_county};
1119 my $script_html = <<END;
1121 function opt(what,value,text) {
1122 var optionName = new Option(text, value, false, false);
1123 var length = what.length;
1124 what.options[length] = optionName;
1126 function ${prefix}country_changed(what) {
1127 country = what.options[what.selectedIndex].text;
1128 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1129 what.form.${prefix}state.options[i] = null;
1131 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1133 foreach my $country ( sort keys %cust_main_county ) {
1134 $script_html .= "\nif ( country == \"$country\" ) {\n";
1135 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1136 my $text = $state || '(n/a)';
1137 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1139 $script_html .= "}\n";
1142 $script_html .= <<END;
1144 function ${prefix}state_changed(what) {
1147 if ( $countyflag ) {
1148 $script_html .= <<END;
1149 state = what.options[what.selectedIndex].text;
1150 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1151 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1152 what.form.${prefix}county.options[i] = null;
1155 foreach my $country ( sort keys %cust_main_county ) {
1156 $script_html .= "\nif ( country == \"$country\" ) {\n";
1157 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1158 $script_html .= "\nif ( state == \"$state\" ) {\n";
1159 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1160 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1161 my $text = $county || '(n/a)';
1163 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1165 $script_html .= "}\n";
1167 $script_html .= "}\n";
1171 $script_html .= <<END;
1176 my $county_html = $script_html;
1177 if ( $countyflag ) {
1178 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1179 $county_html .= '</SELECT>';
1182 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1185 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1186 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1187 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1188 my $text = $state || '(n/a)';
1189 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1190 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1192 $state_html .= '</SELECT>';
1194 $state_html .= '</SELECT>';
1196 my $country_html = qq!<SELECT NAME="${prefix}country" !.
1197 qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
1198 my $countrydefault = $param->{default_country} || 'US';
1199 foreach my $country (
1200 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1201 keys %cust_main_county
1203 my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
1204 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1206 $country_html .= '</SELECT>';
1208 ($county_html, $state_html, $country_html);
1212 #=item expselect HASHREF | LIST
1214 #Takes as input a hashref or list of key/value pairs with the following keys:
1218 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1220 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1224 =item expselect PREFIX [ DATE ]
1226 Takes as input a unique prefix string and the current expiration date, in
1227 yyyy-mm-dd or m-d-yyyy format
1229 Returns an HTML fragments for expiration date selection.
1235 #if ( ref($_[0]) ) {
1239 #my $prefix = $param->{'prefix'};
1240 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1241 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
1243 my $date = scalar(@_) ? shift : '';
1245 my( $m, $y ) = ( 0, 0 );
1246 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1247 ( $m, $y ) = ( $2, $1 );
1248 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1249 ( $m, $y ) = ( $1, $3 );
1251 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1253 $return .= qq!<OPTION VALUE="$_"!;
1254 $return .= " SELECTED" if $_ == $m;
1257 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1259 my $thisYear = $t[5] + 1900;
1260 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1261 $return .= qq!<OPTION VALUE="$_"!;
1262 $return .= " SELECTED" if $_ == $y;
1265 $return .= "</SELECT>";
1270 =item popselector HASHREF | LIST
1272 Takes as input a hashref or list of key/value pairs with the following keys:
1278 Access number number
1282 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>.
1286 Returns an HTML fragment for access number selection.
1290 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1298 my $popnum = $param->{'popnum'};
1299 my $pops = $param->{'pops'};
1301 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1302 return $pops->[0]{city}. ', '. $pops->[0]{state}.
1303 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1304 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1305 if scalar(@$pops) == 1;
1308 my %popnum2pop = ();
1310 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1311 $popnum2pop{$_->{popnum}} = $_;
1316 function opt(what,href,text) {
1317 var optionName = new Option(text, href, false, false)
1318 var length = what.length;
1319 what.options[length] = optionName;
1323 my $init_popstate = $param->{'init_popstate'};
1324 if ( $init_popstate ) {
1325 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1326 $init_popstate. '">';
1329 function acstate_changed(what) {
1330 state = what.options[what.selectedIndex].text;
1331 what.form.popac.options.length = 0
1332 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1336 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1337 foreach my $state ( sort { $a cmp $b } @states ) {
1338 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1340 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1341 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1342 if ($ac eq $param->{'popac'}) {
1343 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1346 $text .= "}\n" unless $init_popstate;
1348 $text .= "popac_changed(what.form.popac)}\n";
1351 function popac_changed(what) {
1352 ac = what.options[what.selectedIndex].text;
1353 what.form.popnum.options.length = 0;
1354 what.form.popnum.options[0] = new Option("City", "-1", false, true);
1358 foreach my $state ( @states ) {
1359 foreach my $popac ( keys %{ $pop{$state} } ) {
1360 $text .= "\nif ( ac == \"$popac\" ) {\n";
1362 foreach my $pop ( @{$pop{$state}->{$popac}}) {
1363 my $o_popnum = $pop->{popnum};
1364 my $poptext = $pop->{city}. ', '. $pop->{state}.
1365 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1367 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1368 if ($popnum == $o_popnum) {
1369 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1377 $text .= "}\n</SCRIPT>\n";
1380 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1381 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1382 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1383 ">$_" foreach sort { $a cmp $b } @states;
1384 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
1387 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1388 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1390 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1393 #comment this block to disable initial list polulation
1394 my @initial_select = ();
1395 if ( scalar( @$pops ) > 100 ) {
1396 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1398 @initial_select = @$pops;
1400 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1401 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1402 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1403 $pop->{city}. ', '. $pop->{state}.
1404 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1407 $text .= qq!</SELECT></TD></TR></TABLE>!;
1413 =item domainselector HASHREF | LIST
1415 Takes as input a hashref or list of key/value pairs with the following keys:
1425 Service number of the selected item.
1429 Returns an HTML fragment for domain selection.
1433 sub domainselector {
1440 my $domsvc= $param->{'domsvc'};
1442 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1443 my $domains = $rv->{'domains'};
1444 $domsvc = $rv->{'domsvc'} unless $domsvc;
1446 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1447 unless scalar(keys %$domains);
1449 if (scalar(keys %$domains) == 1) {
1451 foreach(keys %$domains) {
1454 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1455 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1458 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1461 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1462 $text .= qq!<OPTION VALUE="!. $domain. '"'.
1463 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1464 $domains->{$domain};
1467 $text .= qq!</SELECT></TD></TR>!;
1473 =item didselector HASHREF | LIST
1475 Takes as input a hashref or list of key/value pairs with the following keys:
1485 Returns an HTML fragment for DID selection.
1497 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1498 'args'=>[ %$param ],
1502 $rv->{'error'} || $rv->{'output'};
1508 =head1 RESELLER FUNCTIONS
1510 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1511 with their active session, and the B<customer_info> and B<order_pkg> functions
1512 with their active session and an additional I<custnum> parameter.
1520 =item agent_list_customers
1528 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>