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_logo' => 'MyAccount/invoice_logo',
35 'list_invoices' => 'MyAccount/list_invoices', #?
36 'cancel' => 'MyAccount/cancel', #add to ss cgi!
37 'payment_info' => 'MyAccount/payment_info',
38 'process_payment' => 'MyAccount/process_payment',
39 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
40 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
41 'process_prepay' => 'MyAccount/process_prepay',
42 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
43 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
44 'list_svc_usage' => 'MyAccount/list_svc_usage',
45 'list_support_usage' => 'MyAccount/list_support_usage',
46 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
47 'change_pkg' => 'MyAccount/change_pkg',
48 'order_recharge' => 'MyAccount/order_recharge',
49 'renew_info' => 'MyAccount/renew_info',
50 'order_renew' => 'MyAccount/order_renew',
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',
72 qw( regionselector regionselector_hashref
73 expselect popselector domainselector didselector )
76 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
77 $ENV{'SHELL'} = '/bin/sh';
78 $ENV{'IFS'} = " \t\n";
81 $ENV{'BASH_ENV'} = '';
83 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
84 #if you grant appropriate permissions to whatever user
85 my $freeside_uid = scalar(getpwnam('freeside'));
86 die "not running as the freeside user\n"
87 if $> != $freeside_uid && ! $skip_uid_check;
89 -e $dir or die "FATAL: $dir doesn't exist!";
90 -d $dir or die "FATAL: $dir isn't a directory!";
91 -r $dir or die "FATAL: Can't read $dir as freeside user!";
92 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
94 foreach my $autoload ( keys %autoload ) {
102 #warn scalar(@_). ": ". join(" / ", @_);
106 $param->{_packet} = \''. $autoload{$autoload}. '\';
108 simple_packet($param);
118 warn "sending ". $packet->{_packet}. " to server"
120 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
121 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
122 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
125 #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
127 #block until there is a message on socket
128 # my $w = new IO::Select;
130 # my @wait = $w->can_read;
132 warn "reading message from server"
135 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
136 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
138 warn "returning message to client"
146 FS::SelfService - Freeside self-service API
150 # password and shell account changes
151 use FS::SelfService qw(passwd chfn chsh);
153 # "my account" functionality
154 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
156 my $rv = login( { 'username' => $username,
158 'password' => $password,
162 if ( $rv->{'error'} ) {
163 #handle login error...
166 my $session_id = $rv->{'session_id'};
169 my $customer_info = customer_info( { 'session_id' => $session_id } );
171 #payment_info and process_payment are available in 1.5+ only
172 my $payment_info = payment_info( { 'session_id' => $session_id } );
174 #!!! process_payment example
176 #!!! list_pkgs example
178 #!!! order_pkg example
180 #!!! cancel_pkg example
182 # signup functionality
183 use FS::SelfService qw( signup_info new_customer );
185 my $signup_info = signup_info;
187 $rv = new_customer( {
190 'company' => $company,
191 'address1' => $address1,
192 'address2' => $address2,
196 'country' => $country,
197 'daytime' => $daytime,
201 'payinfo' => $payinfo,
203 'paystart_month' => $paystart_month
204 'paystart_year' => $paystart_year,
205 'payissue' => $payissue,
207 'paydate' => $paydate,
208 'payname' => $payname,
209 'invoicing_list' => $invoicing_list,
210 'referral_custnum' => $referral_custnum,
211 'agentnum' => $agentnum,
212 'pkgpart' => $pkgpart,
214 'username' => $username,
215 '_password' => $password,
219 'phonenum' => $phonenum,
224 my $error = $rv->{'error'};
225 if ( $error eq '_decline' ) {
235 Use this API to implement your own client "self-service" module.
237 If you just want to customize the look of the existing "self-service" module,
240 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
252 =head1 "MY ACCOUNT" FUNCTIONS
258 Creates a user session. Takes a hash reference as parameter with the
277 Returns a hash reference with the following keys:
283 Empty on success, or an error message on errors.
287 Session identifier for successful logins
291 =item customer_info HASHREF
293 Returns general customer information.
295 Takes a hash reference as parameter with a single key: B<session_id>
297 Returns a hash reference with the following keys:
311 Array reference of hash references of open inoices. Each hash reference has
312 the following keys: invnum, date, owed
316 An HTML fragment containing shipping and billing addresses.
318 =item The following fields are also returned
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 payname month year invoicing_list postal_invoicing
324 =item edit_info HASHREF
326 Takes a hash reference as parameter with any of the following keys:
328 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
330 If a field exists, the customer record is updated with the new value of that
331 field. If a field does not exist, that field is not changed on the customer
334 Returns a hash reference with a single key, B<error>, empty on success, or an
335 error message on errors
337 =item invoice HASHREF
339 Returns an invoice. Takes a hash reference as parameter with two keys:
340 session_id and invnum
342 Returns a hash reference with the following keys:
348 Empty on success, or an error message on errors
360 =item list_invoices HASHREF
362 Returns a list of all customer invoices. Takes a hash references with a single
365 Returns a hash reference with the following keys:
371 Empty on success, or an error message on errors
375 Reference to array of hash references with the following keys:
385 Invoice date, in UNIX epoch time
393 Cancels this customer.
395 Takes a hash reference as parameter with a single key: B<session_id>
397 Returns a hash reference with a single key, B<error>, which is empty on
398 success or an error message on errors.
400 =item payment_info HASHREF
402 Returns information that may be useful in displaying a payment page.
404 Takes a hash reference as parameter with a single key: B<session_id>.
406 Returns a hash reference with the following keys:
412 Empty on success, or an error message on errors
420 Exact name on credit card (CARD/DCRD)
444 Customer's current default payment type.
448 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
452 For CARD/DCRD payment types, the card number
456 For CARD/DCRD payment types, expiration month
460 For CARD/DCRD payment types, expiration year
462 =item cust_main_county
464 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.
468 Array reference of all states in the current default country.
472 Hash reference of card types; keys are card types, values are the exact strings
473 passed to the process_payment function
477 Unique transaction identifier (prevents multiple charges), passed to the
478 process_payment function
482 =item process_payment HASHREF
484 Processes a payment and possible change of address or payment type. Takes a
485 hash reference as parameter with the following keys:
499 If true, address and card information entered will be saved for subsequent
504 If true, future credit card payments will be done automatically (sets payby to
505 CARD). If false, future credit card payments will be done on-demand (sets
506 payby to DCRD). This option only has meaning if B<save> is set true.
538 Card expiration month
546 Unique transaction identifier, returned from the payment_info function.
547 Prevents multiple charges.
551 Returns a hash reference with a single key, B<error>, empty on success, or an
552 error message on errors
554 =item process_payment_order_pkg
556 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
557 payment processes sucessfully, the package is ordered. Takes a hash reference
558 as parameter with the keys of both methods.
560 Returns a hash reference with a single key, B<error>, empty on success, or an
561 error message on errors.
563 =item process_payment_order_renew
565 Combines the B<process_payment> and B<order_renew> functions in one step. If
566 the payment processes sucessfully, the renewal is processed. Takes a hash
567 reference as parameter with the keys of both methods.
569 Returns a hash reference with a single key, B<error>, empty on success, or an
570 error message on errors.
574 Returns package information for this customer. For more detail on services,
577 Takes a hash reference as parameter with a single key: B<session_id>
579 Returns a hash reference containing customer package information. The hash reference contains the following keys:
587 =item cust_pkg HASHREF
589 Array reference of hash references, each of which has the fields of a cust_pkg
590 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
591 the internal FS:: objects, but hash references of columns and values.
595 =item part_pkg fields
597 All fields of part_pkg for this specific cust_pkg (be careful with this
598 information - it may reveal more about your available packages than you would
599 like users to know in aggregate)
603 #XXX pare part_pkg fields down to a more secure subset
607 An array of hash references indicating information on unprovisioned services
608 available for provisioning for this specific cust_pkg. Each has the following
613 =item part_svc fields
615 All fields of part_svc (be careful with this information - it may reveal more
616 about your available packages than you would like users to know in aggregate)
620 #XXX pare part_svc fields down to a more secure subset
626 An array of hash references indicating information on the customer services
627 already provisioned for this specific cust_pkg. Each has the following keys:
633 Array reference with three elements:
643 Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)
647 Table name of this service
653 Primary key for this service
657 Service definition (part_pkg)
661 Customer package (cust_pkg)
665 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
673 Empty on success, or an error message on errors.
679 Returns service information for this customer.
681 Takes a hash reference as parameter with a single key: B<session_id>
683 Returns a hash reference containing customer package information. The hash reference contains the following keys:
693 An array of hash references indicating information on all of this customer's
694 services. Each has the following keys:
700 Primary key for this service
708 Meaningful user-specific identifier for the service (i.e. username, domain, or
713 Account (svc_acct) services also have the following keys:
729 Upload bytes remaining
733 Download bytes remaining
737 Total bytes remaining
739 =item recharge_amount
743 =item recharge_seconds
745 Number of seconds gained by recharge
747 =item recharge_upbytes
749 Number of upload bytes gained by recharge
751 =item recharge_downbytes
753 Number of download bytes gained by recharge
755 =item recharge_totalbytes
757 Number of total bytes gained by recharge
765 Orders a package for this customer.
767 Takes a hash reference as parameter with the following keys:
777 pkgpart of package to order
781 optional svcpart, required only if the package definition does not contain
782 one svc_acct service definition with quantity 1 (it may contain others with
795 Optional security phrase
799 Optional Access number number
803 Returns a hash reference with a single key, B<error>, empty on success, or an
804 error message on errors. The special error '_decline' is returned for
805 declined transactions.
809 Provides useful info for early renewals.
811 Takes a hash reference as parameter with the following keys:
821 Returns a hash reference. On errors, it contains a single key, B<error>, with
822 the error message. Otherwise, contains a single key, B<dates>, pointing to
823 an array refernce of hash references. Each hash reference contains the
830 (Future) Bill date. Indicates a future date for which billing could be run.
831 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
834 =item bill_date_pretty
836 (Future) Bill date as a human-readable string. (Convenience for display;
837 subject to change, so best not to parse for the date.)
841 Base amount which will be charged if renewed early as of this date.
845 Renewal date; i.e. even-futher future date at which the customer will be paid
846 through if the early renewal is completed with the given B<bill-date>.
847 Specified as a integer UNIX timestamp.
849 =item renew_date_pretty
851 Renewal date as a human-readable string. (Convenience for display;
852 subject to change, so best not to parse for the date.)
858 Renews this customer early; i.e. runs billing for this customer in advance.
860 Takes a hash reference as parameter with the following keys:
870 Integer date as returned by the B<renew_info> function, indicating the advance
871 date for which to run billing.
875 Returns a hash reference with a single key, B<error>, empty on success, or an
876 error message on errors.
880 Cancels a package for this customer.
882 Takes a hash reference as parameter with the following keys:
892 pkgpart of package to cancel
896 Returns a hash reference with a single key, B<error>, empty on success, or an
897 error message on errors.
901 =head1 SIGNUP FUNCTIONS
905 =item signup_info HASHREF
907 Takes a hash reference as parameter with the following keys:
911 =item session_id - Optional agent/reseller interface session
915 Returns a hash reference containing information that may be useful in
916 displaying a signup page. The hash reference contains the following keys:
920 =item cust_main_county
922 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.
926 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
927 an agentnum specified explicitly via reseller interface session_id in the
932 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.
934 =item agentnum2part_pkg
936 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.
940 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.
942 =item security_phrase
944 True if the "security_phrase" feature is enabled
948 Array reference of acceptable payment types for signup
954 credit card - automatic
958 credit card - on-demand - version 1.5+ only
962 electronic check - automatic
966 electronic check - on-demand - version 1.5+ only
974 billing, not recommended for signups
978 free, definitely not recommended for signups
982 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
988 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
992 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".
1004 =item new_customer HASHREF
1006 Creates a new customer. Takes a hash reference as parameter with the
1013 first name (required)
1017 last name (required)
1021 (not typically collected; mostly used for ACH transactions)
1027 =item address1 (required)
1035 =item city (required)
1043 =item state (required)
1047 =item zip (required)
1053 Daytime phone number
1057 Evening phone number
1065 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1069 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1073 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1077 Expiration date for CARD/DCRD
1081 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1083 =item invoicing_list
1085 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),
1087 =item referral_custnum
1089 referring customer number
1097 pkgpart of initial package
1113 Access number (index, not the literal number)
1117 Country code (to be provisioned as a service)
1121 Phone number (to be provisioned as a service)
1129 Returns a hash reference with the following keys:
1135 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)
1139 =item regionselector HASHREF | LIST
1141 Takes as input a hashref or list of key/value pairs with the following keys:
1145 =item selected_county
1147 Currently selected county
1149 =item selected_state
1151 Currently selected state
1153 =item selected_country
1155 Currently selected country
1159 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1163 Specify a javascript subroutine to call on changes
1169 =item default_country
1175 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>.
1179 Returns a list consisting of three HTML fragments for county selection,
1180 state selection and country selection, respectively.
1184 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1185 sub regionselector {
1192 $param->{'selected_country'} ||= $param->{'default_country'};
1193 $param->{'selected_state'} ||= $param->{'default_state'};
1195 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1199 my %cust_main_county;
1201 # unless ( @cust_main_county ) { #cache
1202 #@cust_main_county = qsearch('cust_main_county', {} );
1203 #foreach my $c ( @cust_main_county ) {
1204 foreach my $c ( @{ $param->{'locales'} } ) {
1205 #$countyflag=1 if $c->county;
1206 $countyflag=1 if $c->{county};
1207 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1208 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1209 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1212 $countyflag=1 if $param->{selected_county};
1214 my $script_html = <<END;
1216 function opt(what,value,text) {
1217 var optionName = new Option(text, value, false, false);
1218 var length = what.length;
1219 what.options[length] = optionName;
1221 function ${prefix}country_changed(what) {
1222 country = what.options[what.selectedIndex].text;
1223 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1224 what.form.${prefix}state.options[i] = null;
1226 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1228 foreach my $country ( sort keys %cust_main_county ) {
1229 $script_html .= "\nif ( country == \"$country\" ) {\n";
1230 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1231 my $text = $state || '(n/a)';
1232 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1234 $script_html .= "}\n";
1237 $script_html .= <<END;
1239 function ${prefix}state_changed(what) {
1242 if ( $countyflag ) {
1243 $script_html .= <<END;
1244 state = what.options[what.selectedIndex].text;
1245 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1246 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1247 what.form.${prefix}county.options[i] = null;
1250 foreach my $country ( sort keys %cust_main_county ) {
1251 $script_html .= "\nif ( country == \"$country\" ) {\n";
1252 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1253 $script_html .= "\nif ( state == \"$state\" ) {\n";
1254 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1255 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1256 my $text = $county || '(n/a)';
1258 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1260 $script_html .= "}\n";
1262 $script_html .= "}\n";
1266 $script_html .= <<END;
1271 my $county_html = $script_html;
1272 if ( $countyflag ) {
1273 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1274 $county_html .= '</SELECT>';
1277 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1280 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1281 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1282 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1283 my $text = $state || '(n/a)';
1284 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1285 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1287 $state_html .= '</SELECT>';
1289 my $country_html = '';
1290 if ( scalar( keys %cust_main_county ) > 1 ) {
1292 $country_html = qq(<SELECT NAME="${prefix}country" ).
1293 qq(onChange="${prefix}country_changed(this); ).
1294 $param->{'onchange'}.
1297 my $countrydefault = $param->{default_country} || 'US';
1298 foreach my $country (
1299 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1300 keys %cust_main_county
1302 my $selected = $country eq $param->{'selected_country'}
1305 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1307 $country_html .= '</SELECT>';
1310 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1311 ' VALUE="'. (keys %cust_main_county )[0]. '">';
1315 ($county_html, $state_html, $country_html);
1319 sub regionselector_hashref {
1320 my ($county_html, $state_html, $country_html) = regionselector(@_);
1322 'county_html' => $county_html,
1323 'state_html' => $state_html,
1324 'country_html' => $country_html,
1328 #=item expselect HASHREF | LIST
1330 #Takes as input a hashref or list of key/value pairs with the following keys:
1334 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1336 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1340 =item expselect PREFIX [ DATE ]
1342 Takes as input a unique prefix string and the current expiration date, in
1343 yyyy-mm-dd or m-d-yyyy format
1345 Returns an HTML fragments for expiration date selection.
1351 #if ( ref($_[0]) ) {
1355 #my $prefix = $param->{'prefix'};
1356 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1357 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
1359 my $date = scalar(@_) ? shift : '';
1361 my( $m, $y ) = ( 0, 0 );
1362 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1363 ( $m, $y ) = ( $2, $1 );
1364 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1365 ( $m, $y ) = ( $1, $3 );
1367 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1369 $return .= qq!<OPTION VALUE="$_"!;
1370 $return .= " SELECTED" if $_ == $m;
1373 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1375 my $thisYear = $t[5] + 1900;
1376 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1377 $return .= qq!<OPTION VALUE="$_"!;
1378 $return .= " SELECTED" if $_ == $y;
1381 $return .= "</SELECT>";
1386 =item popselector HASHREF | LIST
1388 Takes as input a hashref or list of key/value pairs with the following keys:
1394 Access number number
1398 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>.
1402 Returns an HTML fragment for access number selection.
1406 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1414 my $popnum = $param->{'popnum'};
1415 my $pops = $param->{'pops'};
1417 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1418 return $pops->[0]{city}. ', '. $pops->[0]{state}.
1419 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1420 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1421 if scalar(@$pops) == 1;
1424 my %popnum2pop = ();
1426 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1427 $popnum2pop{$_->{popnum}} = $_;
1432 function opt(what,href,text) {
1433 var optionName = new Option(text, href, false, false)
1434 var length = what.length;
1435 what.options[length] = optionName;
1439 my $init_popstate = $param->{'init_popstate'};
1440 if ( $init_popstate ) {
1441 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1442 $init_popstate. '">';
1445 function acstate_changed(what) {
1446 state = what.options[what.selectedIndex].text;
1447 what.form.popac.options.length = 0
1448 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1452 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1453 foreach my $state ( sort { $a cmp $b } @states ) {
1454 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1456 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1457 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1458 if ($ac eq $param->{'popac'}) {
1459 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1462 $text .= "}\n" unless $init_popstate;
1464 $text .= "popac_changed(what.form.popac)}\n";
1467 function popac_changed(what) {
1468 ac = what.options[what.selectedIndex].text;
1469 what.form.popnum.options.length = 0;
1470 what.form.popnum.options[0] = new Option("City", "-1", false, true);
1474 foreach my $state ( @states ) {
1475 foreach my $popac ( keys %{ $pop{$state} } ) {
1476 $text .= "\nif ( ac == \"$popac\" ) {\n";
1478 foreach my $pop ( @{$pop{$state}->{$popac}}) {
1479 my $o_popnum = $pop->{popnum};
1480 my $poptext = $pop->{city}. ', '. $pop->{state}.
1481 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1483 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1484 if ($popnum == $o_popnum) {
1485 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1493 $text .= "}\n</SCRIPT>\n";
1496 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1497 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1498 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1499 ">$_" foreach sort { $a cmp $b } @states;
1500 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
1503 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1504 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1506 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1509 #comment this block to disable initial list polulation
1510 my @initial_select = ();
1511 if ( scalar( @$pops ) > 100 ) {
1512 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1514 @initial_select = @$pops;
1516 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1517 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1518 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1519 $pop->{city}. ', '. $pop->{state}.
1520 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1523 $text .= qq!</SELECT></TD></TR></TABLE>!;
1529 =item domainselector HASHREF | LIST
1531 Takes as input a hashref or list of key/value pairs with the following keys:
1541 Service number of the selected item.
1545 Returns an HTML fragment for domain selection.
1549 sub domainselector {
1556 my $domsvc= $param->{'domsvc'};
1558 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1559 my $domains = $rv->{'domains'};
1560 $domsvc = $rv->{'domsvc'} unless $domsvc;
1562 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1563 unless scalar(keys %$domains);
1565 if (scalar(keys %$domains) == 1) {
1567 foreach(keys %$domains) {
1570 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1571 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1574 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1577 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1578 $text .= qq!<OPTION VALUE="!. $domain. '"'.
1579 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1580 $domains->{$domain};
1583 $text .= qq!</SELECT></TD></TR>!;
1589 =item didselector HASHREF | LIST
1591 Takes as input a hashref or list of key/value pairs with the following keys:
1601 Returns an HTML fragment for DID selection.
1613 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1614 'args'=>[ %$param ],
1618 $rv->{'error'} || $rv->{'output'};
1624 =head1 RESELLER FUNCTIONS
1626 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1627 with their active session, and the B<customer_info> and B<order_pkg> functions
1628 with their active session and an additional I<custnum> parameter.
1636 =item agent_list_customers
1644 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>