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 'switch_acct' => 'MyAccount/switch_acct',
32 'customer_info' => 'MyAccount/customer_info',
33 'customer_info_short' => 'MyAccount/customer_info_short',
34 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
35 'invoice' => 'MyAccount/invoice',
36 'invoice_pdf' => 'MyAccount/invoice_pdf',
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 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
42 'process_payment' => 'MyAccount/process_payment',
43 'store_payment' => 'MyAccount/store_payment',
44 'process_stored_payment' => 'MyAccount/process_stored_payment',
45 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
46 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
47 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
48 'process_prepay' => 'MyAccount/process_prepay',
49 'realtime_collect' => 'MyAccount/realtime_collect',
50 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
51 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
52 'list_svc_usage' => 'MyAccount/list_svc_usage',
53 'list_dsl_devices' => 'MyAccount/list_dsl_devices',
54 'add_dsl_device' => 'MyAccount/add_dsl_device',
55 'delete_dsl_device' => 'MyAccount/delete_dsl_device',
56 'port_graph' => 'MyAccount/port_graph',
57 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
58 'list_support_usage' => 'MyAccount/list_support_usage',
59 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
60 'change_pkg' => 'MyAccount/change_pkg',
61 'order_recharge' => 'MyAccount/order_recharge',
62 'renew_info' => 'MyAccount/renew_info',
63 'order_renew' => 'MyAccount/order_renew',
64 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
65 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi!
66 'charge' => 'MyAccount/charge', #?
67 'part_svc_info' => 'MyAccount/part_svc_info',
68 'provision_acct' => 'MyAccount/provision_acct',
69 'provision_phone' => 'MyAccount/provision_phone',
70 'provision_external' => 'MyAccount/provision_external',
71 'unprovision_svc' => 'MyAccount/unprovision_svc',
72 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
73 'reset_passwd' => 'MyAccount/reset_passwd',
74 'create_ticket' => 'MyAccount/create_ticket',
75 'get_ticket' => 'MyAccount/get_ticket',
76 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
77 'did_report' => 'MyAccount/did_report',
78 'signup_info' => 'Signup/signup_info',
79 'skin_info' => 'MyAccount/skin_info',
80 'access_info' => 'MyAccount/access_info',
81 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
82 'new_customer' => 'Signup/new_customer',
83 'capture_payment' => 'Signup/capture_payment',
84 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
85 'new_agent' => 'Agent/new_agent',
86 'agent_login' => 'Agent/agent_login',
87 'agent_logout' => 'Agent/agent_logout',
88 'agent_info' => 'Agent/agent_info',
89 'agent_list_customers' => 'Agent/agent_list_customers',
90 'check_username' => 'Agent/check_username',
91 'suspend_username' => 'Agent/suspend_username',
92 'unsuspend_username' => 'Agent/unsuspend_username',
93 'mason_comp' => 'MasonComponent/mason_comp',
94 'call_time' => 'PrepaidPhone/call_time',
95 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
96 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
98 #'bulk_processrow' => 'Bulk/processrow',
99 #conflicts w/Agent one# 'check_username' => 'Bulk/check_username',
101 'ping' => 'SGNG/ping',
102 'decompify_pkgs' => 'SGNG/decompify_pkgs',
103 'previous_payment_info' => 'SGNG/previous_payment_info',
104 'previous_payment_info_renew_info'
105 => 'SGNG/previous_payment_info_renew_info',
106 'previous_process_payment' => 'SGNG/previous_process_payment',
107 'previous_process_payment_order_pkg'
108 => 'SGNG/previous_process_payment_order_pkg',
109 'previous_process_payment_change_pkg'
110 => 'SGNG/previous_process_payment_change_pkg',
111 'previous_process_payment_order_renew'
112 => 'SGNG/previous_process_payment_order_renew',
116 qw( regionselector regionselector_hashref location_form
117 expselect popselector domainselector didselector
121 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
122 $ENV{'SHELL'} = '/bin/sh';
123 $ENV{'IFS'} = " \t\n";
126 $ENV{'BASH_ENV'} = '';
128 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
129 #if you grant appropriate permissions to whatever user
130 my $freeside_uid = scalar(getpwnam('freeside'));
131 die "not running as the freeside user\n"
132 if $> != $freeside_uid && ! $skip_uid_check;
134 -e $dir or die "FATAL: $dir doesn't exist!";
135 -d $dir or die "FATAL: $dir isn't a directory!";
136 -r $dir or die "FATAL: Can't read $dir as freeside user!";
137 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
139 foreach my $autoload ( keys %autoload ) {
142 "sub $autoload { ". '
147 #warn scalar(@_). ": ". join(" / ", @_);
151 $param->{_packet} = \''. $autoload{$autoload}. '\';
153 simple_packet($param);
163 warn "sending ". $packet->{_packet}. " to server"
165 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
166 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
167 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
170 #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
172 #block until there is a message on socket
173 # my $w = new IO::Select;
175 # my @wait = $w->can_read;
177 warn "reading message from server"
180 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
181 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
183 warn "returning message to client"
191 FS::SelfService - Freeside self-service API
195 # password and shell account changes
196 use FS::SelfService qw(passwd chfn chsh);
198 # "my account" functionality
199 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
201 my $rv = login( { 'username' => $username,
203 'password' => $password,
207 if ( $rv->{'error'} ) {
208 #handle login error...
211 my $session_id = $rv->{'session_id'};
214 my $customer_info = customer_info( { 'session_id' => $session_id } );
216 #payment_info and process_payment are available in 1.5+ only
217 my $payment_info = payment_info( { 'session_id' => $session_id } );
219 #!!! process_payment example
221 #!!! list_pkgs example
223 #!!! order_pkg example
225 #!!! cancel_pkg example
227 # signup functionality
228 use FS::SelfService qw( signup_info new_customer );
230 my $signup_info = signup_info;
232 $rv = new_customer( {
235 'company' => $company,
236 'address1' => $address1,
237 'address2' => $address2,
241 'country' => $country,
242 'daytime' => $daytime,
246 'payinfo' => $payinfo,
248 'paystart_month' => $paystart_month
249 'paystart_year' => $paystart_year,
250 'payissue' => $payissue,
252 'paydate' => $paydate,
253 'payname' => $payname,
254 'invoicing_list' => $invoicing_list,
255 'referral_custnum' => $referral_custnum,
256 'agentnum' => $agentnum,
257 'pkgpart' => $pkgpart,
259 'username' => $username,
260 '_password' => $password,
264 'phonenum' => $phonenum,
269 my $error = $rv->{'error'};
270 if ( $error eq '_decline' ) {
280 Use this API to implement your own client "self-service" module.
282 If you just want to customize the look of the existing "self-service" module,
285 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
297 =head1 "MY ACCOUNT" FUNCTIONS
303 Creates a user session. Takes a hash reference as parameter with the
322 Returns a hash reference with the following keys:
328 Empty on success, or an error message on errors.
332 Session identifier for successful logins
336 =item customer_info HASHREF
338 Returns general customer information.
340 Takes a hash reference as parameter with a single key: B<session_id>
342 Returns a hash reference with the following keys:
356 Array reference of hash references of open inoices. Each hash reference has
357 the following keys: invnum, date, owed
361 An HTML fragment containing shipping and billing addresses.
363 =item The following fields are also returned
365 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
369 =item edit_info HASHREF
371 Takes a hash reference as parameter with any of the following keys:
373 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
375 If a field exists, the customer record is updated with the new value of that
376 field. If a field does not exist, that field is not changed on the customer
379 Returns a hash reference with a single key, B<error>, empty on success, or an
380 error message on errors
382 =item invoice HASHREF
384 Returns an invoice. Takes a hash reference as parameter with two keys:
385 session_id and invnum
387 Returns a hash reference with the following keys:
393 Empty on success, or an error message on errors
405 =item list_invoices HASHREF
407 Returns a list of all customer invoices. Takes a hash references with a single
410 Returns a hash reference with the following keys:
416 Empty on success, or an error message on errors
420 Reference to array of hash references with the following keys:
430 Invoice date, in UNIX epoch time
438 Cancels this customer.
440 Takes a hash reference as parameter with a single key: B<session_id>
442 Returns a hash reference with a single key, B<error>, which is empty on
443 success or an error message on errors.
445 =item payment_info HASHREF
447 Returns information that may be useful in displaying a payment page.
449 Takes a hash reference as parameter with a single key: B<session_id>.
451 Returns a hash reference with the following keys:
457 Empty on success, or an error message on errors
465 Exact name on credit card (CARD/DCRD)
489 Customer's current default payment type.
493 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
497 For CARD/DCRD payment types, the card number
501 For CARD/DCRD payment types, expiration month
505 For CARD/DCRD payment types, expiration year
507 =item cust_main_county
509 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.
513 Array reference of all states in the current default country.
517 Hash reference of card types; keys are card types, values are the exact strings
518 passed to the process_payment function
522 #this doesn't actually work yet
526 #Unique transaction identifier (prevents multiple charges), passed to the
527 #process_payment function
531 =item process_payment HASHREF
533 Processes a payment and possible change of address or payment type. Takes a
534 hash reference as parameter with the following keys:
548 If true, address and card information entered will be saved for subsequent
553 If true, future credit card payments will be done automatically (sets payby to
554 CARD). If false, future credit card payments will be done on-demand (sets
555 payby to DCRD). This option only has meaning if B<save> is set true.
583 Two-letter country code
591 Card expiration month
599 #this doesn't actually work yet
603 #Unique transaction identifier, returned from the payment_info function.
604 #Prevents multiple charges.
608 Returns a hash reference with a single key, B<error>, empty on success, or an
609 error message on errors.
611 =item process_payment_order_pkg
613 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
614 payment processes sucessfully, the package is ordered. Takes a hash reference
615 as parameter with the keys of both methods.
617 Returns a hash reference with a single key, B<error>, empty on success, or an
618 error message on errors.
620 =item process_payment_change_pkg
622 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
623 payment processes sucessfully, the package is ordered. Takes a hash reference
624 as parameter with the keys of both methods.
626 Returns a hash reference with a single key, B<error>, empty on success, or an
627 error message on errors.
630 =item process_payment_order_renew
632 Combines the B<process_payment> and B<order_renew> functions in one step. If
633 the payment processes sucessfully, the renewal is processed. Takes a hash
634 reference as parameter with the keys of both methods.
636 Returns a hash reference with a single key, B<error>, empty on success, or an
637 error message on errors.
641 Returns package information for this customer. For more detail on services,
644 Takes a hash reference as parameter with a single key: B<session_id>
646 Returns a hash reference containing customer package information. The hash reference contains the following keys:
656 Empty on success, or an error message on errors.
658 =item cust_pkg HASHREF
660 Array reference of hash references, each of which has the fields of a cust_pkg
661 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
662 the internal FS:: objects, but hash references of columns and values.
666 =item part_pkg fields
668 All fields of part_pkg for this specific cust_pkg (be careful with this
669 information - it may reveal more about your available packages than you would
670 like users to know in aggregate)
674 #XXX pare part_pkg fields down to a more secure subset
678 An array of hash references indicating information on unprovisioned services
679 available for provisioning for this specific cust_pkg. Each has the following
684 =item part_svc fields
686 All fields of part_svc (be careful with this information - it may reveal more
687 about your available packages than you would like users to know in aggregate)
691 #XXX pare part_svc fields down to a more secure subset
697 An array of hash references indicating information on the customer services
698 already provisioned for this specific cust_pkg. Each has the following keys:
704 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.
710 Primary key for this service
714 Service definition (see L<FS::part_svc>)
718 Customer package (see L<FS::cust_pkg>)
722 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
730 Returns service information for this customer.
732 Takes a hash reference as parameter with a single key: B<session_id>
734 Returns a hash reference containing customer package information. The hash reference contains the following keys:
744 An array of hash references indicating information on all of this customer's
745 services. Each has the following keys:
751 Primary key for this service
759 Meaningful user-specific identifier for the service (i.e. username, domain, or
764 Account (svc_acct) services also have the following keys:
782 Upload bytes remaining
786 Download bytes remaining
790 Total bytes remaining
792 =item recharge_amount
796 =item recharge_seconds
798 Number of seconds gained by recharge
800 =item recharge_upbytes
802 Number of upload bytes gained by recharge
804 =item recharge_downbytes
806 Number of download bytes gained by recharge
808 =item recharge_totalbytes
810 Number of total bytes gained by recharge
818 Orders a package for this customer.
820 Takes a hash reference as parameter with the following keys:
830 Package to order (see L<FS::part_pkg>).
834 Service to order (see L<FS::part_svc>).
836 Normally optional; required only to provision a non-svc_acct service, or if the
837 package definition does not contain one svc_acct service definition with
838 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
839 also be specified to indicate that no initial service should be provisioned.
843 Fields used when provisioning an svc_acct service:
857 Optional security phrase
861 Optional Access number number
865 Fields used when provisioning an svc_domain service:
875 Fields used when provisioning an svc_phone service:
893 Fields used when provisioning an svc_external service:
907 Fields used when provisioning an svc_pbx service:
921 Returns a hash reference with a single key, B<error>, empty on success, or an
922 error message on errors. The special error '_decline' is returned for
923 declined transactions.
927 Changes a package for this customer.
929 Takes a hash reference as parameter with the following keys:
939 Existing customer package.
943 New package to order (see L<FS::part_pkg>).
947 Returns a hash reference with the following keys:
953 Empty on success, or an error message on errors.
957 On success, the new pkgnum
964 Provides useful info for early renewals.
966 Takes a hash reference as parameter with the following keys:
976 Returns a hash reference. On errors, it contains a single key, B<error>, with
977 the error message. Otherwise, contains a single key, B<dates>, pointing to
978 an array refernce of hash references. Each hash reference contains the
985 (Future) Bill date. Indicates a future date for which billing could be run.
986 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
989 =item bill_date_pretty
991 (Future) Bill date as a human-readable string. (Convenience for display;
992 subject to change, so best not to parse for the date.)
996 Base amount which will be charged if renewed early as of this date.
1000 Renewal date; i.e. even-futher future date at which the customer will be paid
1001 through if the early renewal is completed with the given B<bill-date>.
1002 Specified as a integer UNIX timestamp.
1004 =item renew_date_pretty
1006 Renewal date as a human-readable string. (Convenience for display;
1007 subject to change, so best not to parse for the date.)
1011 Package that will be renewed.
1015 Expiration date of the package that will be renewed.
1017 =item expire_date_pretty
1019 Expiration date of the package that will be renewed, as a human-readable
1020 string. (Convenience for display; subject to change, so best not to parse for
1027 Renews this customer early; i.e. runs billing for this customer in advance.
1029 Takes a hash reference as parameter with the following keys:
1039 Integer date as returned by the B<renew_info> function, indicating the advance
1040 date for which to run billing.
1044 Returns a hash reference with a single key, B<error>, empty on success, or an
1045 error message on errors.
1049 Cancels a package for this customer.
1051 Takes a hash reference as parameter with the following keys:
1061 pkgpart of package to cancel
1065 Returns a hash reference with a single key, B<error>, empty on success, or an
1066 error message on errors.
1070 =head1 SIGNUP FUNCTIONS
1074 =item signup_info HASHREF
1076 Takes a hash reference as parameter with the following keys:
1080 =item session_id - Optional agent/reseller interface session
1084 Returns a hash reference containing information that may be useful in
1085 displaying a signup page. The hash reference contains the following keys:
1089 =item cust_main_county
1091 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.
1095 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
1096 an agentnum specified explicitly via reseller interface session_id in the
1101 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.
1103 =item agentnum2part_pkg
1105 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.
1109 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.
1111 =item security_phrase
1113 True if the "security_phrase" feature is enabled
1117 Array reference of acceptable payment types for signup
1123 credit card - automatic
1127 credit card - on-demand - version 1.5+ only
1131 electronic check - automatic
1135 electronic check - on-demand - version 1.5+ only
1143 billing, not recommended for signups
1147 free, definitely not recommended for signups
1151 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1157 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1161 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".
1167 =item countrydefault
1173 =item new_customer HASHREF
1175 Creates a new customer. Takes a hash reference as parameter with the
1182 first name (required)
1186 last name (required)
1190 (not typically collected; mostly used for ACH transactions)
1196 =item address1 (required)
1204 =item city (required)
1212 =item state (required)
1216 =item zip (required)
1222 Daytime phone number
1226 Evening phone number
1234 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1238 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1242 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1246 Expiration date for CARD/DCRD
1250 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1252 =item invoicing_list
1254 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),
1256 =item referral_custnum
1258 referring customer number
1266 pkgpart of initial package
1282 Access number (index, not the literal number)
1286 Country code (to be provisioned as a service)
1290 Phone number (to be provisioned as a service)
1298 Returns a hash reference with the following keys:
1304 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)
1308 =item regionselector HASHREF | LIST
1310 Takes as input a hashref or list of key/value pairs with the following keys:
1314 =item selected_county
1316 Currently selected county
1318 =item selected_state
1320 Currently selected state
1322 =item selected_country
1324 Currently selected country
1328 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1332 Specify a javascript subroutine to call on changes
1338 =item default_country
1344 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>.
1348 Returns a list consisting of three HTML fragments for county selection,
1349 state selection and country selection, respectively.
1353 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1354 sub regionselector {
1361 $param->{'selected_country'} ||= $param->{'default_country'};
1362 $param->{'selected_state'} ||= $param->{'default_state'};
1364 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1368 my %cust_main_county;
1370 # unless ( @cust_main_county ) { #cache
1371 #@cust_main_county = qsearch('cust_main_county', {} );
1372 #foreach my $c ( @cust_main_county ) {
1373 foreach my $c ( @{ $param->{'locales'} } ) {
1374 #$countyflag=1 if $c->county;
1375 $countyflag=1 if $c->{county};
1376 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1377 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1378 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1381 $countyflag=1 if $param->{selected_county};
1383 my $script_html = <<END;
1385 function opt(what,value,text) {
1386 var optionName = new Option(text, value, false, false);
1387 var length = what.length;
1388 what.options[length] = optionName;
1390 function ${prefix}country_changed(what) {
1391 country = what.options[what.selectedIndex].text;
1392 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1393 what.form.${prefix}state.options[i] = null;
1395 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1397 foreach my $country ( sort keys %cust_main_county ) {
1398 $script_html .= "\nif ( country == \"$country\" ) {\n";
1399 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1400 my $text = $state || '(n/a)';
1401 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1403 $script_html .= "}\n";
1406 $script_html .= <<END;
1408 function ${prefix}state_changed(what) {
1411 if ( $countyflag ) {
1412 $script_html .= <<END;
1413 state = what.options[what.selectedIndex].text;
1414 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1415 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1416 what.form.${prefix}county.options[i] = null;
1419 foreach my $country ( sort keys %cust_main_county ) {
1420 $script_html .= "\nif ( country == \"$country\" ) {\n";
1421 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1422 $script_html .= "\nif ( state == \"$state\" ) {\n";
1423 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1424 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1425 my $text = $county || '(n/a)';
1427 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1429 $script_html .= "}\n";
1431 $script_html .= "}\n";
1435 $script_html .= <<END;
1440 my $county_html = $script_html;
1441 if ( $countyflag ) {
1442 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1443 foreach my $county (
1444 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1446 my $text = $county || '(n/a)';
1447 $county_html .= qq!<OPTION VALUE="$county"!.
1448 ($county eq $param->{'selected_county'} ?
1455 $county_html .= '</SELECT>';
1458 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1461 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1462 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1463 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1464 my $text = $state || '(n/a)';
1465 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1466 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1468 $state_html .= '</SELECT>';
1470 my $country_html = '';
1471 if ( scalar( keys %cust_main_county ) > 1 ) {
1473 $country_html = qq(<SELECT NAME="${prefix}country" ).
1474 qq(onChange="${prefix}country_changed(this); ).
1475 $param->{'onchange'}.
1478 my $countrydefault = $param->{default_country} || 'US';
1479 foreach my $country (
1480 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1481 keys %cust_main_county
1483 my $selected = $country eq $param->{'selected_country'}
1486 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1488 $country_html .= '</SELECT>';
1491 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1492 ' VALUE="'. (keys %cust_main_county )[0]. '">';
1496 ($county_html, $state_html, $country_html);
1500 sub regionselector_hashref {
1501 my ($county_html, $state_html, $country_html) = regionselector(@_);
1503 'county_html' => $county_html,
1504 'state_html' => $state_html,
1505 'country_html' => $country_html,
1509 =item location_form HASHREF | LIST
1511 Takes as input a hashref or list of key/value pairs with the following keys:
1517 Current customer session_id
1521 Omit red asterisks from required fields.
1523 =item address1_label
1525 Label for first address line.
1529 Returns an HTML fragment for a location form (address, city, state, zip,
1542 my $session_id = delete $param->{'session_id'};
1544 my $rv = mason_comp( 'session_id' => $session_id,
1545 'comp' => '/elements/location.html',
1546 'args' => [ %$param ],
1550 $rv->{'error'} || $rv->{'output'};
1555 #=item expselect HASHREF | LIST
1557 #Takes as input a hashref or list of key/value pairs with the following keys:
1561 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1563 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
1567 =item expselect PREFIX [ DATE ]
1569 Takes as input a unique prefix string and the current expiration date, in
1570 yyyy-mm-dd or m-d-yyyy format
1572 Returns an HTML fragments for expiration date selection.
1578 #if ( ref($_[0]) ) {
1582 #my $prefix = $param->{'prefix'};
1583 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1584 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
1586 my $date = scalar(@_) ? shift : '';
1588 my( $m, $y ) = ( 0, 0 );
1589 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
1590 ( $m, $y ) = ( $2, $1 );
1591 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
1592 ( $m, $y ) = ( $1, $3 );
1594 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
1596 $return .= qq!<OPTION VALUE="$_"!;
1597 $return .= " SELECTED" if $_ == $m;
1600 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
1602 my $thisYear = $t[5] + 1900;
1603 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
1604 $return .= qq!<OPTION VALUE="$_"!;
1605 $return .= " SELECTED" if $_ == $y;
1608 $return .= "</SELECT>";
1613 =item popselector HASHREF | LIST
1615 Takes as input a hashref or list of key/value pairs with the following keys:
1621 Access number number
1625 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>.
1629 Returns an HTML fragment for access number selection.
1633 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
1641 my $popnum = $param->{'popnum'};
1642 my $pops = $param->{'pops'};
1644 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
1645 return $pops->[0]{city}. ', '. $pops->[0]{state}.
1646 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
1647 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
1648 if scalar(@$pops) == 1;
1651 my %popnum2pop = ();
1653 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
1654 $popnum2pop{$_->{popnum}} = $_;
1659 function opt(what,href,text) {
1660 var optionName = new Option(text, href, false, false)
1661 var length = what.length;
1662 what.options[length] = optionName;
1666 my $init_popstate = $param->{'init_popstate'};
1667 if ( $init_popstate ) {
1668 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
1669 $init_popstate. '">';
1672 function acstate_changed(what) {
1673 state = what.options[what.selectedIndex].text;
1674 what.form.popac.options.length = 0
1675 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
1679 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
1680 foreach my $state ( sort { $a cmp $b } @states ) {
1681 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
1683 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
1684 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
1685 if ($ac eq $param->{'popac'}) {
1686 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
1689 $text .= "}\n" unless $init_popstate;
1691 $text .= "popac_changed(what.form.popac)}\n";
1694 function popac_changed(what) {
1695 ac = what.options[what.selectedIndex].text;
1696 what.form.popnum.options.length = 0;
1697 what.form.popnum.options[0] = new Option("City", "-1", false, true);
1701 foreach my $state ( @states ) {
1702 foreach my $popac ( keys %{ $pop{$state} } ) {
1703 $text .= "\nif ( ac == \"$popac\" ) {\n";
1705 foreach my $pop ( @{$pop{$state}->{$popac}}) {
1706 my $o_popnum = $pop->{popnum};
1707 my $poptext = $pop->{city}. ', '. $pop->{state}.
1708 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1710 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
1711 if ($popnum == $o_popnum) {
1712 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
1720 $text .= "}\n</SCRIPT>\n";
1722 $param->{'acstate'} = '' unless defined($param->{'acstate'});
1725 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
1726 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
1727 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
1728 ">$_" foreach sort { $a cmp $b } @states;
1729 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
1732 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
1733 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
1735 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
1738 #comment this block to disable initial list polulation
1739 my @initial_select = ();
1740 if ( scalar( @$pops ) > 100 ) {
1741 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
1743 @initial_select = @$pops;
1745 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
1746 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
1747 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
1748 $pop->{city}. ', '. $pop->{state}.
1749 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
1752 $text .= qq!</SELECT></TD></TR></TABLE>!;
1758 =item domainselector HASHREF | LIST
1760 Takes as input a hashref or list of key/value pairs with the following keys:
1770 Service number of the selected item.
1774 Returns an HTML fragment for domain selection.
1778 sub domainselector {
1785 my $domsvc= $param->{'domsvc'};
1787 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
1788 my $domains = $rv->{'domains'};
1789 $domsvc = $rv->{'domsvc'} unless $domsvc;
1791 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
1792 unless scalar(keys %$domains);
1794 if (scalar(keys %$domains) == 1) {
1796 foreach(keys %$domains) {
1799 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
1800 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
1803 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
1806 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
1807 $text .= qq!<OPTION VALUE="!. $domain. '"'.
1808 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
1809 $domains->{$domain};
1812 $text .= qq!</SELECT></TD></TR>!;
1818 =item didselector HASHREF | LIST
1820 Takes as input a hashref or list of key/value pairs with the following keys:
1826 Field name for the returned HTML fragment.
1830 Service definition (see L<FS::part_svc>)
1834 Returns an HTML fragment for DID selection.
1846 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
1847 'args'=>[ %$param ],
1851 $rv->{'error'} || $rv->{'output'};
1857 =head1 RESELLER FUNCTIONS
1859 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
1860 with their active session, and the B<customer_info> and B<order_pkg> functions
1861 with their active session and an additional I<custnum> parameter.
1863 For the most part, development of the reseller web interface has been
1864 superceded by agent-virtualized access to the backend.
1876 =item agent_list_customers
1878 List agent's customers.
1886 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>