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_pbx' => 'MyAccount/provision_pbx',
84 'provision_external' => 'MyAccount/provision_external',
85 'provision_forward' => 'MyAccount/provision_forward',
86 'unprovision_svc' => 'MyAccount/unprovision_svc',
87 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
88 'reset_passwd' => 'MyAccount/reset_passwd',
89 'check_reset_passwd' => 'MyAccount/check_reset_passwd',
90 'process_reset_passwd' => 'MyAccount/process_reset_passwd',
91 'list_tickets' => 'MyAccount/list_tickets',
92 'create_ticket' => 'MyAccount/create_ticket',
93 'get_ticket' => 'MyAccount/get_ticket',
94 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
95 'did_report' => 'MyAccount/did_report',
96 'signup_info' => 'Signup/signup_info',
97 'skin_info' => 'MyAccount/skin_info',
98 'access_info' => 'MyAccount/access_info',
99 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
100 'new_customer' => 'Signup/new_customer',
101 'new_customer_minimal' => 'Signup/new_customer_minimal',
102 'capture_payment' => 'Signup/capture_payment',
103 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
104 'new_agent' => 'Agent/new_agent',
105 'agent_login' => 'Agent/agent_login',
106 'agent_logout' => 'Agent/agent_logout',
107 'agent_info' => 'Agent/agent_info',
108 'agent_list_customers' => 'Agent/agent_list_customers',
109 'check_username' => 'Agent/check_username',
110 'suspend_username' => 'Agent/suspend_username',
111 'unsuspend_username' => 'Agent/unsuspend_username',
112 'mason_comp' => 'MasonComponent/mason_comp',
113 'call_time' => 'PrepaidPhone/call_time',
114 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
115 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
117 'start_thirdparty' => 'MyAccount/start_thirdparty',
118 'finish_thirdparty' => 'MyAccount/finish_thirdparty',
120 'list_quotations' => 'MyAccount/quotation/list_quotations',
121 'quotation_new' => 'MyAccount/quotation/quotation_new',
122 'quotation_delete' => 'MyAccount/quotation/quotation_delete',
123 'quotation_info' => 'MyAccount/quotation/quotation_info',
124 'quotation_print' => 'MyAccount/quotation/quotation_print',
125 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg',
126 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg',
127 'quotation_order' => 'MyAccount/quotation/quotation_order',
132 qw( regionselector regionselector_hashref location_form
133 expselect popselector domainselector didselector
137 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
138 $ENV{'SHELL'} = '/bin/sh';
139 $ENV{'IFS'} = " \t\n";
142 $ENV{'BASH_ENV'} = '';
144 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
145 #if you grant appropriate permissions to whatever user
146 my $freeside_uid = scalar(getpwnam('freeside'));
147 die "not running as the freeside user\n"
148 if $> != $freeside_uid && ! $skip_uid_check;
150 -e $dir or die "FATAL: $dir doesn't exist!";
151 -d $dir or die "FATAL: $dir isn't a directory!";
152 -r $dir or die "FATAL: Can't read $dir as freeside user!";
153 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
155 foreach my $autoload ( keys %autoload ) {
158 "sub $autoload { ". '
163 #warn scalar(@_). ": ". join(" / ", @_);
167 $param->{_packet} = \''. $autoload{$autoload}. '\';
169 simple_packet($param);
179 warn "sending ". $packet->{_packet}. " to server"
181 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
182 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
183 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
186 #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
188 #block until there is a message on socket
189 # my $w = new IO::Select;
191 # my @wait = $w->can_read;
193 warn "reading message from server"
196 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
197 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
199 warn "returning message to client"
207 FS::SelfService - Freeside self-service API
211 # password and shell account changes
212 use FS::SelfService qw(passwd chfn chsh);
214 # "my account" functionality
215 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
217 #new-style login with an email address and password
218 # can also be used for svc_acct login, set $emailaddress to username@domain
219 my $rv = login ( { 'email' => $emailaddress,
220 'password' => $password,
223 if ( $rv->{'error'} ) {
224 #handle login error...
227 $session_id = $rv->{'session_id'};
230 #classic svc_acct-based login with separate username and password
231 my $rv = login( { 'username' => $username,
233 'password' => $password,
236 if ( $rv->{'error'} ) {
237 #handle login error...
240 $session_id = $rv->{'session_id'};
243 #svc_phone login with phone number and PIN
244 my $rv = login( { 'username' => $phone_number,
245 'domain' => 'svc_phone',
249 if ( $rv->{'error'} ) {
250 #handle login error...
253 $session_id = $rv->{'session_id'};
256 my $customer_info = customer_info( { 'session_id' => $session_id } );
258 #payment_info and process_payment are available in 1.5+ only
259 my $payment_info = payment_info( { 'session_id' => $session_id } );
261 #!!! process_payment example
263 #!!! list_pkgs example
265 #!!! order_pkg example
267 #quoting a package, then ordering after confirmation
269 my $rv = quotation_new({ 'session_id' => $session_id });
270 my $qnum = $rv->{quotationnum};
271 # add packages to the quotation
272 $rv = quotation_add_pkg({ 'session_id' => $session_id,
273 'quotationnum' => $qnum,
274 'pkgpart' => $pkgpart,
275 'quantity' => $quantity, # defaults to 1
277 # repeat until all packages are added
278 # view the pricing information
279 $rv = quotation_info({ 'session_id' => $session_id,
280 'quotationnum' => $qnum,
282 print "Total setup charges: ".$rv->{total_setup}."\n".
283 "Total recurring charges: ".$rv->{total_recur}."\n";
284 # quotation_info also provides a detailed breakdown of charges, in
287 # ask customer for confirmation, then:
288 $rv = quotation_order({ 'session_id' => $session_id,
289 'quotationnum' => $qnum,
292 #!!! cancel_pkg example
294 # signup functionality
295 use FS::SelfService qw( signup_info new_customer new_customer_minimal );
297 my $signup_info = signup_info;
299 $rv = new_customer( {
302 'company' => $company,
303 'address1' => $address1,
304 'address2' => $address2,
308 'country' => $country,
309 'daytime' => $daytime,
313 'payinfo' => $payinfo,
315 'paystart_month' => $paystart_month
316 'paystart_year' => $paystart_year,
317 'payissue' => $payissue,
319 'paydate' => $paydate,
320 'payname' => $payname,
321 'invoicing_list' => $invoicing_list,
322 'referral_custnum' => $referral_custnum,
323 'agentnum' => $agentnum,
324 'pkgpart' => $pkgpart,
326 'username' => $username,
327 '_password' => $password,
331 'phonenum' => $phonenum,
336 my $error = $rv->{'error'};
337 if ( $error eq '_decline' ) {
347 Use this API to implement your own client "self-service" module.
349 If you just want to customize the look of the existing "self-service" module,
352 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
358 Changes the password for an existing user in svc_acct. Takes a hash
359 reference with the following keys:
365 Username of the account (required)
369 Domain of the account (required)
373 Old password (required)
377 New password (required)
395 =head1 "MY ACCOUNT" FUNCTIONS
401 Creates a user session. Takes a hash reference as parameter with the
408 Email address (username@domain), instead of username and domain. Required for
409 contact-based self-service login, can also be used for svc_acct-based login.
425 Returns a hash reference with the following keys:
431 Empty on success, or an error message on errors.
435 Session identifier for successful logins
439 =item customer_info HASHREF
441 Returns general customer information.
443 Takes a hash reference as parameter with a single key: B<session_id>
445 Returns a hash reference with the following keys:
459 Array reference of hash references of open inoices. Each hash reference has
460 the following keys: invnum, date, owed
464 An HTML fragment containing shipping and billing addresses.
466 =item The following fields are also returned
468 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
472 =item edit_info HASHREF
474 Takes a hash reference as parameter with any of the following keys:
476 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
478 If a field exists, the customer record is updated with the new value of that
479 field. If a field does not exist, that field is not changed on the customer
482 Returns a hash reference with a single key, B<error>, empty on success, or an
483 error message on errors
485 =item invoice HASHREF
487 Returns an invoice. Takes a hash reference as parameter with two keys:
488 session_id and invnum
490 Returns a hash reference with the following keys:
496 Empty on success, or an error message on errors
508 =item list_invoices HASHREF
510 Returns a list of all customer invoices. Takes a hash references with a single
513 Returns a hash reference with the following keys:
519 Empty on success, or an error message on errors
523 Reference to array of hash references with the following keys:
533 Invoice date, in UNIX epoch time
541 Cancels this customer.
543 Takes a hash reference as parameter with a single key: B<session_id>
545 Returns a hash reference with a single key, B<error>, which is empty on
546 success or an error message on errors.
548 =item payment_info HASHREF
550 Returns information that may be useful in displaying a payment page.
552 Takes a hash reference as parameter with a single key: B<session_id>.
554 Returns a hash reference with the following keys:
560 Empty on success, or an error message on errors
568 Exact name on credit card (CARD/DCRD)
592 Customer's current default payment type.
596 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
600 For CARD/DCRD payment types, the card number
604 For CARD/DCRD payment types, expiration month
608 For CARD/DCRD payment types, expiration year
610 =item cust_main_county
612 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.
616 Array reference of all states in the current default country.
620 Hash reference of card types; keys are card types, values are the exact strings
621 passed to the process_payment function
625 #this doesn't actually work yet
629 #Unique transaction identifier (prevents multiple charges), passed to the
630 #process_payment function
634 =item process_payment HASHREF
636 Processes a payment and possible change of address or payment type. Takes a
637 hash reference as parameter with the following keys:
651 If true, address and card information entered will be saved for subsequent
656 If true, future credit card payments will be done automatically (sets payby to
657 CARD). If false, future credit card payments will be done on-demand (sets
658 payby to DCRD). This option only has meaning if B<save> is set true.
686 Two-letter country code
694 Card expiration month
702 #this doesn't actually work yet
706 #Unique transaction identifier, returned from the payment_info function.
707 #Prevents multiple charges.
711 Returns a hash reference with a single key, B<error>, empty on success, or an
712 error message on errors.
714 =item process_payment_order_pkg
716 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
717 payment processes sucessfully, the package is ordered. Takes a hash reference
718 as parameter with the keys of both methods.
720 Returns a hash reference with a single key, B<error>, empty on success, or an
721 error message on errors.
723 =item process_payment_change_pkg
725 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
726 payment processes sucessfully, the package is ordered. Takes a hash reference
727 as parameter with the keys of both methods.
729 Returns a hash reference with a single key, B<error>, empty on success, or an
730 error message on errors.
733 =item process_payment_order_renew
735 Combines the B<process_payment> and B<order_renew> functions in one step. If
736 the payment processes sucessfully, the renewal is processed. Takes a hash
737 reference as parameter with the keys of both methods.
739 Returns a hash reference with a single key, B<error>, empty on success, or an
740 error message on errors.
744 Returns package information for this customer. For more detail on services,
747 Takes a hash reference as parameter with a single key: B<session_id>
749 Returns a hash reference containing customer package information. The hash reference contains the following keys:
759 Empty on success, or an error message on errors.
761 =item cust_pkg HASHREF
763 Array reference of hash references, each of which has the fields of a cust_pkg
764 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
765 the internal FS:: objects, but hash references of columns and values.
769 =item part_pkg fields
771 All fields of part_pkg for this specific cust_pkg (be careful with this
772 information - it may reveal more about your available packages than you would
773 like users to know in aggregate)
777 #XXX pare part_pkg fields down to a more secure subset
781 An array of hash references indicating information on unprovisioned services
782 available for provisioning for this specific cust_pkg. Each has the following
787 =item part_svc fields
789 All fields of part_svc (be careful with this information - it may reveal more
790 about your available packages than you would like users to know in aggregate)
794 #XXX pare part_svc fields down to a more secure subset
800 An array of hash references indicating information on the customer services
801 already provisioned for this specific cust_pkg. Each has the following keys:
807 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.
813 Primary key for this service
817 Service definition (see L<FS::part_svc>)
821 Customer package (see L<FS::cust_pkg>)
825 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
833 Returns service information for this customer.
835 Takes a hash reference as parameter with a single key: B<session_id>
837 Returns a hash reference containing customer package information. The hash reference contains the following keys:
847 An array of hash references indicating information on all of this customer's
848 services. Each has the following keys:
854 Primary key for this service
862 Meaningful user-specific identifier for the service (i.e. username, domain, or
867 Account (svc_acct) services also have the following keys:
885 Upload bytes remaining
889 Download bytes remaining
893 Total bytes remaining
895 =item recharge_amount
899 =item recharge_seconds
901 Number of seconds gained by recharge
903 =item recharge_upbytes
905 Number of upload bytes gained by recharge
907 =item recharge_downbytes
909 Number of download bytes gained by recharge
911 =item recharge_totalbytes
913 Number of total bytes gained by recharge
921 Orders a package for this customer.
923 Takes a hash reference as parameter with the following keys:
933 Package to order (see L<FS::part_pkg>).
937 Quantity for this package order (default 1).
941 Optional locationnum for this package order, for existing locations.
943 Or, for new locations, pass the following fields: address1*, address2, city*,
944 county, state*, zip*, country. (* = required in this case)
956 Service to order (see L<FS::part_svc>).
958 Normally optional; required only to provision a non-svc_acct service, or if the
959 package definition does not contain one svc_acct service definition with
960 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
961 also be specified to indicate that no initial service should be provisioned.
965 Fields used when provisioning an svc_acct service:
979 Optional security phrase
983 Optional Access number number
987 Fields used when provisioning an svc_domain service:
997 Fields used when provisioning an svc_phone service:
1015 Fields used when provisioning an svc_external service:
1021 External numeric ID.
1025 External text title.
1029 Fields used when provisioning an svc_pbx service:
1043 Returns a hash reference with a single key, B<error>, empty on success, or an
1044 error message on errors. The special error '_decline' is returned for
1045 declined transactions.
1049 Changes a package for this customer.
1051 Takes a hash reference as parameter with the following keys:
1061 Existing customer package.
1065 New package to order (see L<FS::part_pkg>).
1069 Quantity for this package order (default 1).
1073 Returns a hash reference with the following keys:
1079 Empty on success, or an error message on errors.
1083 On success, the new pkgnum
1090 Provides useful info for early renewals.
1092 Takes a hash reference as parameter with the following keys:
1102 Returns a hash reference. On errors, it contains a single key, B<error>, with
1103 the error message. Otherwise, contains a single key, B<dates>, pointing to
1104 an array refernce of hash references. Each hash reference contains the
1111 (Future) Bill date. Indicates a future date for which billing could be run.
1112 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
1115 =item bill_date_pretty
1117 (Future) Bill date as a human-readable string. (Convenience for display;
1118 subject to change, so best not to parse for the date.)
1122 Base amount which will be charged if renewed early as of this date.
1126 Renewal date; i.e. even-futher future date at which the customer will be paid
1127 through if the early renewal is completed with the given B<bill-date>.
1128 Specified as a integer UNIX timestamp.
1130 =item renew_date_pretty
1132 Renewal date as a human-readable string. (Convenience for display;
1133 subject to change, so best not to parse for the date.)
1137 Package that will be renewed.
1141 Expiration date of the package that will be renewed.
1143 =item expire_date_pretty
1145 Expiration date of the package that will be renewed, as a human-readable
1146 string. (Convenience for display; subject to change, so best not to parse for
1153 Renews this customer early; i.e. runs billing for this customer in advance.
1155 Takes a hash reference as parameter with the following keys:
1165 Integer date as returned by the B<renew_info> function, indicating the advance
1166 date for which to run billing.
1170 Returns a hash reference with a single key, B<error>, empty on success, or an
1171 error message on errors.
1175 Cancels a package for this customer.
1177 Takes a hash reference as parameter with the following keys:
1187 pkgpart of package to cancel
1191 Returns a hash reference with a single key, B<error>, empty on success, or an
1192 error message on errors.
1194 =item provision_acct
1196 Provisions an account (svc_acct).
1198 Takes a hash references as parameter with the following keys:
1208 pkgnum of package into which this service is provisioned
1212 svcpart or service definition to provision
1222 =item provision_phone
1224 Provisions a phone number (svc_phone).
1226 Takes a hash references as parameter with the following keys:
1236 pkgnum of package into which this service is provisioned
1240 svcpart or service definition to provision
1260 E911 Address (optional)
1266 Provisions a customer PBX (svc_pbx).
1268 Takes a hash references as parameter with the following keys:
1278 pkgnum of package into which this service is provisioned
1282 svcpart or service definition to provision
1288 =item max_extensions
1290 =item max_simultaneous
1296 =item provision_external
1298 Provisions an external service (svc_external).
1300 Takes a hash references as parameter with the following keys:
1310 pkgnum of package into which this service is provisioned
1314 svcpart or service definition to provision
1324 =head2 "MY ACCOUNT" QUOTATION FUNCTIONS
1326 All of these functions require the user to be logged in, and the 'session_id'
1327 key to be included in the argument hashref.`
1331 =item list_quotations HASHREF
1333 Returns a hashref listing this customer's active self-service quotations.
1336 - 'quotations', an arrayref containing an element for each quotation.
1337 - quotationnum, the primary key
1338 - _date, the date it was started
1339 - num_pkgs, the number of packages
1340 - total_setup, the sum of setup fees
1341 - total_recur, the sum of recurring charges
1343 =item quotation_new HASHREF
1345 Creates an empty quotation and returns a hashref containing 'quotationnum',
1346 the primary key of the new quotation.
1348 =item quotation_delete HASHREF
1350 Disables (does not really delete) a quotation. Takes the following arguments:
1356 =item quotationnum - the quotation to delete
1360 Returns 'error' => a string, which will be empty on success.
1362 =item quotation_info HASHREF
1364 Returns total and detailed pricing information on a quotation.
1366 Takes the following arguments:
1372 =item quotationnum - the quotation to return
1376 Returns a hashref containing:
1378 - total_setup, the total of setup fees (and their taxes)
1379 - total_recur, the total of all recurring charges (and their taxes)
1380 - sections, an arrayref containing an element for each quotation section.
1381 - description, a line of text describing the group of charges
1382 - subtotal, the total of charges in this group (if appropriate)
1383 - detail_items, an arrayref of line items
1384 - pkgnum, the reference number of the package
1385 - description, the package name (or tax name)
1387 - amount, the amount charged
1388 If the detail item represents a subtotal, it will instead contain:
1389 - total_item: description of the subtotal
1390 - total_amount: the subtotal amount
1393 =item quotation_print HASHREF
1395 Renders the quotation as HTML or PDF. Takes the following arguments:
1401 =item quotationnum - the quotation to return
1403 =item format - 'html' or 'pdf'
1407 Returns a hashref containing 'document', the contents of the file.
1409 =item quotation_add_pkg HASHREF
1411 Adds a package to a quotation. Takes the following arguments:
1417 =item pkgpart - the package to add
1419 =item quotationnum - the quotation to add it to
1421 =item quantity - the package quantity (defaults to 1)
1423 =item address1, address2, city, state, zip, country - address fields to set
1424 the service location
1428 Returns 'error' => a string, which will be empty on success.
1430 =item quotation_remove_pkg HASHREF
1432 Removes a package from a quotation. Takes the following arguments:
1438 =item pkgnum - the primary key (quotationpkgnum) of the package to remove
1440 =item quotationnum - the quotation to remove it from
1444 Returns 'error' => a string, which will be empty on success.
1448 =item quotation_order HASHREF
1450 Converts the packages in a quotation into real packages. Takes the following
1453 Takes the following arguments:
1459 =item quotationnum - the quotation to order
1465 =head1 SIGNUP FUNCTIONS
1469 =item signup_info HASHREF
1471 Takes a hash reference as parameter with the following keys:
1475 =item session_id - Optional agent/reseller interface session
1479 Returns a hash reference containing information that may be useful in
1480 displaying a signup page. The hash reference contains the following keys:
1484 =item cust_main_county
1486 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.
1490 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
1491 an agentnum specified explicitly via reseller interface session_id in the
1496 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.
1498 =item agentnum2part_pkg
1500 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.
1504 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.
1506 =item security_phrase
1508 True if the "security_phrase" feature is enabled
1512 Array reference of acceptable payment types for signup
1518 credit card - automatic
1522 credit card - on-demand - version 1.5+ only
1526 electronic check - automatic
1530 electronic check - on-demand - version 1.5+ only
1538 billing, not recommended for signups
1542 free, definitely not recommended for signups
1546 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1552 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1556 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".
1562 =item countrydefault
1568 =item new_customer_minimal HASHREF
1570 Creates a new customer.
1572 Current differences from new_customer: An address is not required. promo_code
1573 and reg_code are not supported. If invoicing_list and _password is passed, a
1574 contact will be created with self-service access (no pkgpart or username is
1575 necessary). No initial billing is run (this may change in a future version).
1577 Takes a hash reference as parameter with the following keys:
1583 first name (required)
1587 last name (required)
1591 (not typically collected; mostly used for ACH transactions)
1623 Daytime phone number
1627 Evening phone number
1635 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1639 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1643 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1647 Expiration date for CARD/DCRD
1651 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1653 =item invoicing_list
1655 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),
1657 =item referral_custnum
1659 referring customer number
1667 pkgpart of initial package
1683 Access number (index, not the literal number)
1687 Country code (to be provisioned as a service)
1691 Phone number (to be provisioned as a service)
1699 Returns a hash reference with the following keys:
1705 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)
1709 =item new_customer HASHREF
1711 Creates a new customer. Takes a hash reference as parameter with the
1718 first name (required)
1722 last name (required)
1726 (not typically collected; mostly used for ACH transactions)
1732 =item address1 (required)
1740 =item city (required)
1748 =item state (required)
1752 =item zip (required)
1758 Daytime phone number
1762 Evening phone number
1770 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1774 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1778 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1782 Expiration date for CARD/DCRD
1786 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1788 =item invoicing_list
1790 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),
1792 =item referral_custnum
1794 referring customer number
1802 pkgpart of initial package
1818 Access number (index, not the literal number)
1822 Country code (to be provisioned as a service)
1826 Phone number (to be provisioned as a service)
1834 Returns a hash reference with the following keys:
1840 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)
1844 =item regionselector HASHREF | LIST
1846 Takes as input a hashref or list of key/value pairs with the following keys:
1850 =item selected_county
1852 Currently selected county
1854 =item selected_state
1856 Currently selected state
1858 =item selected_country
1860 Currently selected country
1864 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1868 Specify a javascript subroutine to call on changes
1874 =item default_country
1880 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>.
1884 Returns a list consisting of three HTML fragments for county selection,
1885 state selection and country selection, respectively.
1889 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1890 sub regionselector {
1897 $param->{'selected_country'} ||= $param->{'default_country'};
1898 $param->{'selected_state'} ||= $param->{'default_state'};
1900 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1904 my %cust_main_county;
1906 # unless ( @cust_main_county ) { #cache
1907 #@cust_main_county = qsearch('cust_main_county', {} );
1908 #foreach my $c ( @cust_main_county ) {
1909 foreach my $c ( @{ $param->{'locales'} } ) {
1910 #$countyflag=1 if $c->county;
1911 $countyflag=1 if $c->{county};
1912 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1913 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1914 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1917 $countyflag=1 if $param->{selected_county};
1919 my $script_html = <<END;
1921 function opt(what,value,text) {
1922 var optionName = new Option(text, value, false, false);
1923 var length = what.length;
1924 what.options[length] = optionName;
1926 function ${prefix}country_changed(what) {
1927 country = what.options[what.selectedIndex].text;
1928 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1929 what.form.${prefix}state.options[i] = null;
1931 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1933 foreach my $country ( sort keys %cust_main_county ) {
1934 $script_html .= "\nif ( country == \"$country\" ) {\n";
1935 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1936 my $text = $state || '(n/a)';
1937 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1939 $script_html .= "}\n";
1942 $script_html .= <<END;
1944 function ${prefix}state_changed(what) {
1947 if ( $countyflag ) {
1948 $script_html .= <<END;
1949 state = what.options[what.selectedIndex].text;
1950 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1951 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1952 what.form.${prefix}county.options[i] = null;
1955 foreach my $country ( sort keys %cust_main_county ) {
1956 $script_html .= "\nif ( country == \"$country\" ) {\n";
1957 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1958 $script_html .= "\nif ( state == \"$state\" ) {\n";
1959 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1960 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1961 my $text = $county || '(n/a)';
1963 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1965 $script_html .= "}\n";
1967 $script_html .= "}\n";
1971 $script_html .= <<END;
1976 my $county_html = $script_html;
1977 if ( $countyflag ) {
1978 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1979 foreach my $county (
1980 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1982 my $text = $county || '(n/a)';
1983 $county_html .= qq!<OPTION VALUE="$county"!.
1984 ($county eq $param->{'selected_county'} ?
1991 $county_html .= '</SELECT>';
1994 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1997 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1998 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1999 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
2000 my $text = $state || '(n/a)';
2001 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
2002 $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
2004 $state_html .= '</SELECT>';
2006 my $country_html = '';
2007 if ( scalar( keys %cust_main_county ) > 1 ) {
2009 $country_html = qq(<SELECT NAME="${prefix}country" ).
2010 qq(onChange="${prefix}country_changed(this); ).
2011 $param->{'onchange'}.
2014 my $countrydefault = $param->{default_country} || 'US';
2015 foreach my $country (
2016 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
2017 keys %cust_main_county
2019 my $selected = $country eq $param->{'selected_country'}
2022 $country_html .= "\n<OPTION$selected>$country</OPTION>"
2024 $country_html .= '</SELECT>';
2027 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
2028 ' VALUE="'. (keys %cust_main_county )[0]. '">';
2032 ($county_html, $state_html, $country_html);
2036 sub regionselector_hashref {
2037 my ($county_html, $state_html, $country_html) = regionselector(@_);
2039 'county_html' => $county_html,
2040 'state_html' => $state_html,
2041 'country_html' => $country_html,
2045 =item location_form HASHREF | LIST
2047 Takes as input a hashref or list of key/value pairs with the following keys:
2053 Current customer session_id
2057 Omit red asterisks from required fields.
2059 =item address1_label
2061 Label for first address line.
2065 Returns an HTML fragment for a location form (address, city, state, zip,
2078 my $session_id = delete $param->{'session_id'};
2080 my $rv = mason_comp( 'session_id' => $session_id,
2081 'comp' => '/elements/location.html',
2082 'args' => [ %$param ],
2086 $rv->{'error'} || $rv->{'output'};
2091 #=item expselect HASHREF | LIST
2093 #Takes as input a hashref or list of key/value pairs with the following keys:
2097 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
2099 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
2103 =item expselect PREFIX [ DATE ]
2105 Takes as input a unique prefix string and the current expiration date, in
2106 yyyy-mm-dd or m-d-yyyy format
2108 Returns an HTML fragments for expiration date selection.
2114 #if ( ref($_[0]) ) {
2118 #my $prefix = $param->{'prefix'};
2119 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2120 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
2122 my $date = scalar(@_) ? shift : '';
2124 my( $m, $y ) = ( 0, 0 );
2125 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
2126 ( $m, $y ) = ( $2, $1 );
2127 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
2128 ( $m, $y ) = ( $1, $3 );
2130 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
2132 $return .= qq!<OPTION VALUE="$_"!;
2133 $return .= " SELECTED" if $_ == $m;
2136 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
2138 my $thisYear = $t[5] + 1900;
2139 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
2140 $return .= qq!<OPTION VALUE="$_"!;
2141 $return .= " SELECTED" if $_ == $y;
2144 $return .= "</SELECT>";
2149 =item popselector HASHREF | LIST
2151 Takes as input a hashref or list of key/value pairs with the following keys:
2157 Access number number
2161 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>.
2165 Returns an HTML fragment for access number selection.
2169 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
2177 my $popnum = $param->{'popnum'};
2178 my $pops = $param->{'pops'};
2180 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2181 return $pops->[0]{city}. ', '. $pops->[0]{state}.
2182 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2183 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2184 if scalar(@$pops) == 1;
2187 my %popnum2pop = ();
2189 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2190 $popnum2pop{$_->{popnum}} = $_;
2195 function opt(what,href,text) {
2196 var optionName = new Option(text, href, false, false)
2197 var length = what.length;
2198 what.options[length] = optionName;
2202 my $init_popstate = $param->{'init_popstate'};
2203 if ( $init_popstate ) {
2204 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2205 $init_popstate. '">';
2208 function acstate_changed(what) {
2209 state = what.options[what.selectedIndex].text;
2210 what.form.popac.options.length = 0
2211 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2215 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2216 foreach my $state ( sort { $a cmp $b } @states ) {
2217 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2219 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2220 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2221 if ($ac eq $param->{'popac'}) {
2222 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2225 $text .= "}\n" unless $init_popstate;
2227 $text .= "popac_changed(what.form.popac)}\n";
2230 function popac_changed(what) {
2231 ac = what.options[what.selectedIndex].text;
2232 what.form.popnum.options.length = 0;
2233 what.form.popnum.options[0] = new Option("City", "-1", false, true);
2237 foreach my $state ( @states ) {
2238 foreach my $popac ( keys %{ $pop{$state} } ) {
2239 $text .= "\nif ( ac == \"$popac\" ) {\n";
2241 foreach my $pop ( @{$pop{$state}->{$popac}}) {
2242 my $o_popnum = $pop->{popnum};
2243 my $poptext = $pop->{city}. ', '. $pop->{state}.
2244 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2246 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2247 if ($popnum == $o_popnum) {
2248 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2256 $text .= "}\n</SCRIPT>\n";
2258 $param->{'acstate'} = '' unless defined($param->{'acstate'});
2261 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2262 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2263 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2264 ">$_" foreach sort { $a cmp $b } @states;
2265 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
2268 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2269 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2271 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2274 #comment this block to disable initial list polulation
2275 my @initial_select = ();
2276 if ( scalar( @$pops ) > 100 ) {
2277 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2279 @initial_select = @$pops;
2281 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2282 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2283 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2284 $pop->{city}. ', '. $pop->{state}.
2285 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2288 $text .= qq!</SELECT></TD></TR></TABLE>!;
2294 =item domainselector HASHREF | LIST
2296 Takes as input a hashref or list of key/value pairs with the following keys:
2306 Service number of the selected item.
2310 Returns an HTML fragment for domain selection.
2314 sub domainselector {
2321 my $domsvc= $param->{'domsvc'};
2323 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2324 my $domains = $rv->{'domains'};
2325 $domsvc = $rv->{'domsvc'} unless $domsvc;
2327 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2328 unless scalar(keys %$domains);
2330 if (scalar(keys %$domains) == 1) {
2332 foreach(keys %$domains) {
2335 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2336 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2339 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2341 $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2343 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2344 $text .= qq!<OPTION VALUE="!. $domain. '"'.
2345 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2346 $domains->{$domain};
2349 $text .= qq!</SELECT></TD></TR>!;
2355 =item didselector HASHREF | LIST
2357 Takes as input a hashref or list of key/value pairs with the following keys:
2363 Field name for the returned HTML fragment.
2367 Service definition (see L<FS::part_svc>)
2371 Returns an HTML fragment for DID selection.
2383 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2384 'args'=>[ %$param ],
2388 $rv->{'error'} || $rv->{'output'};
2394 =head1 RESELLER FUNCTIONS
2396 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2397 with their active session, and the B<customer_info> and B<order_pkg> functions
2398 with their active session and an additional I<custnum> parameter.
2400 For the most part, development of the reseller web interface has been
2401 superceded by agent-virtualized access to the backend.
2413 =item agent_list_customers
2415 List agent's customers.
2423 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>