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',
36 'contact_passwd' => 'MyAccount/contact/contact_passwd',
37 'list_contacts' => 'MyAccount/contact/list_contacts',
38 'edit_contact' => 'MyAccount/contact/edit_contact',
39 'delete_contact' => 'MyAccount/contact/delete_contact',
41 'billing_history' => 'MyAccount/billing_history',
42 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
43 'invoice' => 'MyAccount/invoice',
44 'invoice_pdf' => 'MyAccount/invoice_pdf',
45 'legacy_invoice' => 'MyAccount/legacy_invoice',
46 'legacy_invoice_pdf' => 'MyAccount/legacy_invoice_pdf',
47 'invoice_logo' => 'MyAccount/invoice_logo',
48 'list_invoices' => 'MyAccount/list_invoices', #?
49 'cancel' => 'MyAccount/cancel', #add to ss cgi!
50 'payment_info' => 'MyAccount/payment_info',
51 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
52 'process_payment' => 'MyAccount/process_payment',
53 'store_payment' => 'MyAccount/store_payment',
54 'process_stored_payment' => 'MyAccount/process_stored_payment',
55 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
56 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
57 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
58 'process_prepay' => 'MyAccount/process_prepay',
59 'realtime_collect' => 'MyAccount/realtime_collect',
60 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
61 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
62 'list_svc_usage' => 'MyAccount/list_svc_usage',
63 'svc_status_html' => 'MyAccount/svc_status_html',
64 'svc_status_hash' => 'MyAccount/svc_status_hash',
65 'set_svc_status_hash' => 'MyAccount/set_svc_status_hash',
66 'set_svc_status_listadd' => 'MyAccount/set_svc_status_listadd',
67 'set_svc_status_listdel' => 'MyAccount/set_svc_status_listdel',
68 'set_svc_status_vacationadd'=> 'MyAccount/set_svc_status_vacationadd',
69 'set_svc_status_vacationdel'=> 'MyAccount/set_svc_status_vacationdel',
70 'acct_forward_info' => 'MyAccount/acct_forward_info',
71 'process_acct_forward' => 'MyAccount/process_acct_forward',
72 'list_dsl_devices' => 'MyAccount/list_dsl_devices',
73 'add_dsl_device' => 'MyAccount/add_dsl_device',
74 'delete_dsl_device' => 'MyAccount/delete_dsl_device',
75 'port_graph' => 'MyAccount/port_graph',
76 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
77 'list_support_usage' => 'MyAccount/list_support_usage',
78 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
79 'change_pkg' => 'MyAccount/change_pkg',
80 'order_recharge' => 'MyAccount/order_recharge',
81 'renew_info' => 'MyAccount/renew_info',
82 'order_renew' => 'MyAccount/order_renew',
83 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
84 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi!
85 'charge' => 'MyAccount/charge', #?
86 'part_svc_info' => 'MyAccount/part_svc_info',
87 'provision_acct' => 'MyAccount/provision_acct',
88 'provision_phone' => 'MyAccount/provision_phone',
89 'provision_pbx' => 'MyAccount/provision_pbx',
90 'provision_external' => 'MyAccount/provision_external',
91 'provision_forward' => 'MyAccount/provision_forward',
92 'unprovision_svc' => 'MyAccount/unprovision_svc',
93 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
94 'reset_passwd' => 'MyAccount/reset_passwd',
95 'check_reset_passwd' => 'MyAccount/check_reset_passwd',
96 'process_reset_passwd' => 'MyAccount/process_reset_passwd',
97 'list_tickets' => 'MyAccount/list_tickets',
98 'create_ticket' => 'MyAccount/create_ticket',
99 'get_ticket' => 'MyAccount/get_ticket',
100 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
101 'did_report' => 'MyAccount/did_report',
102 'signup_info' => 'Signup/signup_info',
103 'skin_info' => 'MyAccount/skin_info',
104 'access_info' => 'MyAccount/access_info',
105 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
106 'new_customer' => 'Signup/new_customer',
107 'new_customer_minimal' => 'Signup/new_customer_minimal',
108 'capture_payment' => 'Signup/capture_payment',
109 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
110 'new_agent' => 'Agent/new_agent',
111 'agent_login' => 'Agent/agent_login',
112 'agent_logout' => 'Agent/agent_logout',
113 'agent_info' => 'Agent/agent_info',
114 'agent_list_customers' => 'Agent/agent_list_customers',
115 'check_username' => 'Agent/check_username',
116 'suspend_username' => 'Agent/suspend_username',
117 'unsuspend_username' => 'Agent/unsuspend_username',
118 'mason_comp' => 'MasonComponent/mason_comp',
119 'call_time' => 'PrepaidPhone/call_time',
120 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
121 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
123 'start_thirdparty' => 'MyAccount/start_thirdparty',
124 'finish_thirdparty' => 'MyAccount/finish_thirdparty',
126 'list_quotations' => 'MyAccount/quotation/list_quotations',
127 'quotation_new' => 'MyAccount/quotation/quotation_new',
128 'quotation_delete' => 'MyAccount/quotation/quotation_delete',
129 'quotation_info' => 'MyAccount/quotation/quotation_info',
130 'quotation_print' => 'MyAccount/quotation/quotation_print',
131 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg',
132 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg',
133 'quotation_order' => 'MyAccount/quotation/quotation_order',
138 qw( regionselector regionselector_hashref location_form
139 expselect popselector domainselector didselector
143 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
144 $ENV{'SHELL'} = '/bin/sh';
145 $ENV{'IFS'} = " \t\n";
148 $ENV{'BASH_ENV'} = '';
150 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
151 #if you grant appropriate permissions to whatever user
152 my $freeside_uid = scalar(getpwnam('freeside'));
153 die "not running as the freeside user\n"
154 if $> != $freeside_uid && ! $skip_uid_check;
156 -e $dir or die "FATAL: $dir doesn't exist!";
157 -d $dir or die "FATAL: $dir isn't a directory!";
158 -r $dir or die "FATAL: Can't read $dir as freeside user!";
159 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
161 foreach my $autoload ( keys %autoload ) {
164 "sub $autoload { ". '
169 #warn scalar(@_). ": ". join(" / ", @_);
173 $param->{_packet} = \''. $autoload{$autoload}. '\';
175 simple_packet($param);
185 warn "sending ". $packet->{_packet}. " to server"
187 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
188 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
189 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
192 #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
194 #block until there is a message on socket
195 # my $w = new IO::Select;
197 # my @wait = $w->can_read;
199 warn "reading message from server"
202 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
203 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
205 warn "returning message to client"
213 FS::SelfService - Freeside self-service API
217 # password and shell account changes
218 use FS::SelfService qw(passwd chfn chsh);
220 # "my account" functionality
221 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
223 #new-style login with an email address and password
224 # can also be used for svc_acct login, set $emailaddress to username@domain
225 my $rv = login ( { 'email' => $emailaddress,
226 'password' => $password,
229 if ( $rv->{'error'} ) {
230 #handle login error...
233 $session_id = $rv->{'session_id'};
236 #classic svc_acct-based login with separate username and password
237 my $rv = login( { 'username' => $username,
239 'password' => $password,
242 if ( $rv->{'error'} ) {
243 #handle login error...
246 $session_id = $rv->{'session_id'};
249 #svc_phone login with phone number and PIN
250 my $rv = login( { 'username' => $phone_number,
251 'domain' => 'svc_phone',
255 if ( $rv->{'error'} ) {
256 #handle login error...
259 $session_id = $rv->{'session_id'};
262 my $customer_info = customer_info( { 'session_id' => $session_id } );
264 #payment_info and process_payment are available in 1.5+ only
265 my $payment_info = payment_info( { 'session_id' => $session_id } );
267 #!!! process_payment example
269 #!!! list_pkgs example
271 #!!! order_pkg example
273 #quoting a package, then ordering after confirmation
275 my $rv = quotation_new({ 'session_id' => $session_id });
276 my $qnum = $rv->{quotationnum};
277 # add packages to the quotation
278 $rv = quotation_add_pkg({ 'session_id' => $session_id,
279 'quotationnum' => $qnum,
280 'pkgpart' => $pkgpart,
281 'quantity' => $quantity, # defaults to 1
283 # repeat until all packages are added
284 # view the pricing information
285 $rv = quotation_info({ 'session_id' => $session_id,
286 'quotationnum' => $qnum,
288 print "Total setup charges: ".$rv->{total_setup}."\n".
289 "Total recurring charges: ".$rv->{total_recur}."\n";
290 # quotation_info also provides a detailed breakdown of charges, in
293 # ask customer for confirmation, then:
294 $rv = quotation_order({ 'session_id' => $session_id,
295 'quotationnum' => $qnum,
298 #!!! cancel_pkg example
300 # signup functionality
301 use FS::SelfService qw( signup_info new_customer new_customer_minimal );
303 my $signup_info = signup_info;
305 $rv = new_customer( {
308 'company' => $company,
309 'address1' => $address1,
310 'address2' => $address2,
314 'country' => $country,
315 'daytime' => $daytime,
319 'payinfo' => $payinfo,
321 'paystart_month' => $paystart_month
322 'paystart_year' => $paystart_year,
323 'payissue' => $payissue,
325 'paydate' => $paydate,
326 'payname' => $payname,
327 'invoicing_list' => $invoicing_list,
328 'referral_custnum' => $referral_custnum,
329 'agentnum' => $agentnum,
330 'pkgpart' => $pkgpart,
332 'username' => $username,
333 '_password' => $password,
337 'phonenum' => $phonenum,
342 my $error = $rv->{'error'};
343 if ( $error eq '_decline' ) {
353 Use this API to implement your own client "self-service" module.
355 If you just want to customize the look of the existing "self-service" module,
358 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
364 Changes the password for an existing user in svc_acct. Takes a hash
365 reference with the following keys:
371 Username of the account (required)
375 Domain of the account (required)
379 Old password (required)
383 New password (required)
401 =head1 "MY ACCOUNT" FUNCTIONS
407 Creates a user session. Takes a hash reference as parameter with the
414 Email address (username@domain), instead of username and domain. Required for
415 contact-based self-service login, can also be used for svc_acct-based login.
431 Returns a hash reference with the following keys:
437 Empty on success, or an error message on errors.
441 Session identifier for successful logins
445 =item customer_info HASHREF
447 Returns general customer information.
449 Takes a hash reference as parameter with a single key: B<session_id>
451 Returns a hash reference with the following keys:
465 Array reference of hash references of open inoices. Each hash reference has
466 the following keys: invnum, date, owed
470 An HTML fragment containing shipping and billing addresses.
472 =item The following fields are also returned
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 payname month year invoicing_list postal_invoicing
478 =item edit_info HASHREF
480 Takes a hash reference as parameter with any of the following keys:
482 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
484 If a field exists, the customer record is updated with the new value of that
485 field. If a field does not exist, that field is not changed on the customer
488 Returns a hash reference with a single key, B<error>, empty on success, or an
489 error message on errors
491 =item invoice HASHREF
493 Returns an invoice. Takes a hash reference as parameter with two keys:
494 session_id and invnum
496 Returns a hash reference with the following keys:
502 Empty on success, or an error message on errors
514 =item list_invoices HASHREF
516 Returns a list of all customer invoices. Takes a hash reference with a single
519 Returns a hash reference with the following keys:
525 Empty on success, or an error message on errors
529 Reference to array of hash references with the following keys:
539 Invoice date, in UNIX epoch time
547 Cancels this customer.
549 Takes a hash reference as parameter with a single key: B<session_id>
551 Returns a hash reference with a single key, B<error>, which is empty on
552 success or an error message on errors.
554 =item payment_info HASHREF
556 Returns information that may be useful in displaying a payment page.
558 Takes a hash reference as parameter with a single key: B<session_id>.
560 Returns a hash reference with the following keys:
566 Empty on success, or an error message on errors
574 Exact name on credit card (CARD/DCRD)
598 Customer's current default payment type.
602 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
606 For CARD/DCRD payment types, the card number
610 For CARD/DCRD payment types, expiration month
614 For CARD/DCRD payment types, expiration year
616 =item cust_main_county
618 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.
622 Array reference of all states in the current default country.
626 Hash reference of card types; keys are card types, values are the exact strings
627 passed to the process_payment function
631 #this doesn't actually work yet
635 #Unique transaction identifier (prevents multiple charges), passed to the
636 #process_payment function
640 =item process_payment HASHREF
642 Processes a payment and possible change of address or payment type. Takes a
643 hash reference as parameter with the following keys:
657 If true, address and card information entered will be saved for subsequent
662 If true, future credit card payments will be done automatically (sets payby to
663 CARD). If false, future credit card payments will be done on-demand (sets
664 payby to DCRD). This option only has meaning if B<save> is set true.
692 Two-letter country code
700 Card expiration month
708 #this doesn't actually work yet
712 #Unique transaction identifier, returned from the payment_info function.
713 #Prevents multiple charges.
717 Returns a hash reference with a single key, B<error>, empty on success, or an
718 error message on errors.
720 =item process_payment_order_pkg
722 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
723 payment processes sucessfully, the package is ordered. Takes a hash reference
724 as parameter with the keys of both methods.
726 Returns a hash reference with a single key, B<error>, empty on success, or an
727 error message on errors.
729 =item process_payment_change_pkg
731 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
732 payment processes sucessfully, the package is ordered. Takes a hash reference
733 as parameter with the keys of both methods.
735 Returns a hash reference with a single key, B<error>, empty on success, or an
736 error message on errors.
739 =item process_payment_order_renew
741 Combines the B<process_payment> and B<order_renew> functions in one step. If
742 the payment processes sucessfully, the renewal is processed. Takes a hash
743 reference as parameter with the keys of both methods.
745 Returns a hash reference with a single key, B<error>, empty on success, or an
746 error message on errors.
750 Returns package information for this customer. For more detail on services,
753 Takes a hash reference as parameter with a single key: B<session_id>
755 Returns a hash reference containing customer package information. The hash reference contains the following keys:
765 Empty on success, or an error message on errors.
767 =item cust_pkg HASHREF
769 Array reference of hash references, each of which has the fields of a cust_pkg
770 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
771 the internal FS:: objects, but hash references of columns and values.
775 =item part_pkg fields
777 All fields of part_pkg for this specific cust_pkg (be careful with this
778 information - it may reveal more about your available packages than you would
779 like users to know in aggregate)
783 #XXX pare part_pkg fields down to a more secure subset
787 An array of hash references indicating information on unprovisioned services
788 available for provisioning for this specific cust_pkg. Each has the following
793 =item part_svc fields
795 All fields of part_svc (be careful with this information - it may reveal more
796 about your available packages than you would like users to know in aggregate)
800 #XXX pare part_svc fields down to a more secure subset
806 An array of hash references indicating information on the customer services
807 already provisioned for this specific cust_pkg. Each has the following keys:
813 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.
819 Primary key for this service
823 Service definition (see L<FS::part_svc>)
827 Customer package (see L<FS::cust_pkg>)
831 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
839 Returns service information for this customer.
841 Takes a hash reference as parameter with a single key: B<session_id>
843 Returns a hash reference containing customer package information. The hash reference contains the following keys:
853 An array of hash references indicating information on all of this customer's
854 services. Each has the following keys:
860 Primary key for this service
868 Meaningful user-specific identifier for the service (i.e. username, domain, or
873 Account (svc_acct) services also have the following keys:
891 Upload bytes remaining
895 Download bytes remaining
899 Total bytes remaining
901 =item recharge_amount
905 =item recharge_seconds
907 Number of seconds gained by recharge
909 =item recharge_upbytes
911 Number of upload bytes gained by recharge
913 =item recharge_downbytes
915 Number of download bytes gained by recharge
917 =item recharge_totalbytes
919 Number of total bytes gained by recharge
927 Orders a package for this customer.
929 Takes a hash reference as parameter with the following keys:
939 Package to order (see L<FS::part_pkg>).
943 Quantity for this package order (default 1).
947 Optional locationnum for this package order, for existing locations.
949 Or, for new locations, pass the following fields: address1*, address2, city*,
950 county, state*, zip*, country. (* = required in this case)
962 Service to order (see L<FS::part_svc>).
964 Normally optional; required only to provision a non-svc_acct service, or if the
965 package definition does not contain one svc_acct service definition with
966 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
967 also be specified to indicate that no initial service should be provisioned.
971 Fields used when provisioning an svc_acct service:
985 Optional security phrase
989 Optional Access number number
993 Fields used when provisioning an svc_domain service:
1003 Fields used when provisioning an svc_phone service:
1021 Fields used when provisioning an svc_external service:
1027 External numeric ID.
1031 External text title.
1035 Fields used when provisioning an svc_pbx service:
1049 Returns a hash reference with a single key, B<error>, empty on success, or an
1050 error message on errors. The special error '_decline' is returned for
1051 declined transactions.
1055 Changes a package for this customer.
1057 Takes a hash reference as parameter with the following keys:
1067 Existing customer package.
1071 New package to order (see L<FS::part_pkg>).
1075 Quantity for this package order (default 1).
1079 Returns a hash reference with the following keys:
1085 Empty on success, or an error message on errors.
1089 On success, the new pkgnum
1096 Provides useful info for early renewals.
1098 Takes a hash reference as parameter with the following keys:
1108 Returns a hash reference. On errors, it contains a single key, B<error>, with
1109 the error message. Otherwise, contains a single key, B<dates>, pointing to
1110 an array refernce of hash references. Each hash reference contains the
1117 (Future) Bill date. Indicates a future date for which billing could be run.
1118 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
1121 =item bill_date_pretty
1123 (Future) Bill date as a human-readable string. (Convenience for display;
1124 subject to change, so best not to parse for the date.)
1128 Base amount which will be charged if renewed early as of this date.
1132 Renewal date; i.e. even-futher future date at which the customer will be paid
1133 through if the early renewal is completed with the given B<bill-date>.
1134 Specified as a integer UNIX timestamp.
1136 =item renew_date_pretty
1138 Renewal date as a human-readable string. (Convenience for display;
1139 subject to change, so best not to parse for the date.)
1143 Package that will be renewed.
1147 Expiration date of the package that will be renewed.
1149 =item expire_date_pretty
1151 Expiration date of the package that will be renewed, as a human-readable
1152 string. (Convenience for display; subject to change, so best not to parse for
1159 Renews this customer early; i.e. runs billing for this customer in advance.
1161 Takes a hash reference as parameter with the following keys:
1171 Integer date as returned by the B<renew_info> function, indicating the advance
1172 date for which to run billing.
1176 Returns a hash reference with a single key, B<error>, empty on success, or an
1177 error message on errors.
1181 Cancels a package for this customer.
1183 Takes a hash reference as parameter with the following keys:
1193 pkgpart of package to cancel
1197 Returns a hash reference with a single key, B<error>, empty on success, or an
1198 error message on errors.
1200 =item provision_acct
1202 Provisions an account (svc_acct).
1204 Takes a hash reference as parameter with the following keys:
1214 pkgnum of package into which this service is provisioned
1218 svcpart or service definition to provision
1228 =item provision_phone
1230 Provisions a phone number (svc_phone).
1232 Takes a hash reference as parameter with the following keys:
1242 pkgnum of package into which this service is provisioned
1246 svcpart or service definition to provision
1266 E911 Address (optional)
1272 Provisions a customer PBX (svc_pbx).
1274 Takes a hash reference as parameter with the following keys:
1284 pkgnum of package into which this service is provisioned
1288 svcpart or service definition to provision
1294 =item max_extensions
1296 =item max_simultaneous
1302 =item provision_external
1304 Provisions an external service (svc_external).
1306 Takes a hash reference as parameter with the following keys:
1316 pkgnum of package into which this service is provisioned
1320 svcpart or service definition to provision
1330 =head2 "MY ACCOUNT" CONTACT FUNCTIONS
1334 =item contact_passwd
1336 Changes the password for the currently-logged in contact.
1338 Takes a hash reference as parameter with the following keys:
1348 Returns a hash reference with a single parameter, B<error>, which contains an
1349 error message, or empty on success.
1355 Updates information for the currently-logged in contact, or (optionally) the
1358 Takes a hash reference as parameter with the following keys:
1366 If already logged in as a contact, this is optional.
1376 Returns a hash reference with a single parameter, B<error>, which contains an
1377 error message, or empty on success.
1379 =item delete_contact
1381 =head2 "MY ACCOUNT" QUOTATION FUNCTIONS
1383 All of these functions require the user to be logged in, and the 'session_id'
1384 key to be included in the argument hashref.`
1388 =item list_quotations HASHREF
1390 Returns a hashref listing this customer's active self-service quotations.
1397 an arrayref containing an element for each quotation.
1405 the date it was started
1409 the number of packages
1413 the sum of setup fees
1417 the sum of recurring charges
1421 =item quotation_new HASHREF
1423 Creates an empty quotation and returns a hashref containing 'quotationnum',
1424 the primary key of the new quotation.
1426 =item quotation_delete HASHREF
1428 Disables (does not really delete) a quotation. Takes the following arguments:
1434 =item quotationnum - the quotation to delete
1438 Returns 'error' => a string, which will be empty on success.
1440 =item quotation_info HASHREF
1442 Returns total and detailed pricing information on a quotation.
1444 Takes the following arguments:
1450 =item quotationnum - the quotation to return
1454 Returns a hashref containing:
1456 - total_setup, the total of setup fees (and their taxes)
1457 - total_recur, the total of all recurring charges (and their taxes)
1458 - sections, an arrayref containing an element for each quotation section.
1459 - description, a line of text describing the group of charges
1460 - subtotal, the total of charges in this group (if appropriate)
1461 - detail_items, an arrayref of line items
1462 - pkgnum, the reference number of the package
1463 - description, the package name (or tax name)
1465 - amount, the amount charged
1466 If the detail item represents a subtotal, it will instead contain:
1467 - total_item: description of the subtotal
1468 - total_amount: the subtotal amount
1471 =item quotation_print HASHREF
1473 Renders the quotation as HTML or PDF. Takes the following arguments:
1479 =item quotationnum - the quotation to return
1481 =item format - 'html' or 'pdf'
1485 Returns a hashref containing 'document', the contents of the file.
1487 =item quotation_add_pkg HASHREF
1489 Adds a package to a quotation. Takes the following arguments:
1495 =item pkgpart - the package to add
1497 =item quotationnum - the quotation to add it to
1499 =item quantity - the package quantity (defaults to 1)
1501 =item address1, address2, city, state, zip, country - address fields to set
1502 the service location
1506 Returns 'error' => a string, which will be empty on success.
1508 =item quotation_remove_pkg HASHREF
1510 Removes a package from a quotation. Takes the following arguments:
1516 =item pkgnum - the primary key (quotationpkgnum) of the package to remove
1518 =item quotationnum - the quotation to remove it from
1522 Returns 'error' => a string, which will be empty on success.
1526 =item quotation_order HASHREF
1528 Converts the packages in a quotation into real packages. Takes the following
1531 Takes the following arguments:
1537 =item quotationnum - the quotation to order
1543 =head1 SIGNUP FUNCTIONS
1547 =item signup_info HASHREF
1549 Takes a hash reference as parameter with the following keys:
1553 =item session_id - Optional agent/reseller interface session
1557 Returns a hash reference containing information that may be useful in
1558 displaying a signup page. The hash reference contains the following keys:
1562 =item cust_main_county
1564 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.
1568 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
1569 an agentnum specified explicitly via reseller interface session_id in the
1574 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.
1576 =item agentnum2part_pkg
1578 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.
1582 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.
1584 =item security_phrase
1586 True if the "security_phrase" feature is enabled
1590 Array reference of acceptable payment types for signup
1596 credit card - automatic
1600 credit card - on-demand - version 1.5+ only
1604 electronic check - automatic
1608 electronic check - on-demand - version 1.5+ only
1616 billing, not recommended for signups
1620 free, definitely not recommended for signups
1624 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1630 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1634 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".
1640 =item countrydefault
1646 =item new_customer_minimal HASHREF
1648 Creates a new customer.
1650 Current differences from new_customer: An address is not required. promo_code
1651 and reg_code are not supported. If invoicing_list and _password is passed, a
1652 contact will be created with self-service access (no pkgpart or username is
1653 necessary). No initial billing is run (this may change in a future version).
1655 Takes a hash reference as parameter with the following keys:
1661 first name (required)
1665 last name (required)
1669 (not typically collected; mostly used for ACH transactions)
1701 Daytime phone number
1705 Evening phone number
1713 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1717 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1721 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1725 Expiration date for CARD/DCRD
1729 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1731 =item invoicing_list
1733 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),
1735 =item referral_custnum
1737 referring customer number
1745 pkgpart of initial package
1761 Access number (index, not the literal number)
1765 Country code (to be provisioned as a service)
1769 Phone number (to be provisioned as a service)
1777 Returns a hash reference with the following keys:
1783 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)
1787 =item new_customer HASHREF
1789 Creates a new customer. Takes a hash reference as parameter with the
1796 first name (required)
1800 last name (required)
1804 (not typically collected; mostly used for ACH transactions)
1810 =item address1 (required)
1818 =item city (required)
1826 =item state (required)
1830 =item zip (required)
1836 Daytime phone number
1840 Evening phone number
1848 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1852 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1856 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1860 Expiration date for CARD/DCRD
1864 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1866 =item invoicing_list
1868 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),
1870 =item referral_custnum
1872 referring customer number
1880 pkgpart of initial package
1896 Access number (index, not the literal number)
1900 Country code (to be provisioned as a service)
1904 Phone number (to be provisioned as a service)
1912 Returns a hash reference with the following keys:
1918 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)
1922 =item regionselector HASHREF | LIST
1924 Takes as input a hashref or list of key/value pairs with the following keys:
1928 =item selected_county
1930 Currently selected county
1932 =item selected_state
1934 Currently selected state
1936 =item selected_country
1938 Currently selected country
1942 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1946 Specify a javascript subroutine to call on changes
1952 =item default_country
1958 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>.
1962 Returns a list consisting of three HTML fragments for county selection,
1963 state selection and country selection, respectively.
1967 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1968 sub regionselector {
1975 $param->{'selected_country'} ||= $param->{'default_country'};
1976 $param->{'selected_state'} ||= $param->{'default_state'};
1978 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1982 my %cust_main_county;
1984 # unless ( @cust_main_county ) { #cache
1985 #@cust_main_county = qsearch('cust_main_county', {} );
1986 #foreach my $c ( @cust_main_county ) {
1987 foreach my $c ( @{ $param->{'locales'} } ) {
1988 #$countyflag=1 if $c->county;
1989 $countyflag=1 if $c->{county};
1990 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1991 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1992 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1995 $countyflag=1 if $param->{selected_county};
1997 my $script_html = <<END;
1999 function opt(what,value,text) {
2000 var optionName = new Option(text, value, false, false);
2001 var length = what.length;
2002 what.options[length] = optionName;
2004 function ${prefix}country_changed(what) {
2005 country = what.options[what.selectedIndex].text;
2006 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
2007 what.form.${prefix}state.options[i] = null;
2009 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
2011 foreach my $country ( sort keys %cust_main_county ) {
2012 $script_html .= "\nif ( country == \"$country\" ) {\n";
2013 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
2014 my $text = $state || '(n/a)';
2015 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
2017 $script_html .= "}\n";
2020 $script_html .= <<END;
2022 function ${prefix}state_changed(what) {
2025 if ( $countyflag ) {
2026 $script_html .= <<END;
2027 state = what.options[what.selectedIndex].text;
2028 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
2029 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
2030 what.form.${prefix}county.options[i] = null;
2033 foreach my $country ( sort keys %cust_main_county ) {
2034 $script_html .= "\nif ( country == \"$country\" ) {\n";
2035 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
2036 $script_html .= "\nif ( state == \"$state\" ) {\n";
2037 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
2038 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
2039 my $text = $county || '(n/a)';
2041 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
2043 $script_html .= "}\n";
2045 $script_html .= "}\n";
2049 $script_html .= <<END;
2054 my $county_html = $script_html;
2055 if ( $countyflag ) {
2056 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
2057 foreach my $county (
2058 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
2060 my $text = $county || '(n/a)';
2061 $county_html .= qq!<OPTION VALUE="$county"!.
2062 ($county eq $param->{'selected_county'} ?
2069 $county_html .= '</SELECT>';
2072 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
2075 my $state_html = qq!<SELECT NAME="${prefix}state" !.
2076 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
2077 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
2078 my $text = $state || '(n/a)';
2079 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
2080 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
2082 $state_html .= '</SELECT>';
2084 my $country_html = '';
2085 if ( scalar( keys %cust_main_county ) > 1 ) {
2087 $country_html = qq(<SELECT NAME="${prefix}country" ).
2088 qq(onChange="${prefix}country_changed(this); ).
2089 $param->{'onchange'}.
2092 my $countrydefault = $param->{default_country} || 'US';
2093 foreach my $country (
2094 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
2095 keys %cust_main_county
2097 my $selected = $country eq $param->{'selected_country'}
2100 $country_html .= "\n<OPTION$selected>$country</OPTION>"
2102 $country_html .= '</SELECT>';
2105 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
2106 ' VALUE="'. (keys %cust_main_county )[0]. '">';
2110 ($county_html, $state_html, $country_html);
2114 sub regionselector_hashref {
2115 my ($county_html, $state_html, $country_html) = regionselector(@_);
2117 'county_html' => $county_html,
2118 'state_html' => $state_html,
2119 'country_html' => $country_html,
2123 =item location_form HASHREF | LIST
2125 Takes as input a hashref or list of key/value pairs with the following keys:
2131 Current customer session_id
2135 Omit red asterisks from required fields.
2137 =item address1_label
2139 Label for first address line.
2143 Returns an HTML fragment for a location form (address, city, state, zip,
2156 my $session_id = delete $param->{'session_id'};
2158 my $rv = mason_comp( 'session_id' => $session_id,
2159 'comp' => '/elements/location.html',
2160 'args' => [ %$param ],
2164 $rv->{'error'} || $rv->{'output'};
2169 #=item expselect HASHREF | LIST
2171 #Takes as input a hashref or list of key/value pairs with the following keys:
2175 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
2177 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
2181 =item expselect PREFIX [ DATE ]
2183 Takes as input a unique prefix string and the current expiration date, in
2184 yyyy-mm-dd or m-d-yyyy format
2186 Returns an HTML fragments for expiration date selection.
2192 #if ( ref($_[0]) ) {
2196 #my $prefix = $param->{'prefix'};
2197 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2198 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
2200 my $date = scalar(@_) ? shift : '';
2202 my( $m, $y ) = ( 0, 0 );
2203 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
2204 ( $m, $y ) = ( $2, $1 );
2205 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
2206 ( $m, $y ) = ( $1, $3 );
2208 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
2210 $return .= qq!<OPTION VALUE="$_"!;
2211 $return .= " SELECTED" if $_ == $m;
2214 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
2216 my $thisYear = $t[5] + 1900;
2217 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
2218 $return .= qq!<OPTION VALUE="$_"!;
2219 $return .= " SELECTED" if $_ == $y;
2222 $return .= "</SELECT>";
2227 =item popselector HASHREF | LIST
2229 Takes as input a hashref or list of key/value pairs with the following keys:
2235 Access number number
2239 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>.
2243 Returns an HTML fragment for access number selection.
2247 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
2255 my $popnum = $param->{'popnum'};
2256 my $pops = $param->{'pops'};
2258 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2259 return $pops->[0]{city}. ', '. $pops->[0]{state}.
2260 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2261 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2262 if scalar(@$pops) == 1;
2265 my %popnum2pop = ();
2267 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2268 $popnum2pop{$_->{popnum}} = $_;
2273 function opt(what,href,text) {
2274 var optionName = new Option(text, href, false, false)
2275 var length = what.length;
2276 what.options[length] = optionName;
2280 my $init_popstate = $param->{'init_popstate'};
2281 if ( $init_popstate ) {
2282 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2283 $init_popstate. '">';
2286 function acstate_changed(what) {
2287 state = what.options[what.selectedIndex].text;
2288 what.form.popac.options.length = 0
2289 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2293 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2294 foreach my $state ( sort { $a cmp $b } @states ) {
2295 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2297 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2298 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2299 if ($ac eq $param->{'popac'}) {
2300 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2303 $text .= "}\n" unless $init_popstate;
2305 $text .= "popac_changed(what.form.popac)}\n";
2308 function popac_changed(what) {
2309 ac = what.options[what.selectedIndex].text;
2310 what.form.popnum.options.length = 0;
2311 what.form.popnum.options[0] = new Option("City", "-1", false, true);
2315 foreach my $state ( @states ) {
2316 foreach my $popac ( keys %{ $pop{$state} } ) {
2317 $text .= "\nif ( ac == \"$popac\" ) {\n";
2319 foreach my $pop ( @{$pop{$state}->{$popac}}) {
2320 my $o_popnum = $pop->{popnum};
2321 my $poptext = $pop->{city}. ', '. $pop->{state}.
2322 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2324 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2325 if ($popnum == $o_popnum) {
2326 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2334 $text .= "}\n</SCRIPT>\n";
2336 $param->{'acstate'} = '' unless defined($param->{'acstate'});
2339 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2340 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2341 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2342 ">$_" foreach sort { $a cmp $b } @states;
2343 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
2346 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2347 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2349 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2352 #comment this block to disable initial list polulation
2353 my @initial_select = ();
2354 if ( scalar( @$pops ) > 100 ) {
2355 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2357 @initial_select = @$pops;
2359 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2360 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2361 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2362 $pop->{city}. ', '. $pop->{state}.
2363 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2366 $text .= qq!</SELECT></TD></TR></TABLE>!;
2372 =item domainselector HASHREF | LIST
2374 Takes as input a hashref or list of key/value pairs with the following keys:
2384 Service number of the selected item.
2388 Returns an HTML fragment for domain selection.
2392 sub domainselector {
2399 my $domsvc= $param->{'domsvc'};
2401 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2402 my $domains = $rv->{'domains'};
2403 $domsvc = $rv->{'domsvc'} unless $domsvc;
2405 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2406 unless scalar(keys %$domains);
2408 if (scalar(keys %$domains) == 1) {
2410 foreach(keys %$domains) {
2413 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2414 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2417 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2419 $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2421 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2422 $text .= qq!<OPTION VALUE="!. $domain. '"'.
2423 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2424 $domains->{$domain};
2427 $text .= qq!</SELECT></TD></TR>!;
2433 =item didselector HASHREF | LIST
2435 Takes as input a hashref or list of key/value pairs with the following keys:
2441 Field name for the returned HTML fragment.
2445 Service definition (see L<FS::part_svc>)
2449 Returns an HTML fragment for DID selection.
2461 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2462 'args'=>[ %$param ],
2466 $rv->{'error'} || $rv->{'output'};
2472 =head1 RESELLER FUNCTIONS
2474 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2475 with their active session, and the B<customer_info> and B<order_pkg> functions
2476 with their active session and an additional I<custnum> parameter.
2478 For the most part, development of the reseller web interface has been
2479 superceded by agent-virtualized access to the backend.
2491 =item agent_list_customers
2493 List agent's customers.
2501 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>