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_banner_image' => 'MyAccount/login_banner_image',
30 'login' => 'MyAccount/login',
31 'logout' => 'MyAccount/logout',
32 'switch_acct' => 'MyAccount/switch_acct',
33 'customer_info' => 'MyAccount/customer_info',
34 'customer_info_short' => 'MyAccount/customer_info_short',
35 'billing_history' => 'MyAccount/billing_history',
36 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
37 'invoice' => 'MyAccount/invoice',
38 'invoice_pdf' => 'MyAccount/invoice_pdf',
39 'legacy_invoice' => 'MyAccount/legacy_invoice',
40 'legacy_invoice_pdf' => 'MyAccount/legacy_invoice_pdf',
41 'invoice_logo' => 'MyAccount/invoice_logo',
42 'list_invoices' => 'MyAccount/list_invoices', #?
43 'cancel' => 'MyAccount/cancel', #add to ss cgi!
44 'payment_info' => 'MyAccount/payment_info',
45 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
46 'process_payment' => 'MyAccount/process_payment',
47 'store_payment' => 'MyAccount/store_payment',
48 'process_stored_payment' => 'MyAccount/process_stored_payment',
49 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
50 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
51 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
52 'process_prepay' => 'MyAccount/process_prepay',
53 'realtime_collect' => 'MyAccount/realtime_collect',
54 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
55 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
56 'list_svc_usage' => 'MyAccount/list_svc_usage',
57 'svc_status_html' => 'MyAccount/svc_status_html',
58 'svc_status_hash' => 'MyAccount/svc_status_hash',
59 'set_svc_status_hash' => 'MyAccount/set_svc_status_hash',
60 'set_svc_status_listadd' => 'MyAccount/set_svc_status_listadd',
61 'set_svc_status_listdel' => 'MyAccount/set_svc_status_listdel',
62 'set_svc_status_vacationadd'=> 'MyAccount/set_svc_status_vacationadd',
63 'set_svc_status_vacationdel'=> 'MyAccount/set_svc_status_vacationdel',
64 'acct_forward_info' => 'MyAccount/acct_forward_info',
65 'process_acct_forward' => 'MyAccount/process_acct_forward',
66 'list_dsl_devices' => 'MyAccount/list_dsl_devices',
67 'add_dsl_device' => 'MyAccount/add_dsl_device',
68 'delete_dsl_device' => 'MyAccount/delete_dsl_device',
69 'port_graph' => 'MyAccount/port_graph',
70 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
71 'list_support_usage' => 'MyAccount/list_support_usage',
72 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
73 'change_pkg' => 'MyAccount/change_pkg',
74 'order_recharge' => 'MyAccount/order_recharge',
75 'renew_info' => 'MyAccount/renew_info',
76 'order_renew' => 'MyAccount/order_renew',
77 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
78 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi!
79 'charge' => 'MyAccount/charge', #?
80 'part_svc_info' => 'MyAccount/part_svc_info',
81 'provision_acct' => 'MyAccount/provision_acct',
82 'provision_phone' => 'MyAccount/provision_phone',
83 'provision_external' => 'MyAccount/provision_external',
84 'unprovision_svc' => 'MyAccount/unprovision_svc',
85 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
86 'reset_passwd' => 'MyAccount/reset_passwd',
87 'check_reset_passwd' => 'MyAccount/check_reset_passwd',
88 'process_reset_passwd' => 'MyAccount/process_reset_passwd',
89 'list_tickets' => 'MyAccount/list_tickets',
90 'create_ticket' => 'MyAccount/create_ticket',
91 'get_ticket' => 'MyAccount/get_ticket',
92 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
93 'did_report' => 'MyAccount/did_report',
94 'signup_info' => 'Signup/signup_info',
95 'skin_info' => 'MyAccount/skin_info',
96 'access_info' => 'MyAccount/access_info',
97 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
98 'new_customer' => 'Signup/new_customer',
99 'new_customer_minimal' => 'Signup/new_customer_minimal',
100 'capture_payment' => 'Signup/capture_payment',
101 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
102 'new_agent' => 'Agent/new_agent',
103 'agent_login' => 'Agent/agent_login',
104 'agent_logout' => 'Agent/agent_logout',
105 'agent_info' => 'Agent/agent_info',
106 'agent_list_customers' => 'Agent/agent_list_customers',
107 'check_username' => 'Agent/check_username',
108 'suspend_username' => 'Agent/suspend_username',
109 'unsuspend_username' => 'Agent/unsuspend_username',
110 'mason_comp' => 'MasonComponent/mason_comp',
111 'call_time' => 'PrepaidPhone/call_time',
112 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
113 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
115 'start_thirdparty' => 'MyAccount/start_thirdparty',
116 'finish_thirdparty' => 'MyAccount/finish_thirdparty',
118 'list_quotations' => 'MyAccount/quotation/list_quotations',
119 'quotation_new' => 'MyAccount/quotation/quotation_new',
120 'quotation_delete' => 'MyAccount/quotation/quotation_delete',
121 'quotation_info' => 'MyAccount/quotation/quotation_info',
122 'quotation_print' => 'MyAccount/quotation/quotation_print',
123 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg',
124 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg',
125 'quotation_order' => 'MyAccount/quotation/quotation_order',
130 qw( regionselector regionselector_hashref location_form
131 expselect popselector domainselector didselector
135 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
136 $ENV{'SHELL'} = '/bin/sh';
137 $ENV{'IFS'} = " \t\n";
140 $ENV{'BASH_ENV'} = '';
142 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
143 #if you grant appropriate permissions to whatever user
144 my $freeside_uid = scalar(getpwnam('freeside'));
145 die "not running as the freeside user\n"
146 if $> != $freeside_uid && ! $skip_uid_check;
148 -e $dir or die "FATAL: $dir doesn't exist!";
149 -d $dir or die "FATAL: $dir isn't a directory!";
150 -r $dir or die "FATAL: Can't read $dir as freeside user!";
151 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
153 foreach my $autoload ( keys %autoload ) {
156 "sub $autoload { ". '
161 #warn scalar(@_). ": ". join(" / ", @_);
165 $param->{_packet} = \''. $autoload{$autoload}. '\';
167 simple_packet($param);
177 warn "sending ". $packet->{_packet}. " to server"
179 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
180 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
181 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
184 #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
186 #block until there is a message on socket
187 # my $w = new IO::Select;
189 # my @wait = $w->can_read;
191 warn "reading message from server"
194 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
195 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
197 warn "returning message to client"
205 FS::SelfService - Freeside self-service API
209 # password and shell account changes
210 use FS::SelfService qw(passwd chfn chsh);
212 # "my account" functionality
213 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
215 #new-style login with an email address and password
216 # can also be used for svc_acct login, set $emailaddress to username@domain
217 my $rv = login ( { 'email' => $emailaddress,
218 'password' => $password,
221 if ( $rv->{'error'} ) {
222 #handle login error...
225 $session_id = $rv->{'session_id'};
228 #classic svc_acct-based login with separate username and password
229 my $rv = login( { 'username' => $username,
231 'password' => $password,
234 if ( $rv->{'error'} ) {
235 #handle login error...
238 $session_id = $rv->{'session_id'};
241 #svc_phone login with phone number and PIN
242 my $rv = login( { 'username' => $phone_number,
243 'domain' => 'svc_phone',
247 if ( $rv->{'error'} ) {
248 #handle login error...
251 $session_id = $rv->{'session_id'};
254 my $customer_info = customer_info( { 'session_id' => $session_id } );
256 #payment_info and process_payment are available in 1.5+ only
257 my $payment_info = payment_info( { 'session_id' => $session_id } );
259 #!!! process_payment example
261 #!!! list_pkgs example
263 #!!! order_pkg example
265 #quoting a package, then ordering after confirmation
267 my $rv = quotation_new({ 'session_id' => $session_id });
268 my $qnum = $rv->{quotationnum};
269 # add packages to the quotation
270 $rv = quotation_add_pkg({ 'session_id' => $session_id,
271 'quotationnum' => $qnum,
272 'pkgpart' => $pkgpart,
273 'quantity' => $quantity, # defaults to 1
275 # repeat until all packages are added
276 # view the pricing information
277 $rv = quotation_info({ 'session_id' => $session_id,
278 'quotationnum' => $qnum,
280 print "Total setup charges: ".$rv->{total_setup}."\n".
281 "Total recurring charges: ".$rv->{total_recur}."\n";
282 # quotation_info also provides a detailed breakdown of charges, in
285 # ask customer for confirmation, then:
286 $rv = quotation_order({ 'session_id' => $session_id,
287 'quotationnum' => $qnum,
290 #!!! cancel_pkg example
292 # signup functionality
293 use FS::SelfService qw( signup_info new_customer new_customer_minimal );
295 my $signup_info = signup_info;
297 $rv = new_customer( {
300 'company' => $company,
301 'address1' => $address1,
302 'address2' => $address2,
306 'country' => $country,
307 'daytime' => $daytime,
311 'payinfo' => $payinfo,
313 'paystart_month' => $paystart_month
314 'paystart_year' => $paystart_year,
315 'payissue' => $payissue,
317 'paydate' => $paydate,
318 'payname' => $payname,
319 'invoicing_list' => $invoicing_list,
320 'referral_custnum' => $referral_custnum,
321 'agentnum' => $agentnum,
322 'pkgpart' => $pkgpart,
324 'username' => $username,
325 '_password' => $password,
329 'phonenum' => $phonenum,
334 my $error = $rv->{'error'};
335 if ( $error eq '_decline' ) {
345 Use this API to implement your own client "self-service" module.
347 If you just want to customize the look of the existing "self-service" module,
350 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
356 Changes the password for an existing user in svc_acct. Takes a hash
357 reference with the following keys:
363 Username of the account (required)
367 Domain of the account (required)
371 Old password (required)
375 New password (required)
393 =head1 "MY ACCOUNT" FUNCTIONS
399 Creates a user session. Takes a hash reference as parameter with the
406 Email address (username@domain), instead of username and domain. Required for
407 contact-based self-service login, can also be used for svc_acct-based login.
423 Returns a hash reference with the following keys:
429 Empty on success, or an error message on errors.
433 Session identifier for successful logins
437 =item customer_info HASHREF
439 Returns general customer information.
441 Takes a hash reference as parameter with a single key: B<session_id>
443 Returns a hash reference with the following keys:
457 Array reference of hash references of open inoices. Each hash reference has
458 the following keys: invnum, date, owed
462 An HTML fragment containing shipping and billing addresses.
464 =item The following fields are also returned
466 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
470 =item edit_info HASHREF
472 Takes a hash reference as parameter with any of the following keys:
474 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
476 If a field exists, the customer record is updated with the new value of that
477 field. If a field does not exist, that field is not changed on the customer
480 Returns a hash reference with a single key, B<error>, empty on success, or an
481 error message on errors
483 =item invoice HASHREF
485 Returns an invoice. Takes a hash reference as parameter with two keys:
486 session_id and invnum
488 Returns a hash reference with the following keys:
494 Empty on success, or an error message on errors
506 =item list_invoices HASHREF
508 Returns a list of all customer invoices. Takes a hash references with a single
511 Returns a hash reference with the following keys:
517 Empty on success, or an error message on errors
521 Reference to array of hash references with the following keys:
531 Invoice date, in UNIX epoch time
539 Cancels this customer.
541 Takes a hash reference as parameter with a single key: B<session_id>
543 Returns a hash reference with a single key, B<error>, which is empty on
544 success or an error message on errors.
546 =item payment_info HASHREF
548 Returns information that may be useful in displaying a payment page.
550 Takes a hash reference as parameter with a single key: B<session_id>.
552 Returns a hash reference with the following keys:
558 Empty on success, or an error message on errors
566 Exact name on credit card (CARD/DCRD)
590 Customer's current default payment type.
594 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
598 For CARD/DCRD payment types, the card number
602 For CARD/DCRD payment types, expiration month
606 For CARD/DCRD payment types, expiration year
608 =item cust_main_county
610 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.
614 Array reference of all states in the current default country.
618 Hash reference of card types; keys are card types, values are the exact strings
619 passed to the process_payment function
623 #this doesn't actually work yet
627 #Unique transaction identifier (prevents multiple charges), passed to the
628 #process_payment function
632 =item process_payment HASHREF
634 Processes a payment and possible change of address or payment type. Takes a
635 hash reference as parameter with the following keys:
649 If true, address and card information entered will be saved for subsequent
654 If true, future credit card payments will be done automatically (sets payby to
655 CARD). If false, future credit card payments will be done on-demand (sets
656 payby to DCRD). This option only has meaning if B<save> is set true.
684 Two-letter country code
692 Card expiration month
700 #this doesn't actually work yet
704 #Unique transaction identifier, returned from the payment_info function.
705 #Prevents multiple charges.
709 Returns a hash reference with a single key, B<error>, empty on success, or an
710 error message on errors.
712 =item process_payment_order_pkg
714 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
715 payment processes sucessfully, the package is ordered. Takes a hash reference
716 as parameter with the keys of both methods.
718 Returns a hash reference with a single key, B<error>, empty on success, or an
719 error message on errors.
721 =item process_payment_change_pkg
723 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
724 payment processes sucessfully, the package is ordered. Takes a hash reference
725 as parameter with the keys of both methods.
727 Returns a hash reference with a single key, B<error>, empty on success, or an
728 error message on errors.
731 =item process_payment_order_renew
733 Combines the B<process_payment> and B<order_renew> functions in one step. If
734 the payment processes sucessfully, the renewal is processed. Takes a hash
735 reference as parameter with the keys of both methods.
737 Returns a hash reference with a single key, B<error>, empty on success, or an
738 error message on errors.
742 Returns package information for this customer. For more detail on services,
745 Takes a hash reference as parameter with a single key: B<session_id>
747 Returns a hash reference containing customer package information. The hash reference contains the following keys:
757 Empty on success, or an error message on errors.
759 =item cust_pkg HASHREF
761 Array reference of hash references, each of which has the fields of a cust_pkg
762 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
763 the internal FS:: objects, but hash references of columns and values.
767 =item part_pkg fields
769 All fields of part_pkg for this specific cust_pkg (be careful with this
770 information - it may reveal more about your available packages than you would
771 like users to know in aggregate)
775 #XXX pare part_pkg fields down to a more secure subset
779 An array of hash references indicating information on unprovisioned services
780 available for provisioning for this specific cust_pkg. Each has the following
785 =item part_svc fields
787 All fields of part_svc (be careful with this information - it may reveal more
788 about your available packages than you would like users to know in aggregate)
792 #XXX pare part_svc fields down to a more secure subset
798 An array of hash references indicating information on the customer services
799 already provisioned for this specific cust_pkg. Each has the following keys:
805 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.
811 Primary key for this service
815 Service definition (see L<FS::part_svc>)
819 Customer package (see L<FS::cust_pkg>)
823 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
831 Returns service information for this customer.
833 Takes a hash reference as parameter with a single key: B<session_id>
835 Returns a hash reference containing customer package information. The hash reference contains the following keys:
845 An array of hash references indicating information on all of this customer's
846 services. Each has the following keys:
852 Primary key for this service
860 Meaningful user-specific identifier for the service (i.e. username, domain, or
865 Account (svc_acct) services also have the following keys:
883 Upload bytes remaining
887 Download bytes remaining
891 Total bytes remaining
893 =item recharge_amount
897 =item recharge_seconds
899 Number of seconds gained by recharge
901 =item recharge_upbytes
903 Number of upload bytes gained by recharge
905 =item recharge_downbytes
907 Number of download bytes gained by recharge
909 =item recharge_totalbytes
911 Number of total bytes gained by recharge
919 Orders a package for this customer.
921 Takes a hash reference as parameter with the following keys:
931 Package to order (see L<FS::part_pkg>).
935 Quantity for this package order (default 1).
939 Optional locationnum for this package order, for existing locations.
941 Or, for new locations, pass the following fields: address1*, address2, city*,
942 county, state*, zip*, country. (* = required in this case)
954 Service to order (see L<FS::part_svc>).
956 Normally optional; required only to provision a non-svc_acct service, or if the
957 package definition does not contain one svc_acct service definition with
958 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
959 also be specified to indicate that no initial service should be provisioned.
963 Fields used when provisioning an svc_acct service:
977 Optional security phrase
981 Optional Access number number
985 Fields used when provisioning an svc_domain service:
995 Fields used when provisioning an svc_phone service:
1013 Fields used when provisioning an svc_external service:
1019 External numeric ID.
1023 External text title.
1027 Fields used when provisioning an svc_pbx service:
1041 Returns a hash reference with a single key, B<error>, empty on success, or an
1042 error message on errors. The special error '_decline' is returned for
1043 declined transactions.
1047 Changes a package for this customer.
1049 Takes a hash reference as parameter with the following keys:
1059 Existing customer package.
1063 New package to order (see L<FS::part_pkg>).
1067 Quantity for this package order (default 1).
1071 Returns a hash reference with the following keys:
1077 Empty on success, or an error message on errors.
1081 On success, the new pkgnum
1088 Provides useful info for early renewals.
1090 Takes a hash reference as parameter with the following keys:
1100 Returns a hash reference. On errors, it contains a single key, B<error>, with
1101 the error message. Otherwise, contains a single key, B<dates>, pointing to
1102 an array refernce of hash references. Each hash reference contains the
1109 (Future) Bill date. Indicates a future date for which billing could be run.
1110 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
1113 =item bill_date_pretty
1115 (Future) Bill date as a human-readable string. (Convenience for display;
1116 subject to change, so best not to parse for the date.)
1120 Base amount which will be charged if renewed early as of this date.
1124 Renewal date; i.e. even-futher future date at which the customer will be paid
1125 through if the early renewal is completed with the given B<bill-date>.
1126 Specified as a integer UNIX timestamp.
1128 =item renew_date_pretty
1130 Renewal date as a human-readable string. (Convenience for display;
1131 subject to change, so best not to parse for the date.)
1135 Package that will be renewed.
1139 Expiration date of the package that will be renewed.
1141 =item expire_date_pretty
1143 Expiration date of the package that will be renewed, as a human-readable
1144 string. (Convenience for display; subject to change, so best not to parse for
1151 Renews this customer early; i.e. runs billing for this customer in advance.
1153 Takes a hash reference as parameter with the following keys:
1163 Integer date as returned by the B<renew_info> function, indicating the advance
1164 date for which to run billing.
1168 Returns a hash reference with a single key, B<error>, empty on success, or an
1169 error message on errors.
1173 Cancels a package for this customer.
1175 Takes a hash reference as parameter with the following keys:
1185 pkgpart of package to cancel
1189 Returns a hash reference with a single key, B<error>, empty on success, or an
1190 error message on errors.
1192 =item provision_acct
1194 Provisions an account (svc_acct).
1196 Takes a hash references as parameter with the following keys:
1206 pkgnum of package into which this service is provisioned
1210 svcpart or service definition to provision
1220 =item provision_phone
1222 Provisions a phone number (svc_phone).
1224 Takes a hash references as parameter with the following keys:
1234 pkgnum of package into which this service is provisioned
1238 svcpart or service definition to provision
1258 E911 Address (optional)
1262 =item provision_external
1264 Provisions an external service (svc_external).
1266 Takes a hash references as parameter with the following keys:
1276 pkgnum of package into which this service is provisioned
1280 svcpart or service definition to provision
1290 =head2 "MY ACCOUNT" QUOTATION FUNCTIONS
1292 All of these functions require the user to be logged in, and the 'session_id'
1293 key to be included in the argument hashref.`
1297 =item list_quotations HASHREF
1299 Returns a hashref listing this customer's active self-service quotations.
1302 - 'quotations', an arrayref containing an element for each quotation.
1303 - quotationnum, the primary key
1304 - _date, the date it was started
1305 - num_pkgs, the number of packages
1306 - total_setup, the sum of setup fees
1307 - total_recur, the sum of recurring charges
1309 =item quotation_new HASHREF
1311 Creates an empty quotation and returns a hashref containing 'quotationnum',
1312 the primary key of the new quotation.
1314 =item quotation_delete HASHREF
1316 Disables (does not really delete) a quotation. Takes the following arguments:
1322 =item quotationnum - the quotation to delete
1326 Returns 'error' => a string, which will be empty on success.
1328 =item quotation_info HASHREF
1330 Returns total and detailed pricing information on a quotation.
1332 Takes the following arguments:
1338 =item quotationnum - the quotation to return
1342 Returns a hashref containing:
1344 - total_setup, the total of setup fees (and their taxes)
1345 - total_recur, the total of all recurring charges (and their taxes)
1346 - sections, an arrayref containing an element for each quotation section.
1347 - description, a line of text describing the group of charges
1348 - subtotal, the total of charges in this group (if appropriate)
1349 - detail_items, an arrayref of line items
1350 - pkgnum, the reference number of the package
1351 - description, the package name (or tax name)
1353 - amount, the amount charged
1354 If the detail item represents a subtotal, it will instead contain:
1355 - total_item: description of the subtotal
1356 - total_amount: the subtotal amount
1359 =item quotation_print HASHREF
1361 Renders the quotation as HTML or PDF. Takes the following arguments:
1367 =item quotationnum - the quotation to return
1369 =item format - 'html' or 'pdf'
1373 Returns a hashref containing 'document', the contents of the file.
1375 =item quotation_add_pkg HASHREF
1377 Adds a package to a quotation. Takes the following arguments:
1383 =item pkgpart - the package to add
1385 =item quotationnum - the quotation to add it to
1387 =item quantity - the package quantity (defaults to 1)
1389 =item address1, address2, city, state, zip, country - address fields to set
1390 the service location
1394 Returns 'error' => a string, which will be empty on success.
1396 =item quotation_remove_pkg HASHREF
1398 Removes a package from a quotation. Takes the following arguments:
1404 =item pkgnum - the primary key (quotationpkgnum) of the package to remove
1406 =item quotationnum - the quotation to remove it from
1410 Returns 'error' => a string, which will be empty on success.
1414 =item quotation_order HASHREF
1416 Converts the packages in a quotation into real packages. Takes the following
1419 Takes the following arguments:
1425 =item quotationnum - the quotation to order
1431 =head1 SIGNUP FUNCTIONS
1435 =item signup_info HASHREF
1437 Takes a hash reference as parameter with the following keys:
1441 =item session_id - Optional agent/reseller interface session
1445 Returns a hash reference containing information that may be useful in
1446 displaying a signup page. The hash reference contains the following keys:
1450 =item cust_main_county
1452 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.
1456 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
1457 an agentnum specified explicitly via reseller interface session_id in the
1462 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.
1464 =item agentnum2part_pkg
1466 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.
1470 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.
1472 =item security_phrase
1474 True if the "security_phrase" feature is enabled
1478 Array reference of acceptable payment types for signup
1484 credit card - automatic
1488 credit card - on-demand - version 1.5+ only
1492 electronic check - automatic
1496 electronic check - on-demand - version 1.5+ only
1504 billing, not recommended for signups
1508 free, definitely not recommended for signups
1512 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1518 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1522 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".
1528 =item countrydefault
1534 =item new_customer_minimal HASHREF
1536 Creates a new customer.
1538 Current differences from new_customer: An address is not required. promo_code
1539 and reg_code are not supported. If invoicing_list and _password is passed, a
1540 contact will be created with self-service access (no pkgpart or username is
1541 necessary). No initial billing is run (this may change in a future version).
1543 Takes a hash reference as parameter with the following keys:
1549 first name (required)
1553 last name (required)
1557 (not typically collected; mostly used for ACH transactions)
1589 Daytime phone number
1593 Evening phone number
1601 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1605 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1609 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1613 Expiration date for CARD/DCRD
1617 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1619 =item invoicing_list
1621 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),
1623 =item referral_custnum
1625 referring customer number
1633 pkgpart of initial package
1649 Access number (index, not the literal number)
1653 Country code (to be provisioned as a service)
1657 Phone number (to be provisioned as a service)
1665 Returns a hash reference with the following keys:
1671 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)
1675 =item new_customer HASHREF
1677 Creates a new customer. Takes a hash reference as parameter with the
1684 first name (required)
1688 last name (required)
1692 (not typically collected; mostly used for ACH transactions)
1698 =item address1 (required)
1706 =item city (required)
1714 =item state (required)
1718 =item zip (required)
1724 Daytime phone number
1728 Evening phone number
1736 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1740 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1744 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1748 Expiration date for CARD/DCRD
1752 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1754 =item invoicing_list
1756 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),
1758 =item referral_custnum
1760 referring customer number
1768 pkgpart of initial package
1784 Access number (index, not the literal number)
1788 Country code (to be provisioned as a service)
1792 Phone number (to be provisioned as a service)
1800 Returns a hash reference with the following keys:
1806 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)
1810 =item regionselector HASHREF | LIST
1812 Takes as input a hashref or list of key/value pairs with the following keys:
1816 =item selected_county
1818 Currently selected county
1820 =item selected_state
1822 Currently selected state
1824 =item selected_country
1826 Currently selected country
1830 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1834 Specify a javascript subroutine to call on changes
1840 =item default_country
1846 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>.
1850 Returns a list consisting of three HTML fragments for county selection,
1851 state selection and country selection, respectively.
1855 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1856 sub regionselector {
1863 $param->{'selected_country'} ||= $param->{'default_country'};
1864 $param->{'selected_state'} ||= $param->{'default_state'};
1866 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1870 my %cust_main_county;
1872 # unless ( @cust_main_county ) { #cache
1873 #@cust_main_county = qsearch('cust_main_county', {} );
1874 #foreach my $c ( @cust_main_county ) {
1875 foreach my $c ( @{ $param->{'locales'} } ) {
1876 #$countyflag=1 if $c->county;
1877 $countyflag=1 if $c->{county};
1878 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1879 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1880 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1883 $countyflag=1 if $param->{selected_county};
1885 my $script_html = <<END;
1887 function opt(what,value,text) {
1888 var optionName = new Option(text, value, false, false);
1889 var length = what.length;
1890 what.options[length] = optionName;
1892 function ${prefix}country_changed(what) {
1893 country = what.options[what.selectedIndex].text;
1894 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1895 what.form.${prefix}state.options[i] = null;
1897 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1899 foreach my $country ( sort keys %cust_main_county ) {
1900 $script_html .= "\nif ( country == \"$country\" ) {\n";
1901 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1902 my $text = $state || '(n/a)';
1903 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1905 $script_html .= "}\n";
1908 $script_html .= <<END;
1910 function ${prefix}state_changed(what) {
1913 if ( $countyflag ) {
1914 $script_html .= <<END;
1915 state = what.options[what.selectedIndex].text;
1916 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1917 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1918 what.form.${prefix}county.options[i] = null;
1921 foreach my $country ( sort keys %cust_main_county ) {
1922 $script_html .= "\nif ( country == \"$country\" ) {\n";
1923 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1924 $script_html .= "\nif ( state == \"$state\" ) {\n";
1925 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1926 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1927 my $text = $county || '(n/a)';
1929 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1931 $script_html .= "}\n";
1933 $script_html .= "}\n";
1937 $script_html .= <<END;
1942 my $county_html = $script_html;
1943 if ( $countyflag ) {
1944 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1945 foreach my $county (
1946 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1948 my $text = $county || '(n/a)';
1949 $county_html .= qq!<OPTION VALUE="$county"!.
1950 ($county eq $param->{'selected_county'} ?
1957 $county_html .= '</SELECT>';
1960 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1963 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1964 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1965 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1966 my $text = $state || '(n/a)';
1967 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1968 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
1970 $state_html .= '</SELECT>';
1972 my $country_html = '';
1973 if ( scalar( keys %cust_main_county ) > 1 ) {
1975 $country_html = qq(<SELECT NAME="${prefix}country" ).
1976 qq(onChange="${prefix}country_changed(this); ).
1977 $param->{'onchange'}.
1980 my $countrydefault = $param->{default_country} || 'US';
1981 foreach my $country (
1982 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
1983 keys %cust_main_county
1985 my $selected = $country eq $param->{'selected_country'}
1988 $country_html .= "\n<OPTION$selected>$country</OPTION>"
1990 $country_html .= '</SELECT>';
1993 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
1994 ' VALUE="'. (keys %cust_main_county )[0]. '">';
1998 ($county_html, $state_html, $country_html);
2002 sub regionselector_hashref {
2003 my ($county_html, $state_html, $country_html) = regionselector(@_);
2005 'county_html' => $county_html,
2006 'state_html' => $state_html,
2007 'country_html' => $country_html,
2011 =item location_form HASHREF | LIST
2013 Takes as input a hashref or list of key/value pairs with the following keys:
2019 Current customer session_id
2023 Omit red asterisks from required fields.
2025 =item address1_label
2027 Label for first address line.
2031 Returns an HTML fragment for a location form (address, city, state, zip,
2044 my $session_id = delete $param->{'session_id'};
2046 my $rv = mason_comp( 'session_id' => $session_id,
2047 'comp' => '/elements/location.html',
2048 'args' => [ %$param ],
2052 $rv->{'error'} || $rv->{'output'};
2057 #=item expselect HASHREF | LIST
2059 #Takes as input a hashref or list of key/value pairs with the following keys:
2063 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
2065 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
2069 =item expselect PREFIX [ DATE ]
2071 Takes as input a unique prefix string and the current expiration date, in
2072 yyyy-mm-dd or m-d-yyyy format
2074 Returns an HTML fragments for expiration date selection.
2080 #if ( ref($_[0]) ) {
2084 #my $prefix = $param->{'prefix'};
2085 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2086 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
2088 my $date = scalar(@_) ? shift : '';
2090 my( $m, $y ) = ( 0, 0 );
2091 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
2092 ( $m, $y ) = ( $2, $1 );
2093 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
2094 ( $m, $y ) = ( $1, $3 );
2096 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
2098 $return .= qq!<OPTION VALUE="$_"!;
2099 $return .= " SELECTED" if $_ == $m;
2102 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
2104 my $thisYear = $t[5] + 1900;
2105 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
2106 $return .= qq!<OPTION VALUE="$_"!;
2107 $return .= " SELECTED" if $_ == $y;
2110 $return .= "</SELECT>";
2115 =item popselector HASHREF | LIST
2117 Takes as input a hashref or list of key/value pairs with the following keys:
2123 Access number number
2127 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>.
2131 Returns an HTML fragment for access number selection.
2135 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
2143 my $popnum = $param->{'popnum'};
2144 my $pops = $param->{'pops'};
2146 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2147 return $pops->[0]{city}. ', '. $pops->[0]{state}.
2148 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2149 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2150 if scalar(@$pops) == 1;
2153 my %popnum2pop = ();
2155 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2156 $popnum2pop{$_->{popnum}} = $_;
2161 function opt(what,href,text) {
2162 var optionName = new Option(text, href, false, false)
2163 var length = what.length;
2164 what.options[length] = optionName;
2168 my $init_popstate = $param->{'init_popstate'};
2169 if ( $init_popstate ) {
2170 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2171 $init_popstate. '">';
2174 function acstate_changed(what) {
2175 state = what.options[what.selectedIndex].text;
2176 what.form.popac.options.length = 0
2177 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2181 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2182 foreach my $state ( sort { $a cmp $b } @states ) {
2183 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2185 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2186 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2187 if ($ac eq $param->{'popac'}) {
2188 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2191 $text .= "}\n" unless $init_popstate;
2193 $text .= "popac_changed(what.form.popac)}\n";
2196 function popac_changed(what) {
2197 ac = what.options[what.selectedIndex].text;
2198 what.form.popnum.options.length = 0;
2199 what.form.popnum.options[0] = new Option("City", "-1", false, true);
2203 foreach my $state ( @states ) {
2204 foreach my $popac ( keys %{ $pop{$state} } ) {
2205 $text .= "\nif ( ac == \"$popac\" ) {\n";
2207 foreach my $pop ( @{$pop{$state}->{$popac}}) {
2208 my $o_popnum = $pop->{popnum};
2209 my $poptext = $pop->{city}. ', '. $pop->{state}.
2210 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2212 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2213 if ($popnum == $o_popnum) {
2214 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2222 $text .= "}\n</SCRIPT>\n";
2224 $param->{'acstate'} = '' unless defined($param->{'acstate'});
2227 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2228 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2229 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2230 ">$_" foreach sort { $a cmp $b } @states;
2231 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
2234 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2235 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2237 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2240 #comment this block to disable initial list polulation
2241 my @initial_select = ();
2242 if ( scalar( @$pops ) > 100 ) {
2243 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2245 @initial_select = @$pops;
2247 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2248 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2249 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2250 $pop->{city}. ', '. $pop->{state}.
2251 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2254 $text .= qq!</SELECT></TD></TR></TABLE>!;
2260 =item domainselector HASHREF | LIST
2262 Takes as input a hashref or list of key/value pairs with the following keys:
2272 Service number of the selected item.
2276 Returns an HTML fragment for domain selection.
2280 sub domainselector {
2287 my $domsvc= $param->{'domsvc'};
2289 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2290 my $domains = $rv->{'domains'};
2291 $domsvc = $rv->{'domsvc'} unless $domsvc;
2293 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2294 unless scalar(keys %$domains);
2296 if (scalar(keys %$domains) == 1) {
2298 foreach(keys %$domains) {
2301 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2302 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2305 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2307 $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2309 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2310 $text .= qq!<OPTION VALUE="!. $domain. '"'.
2311 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2312 $domains->{$domain};
2315 $text .= qq!</SELECT></TD></TR>!;
2321 =item didselector HASHREF | LIST
2323 Takes as input a hashref or list of key/value pairs with the following keys:
2329 Field name for the returned HTML fragment.
2333 Service definition (see L<FS::part_svc>)
2337 Returns an HTML fragment for DID selection.
2349 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2350 'args'=>[ %$param ],
2354 $rv->{'error'} || $rv->{'output'};
2360 =head1 RESELLER FUNCTIONS
2362 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2363 with their active session, and the B<customer_info> and B<order_pkg> functions
2364 with their active session and an additional I<custnum> parameter.
2366 For the most part, development of the reseller web interface has been
2367 superceded by agent-virtualized access to the backend.
2379 =item agent_list_customers
2381 List agent's customers.
2389 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>