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 'switch_cust' => 'MyAccount/switch_cust',
34 'customer_info' => 'MyAccount/customer_info',
35 'customer_info_short' => 'MyAccount/customer_info_short',
36 'billing_history' => 'MyAccount/billing_history',
37 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
38 'invoice' => 'MyAccount/invoice',
39 'invoice_pdf' => 'MyAccount/invoice_pdf',
40 'legacy_invoice' => 'MyAccount/legacy_invoice',
41 'legacy_invoice_pdf' => 'MyAccount/legacy_invoice_pdf',
42 'invoice_logo' => 'MyAccount/invoice_logo',
43 'list_invoices' => 'MyAccount/list_invoices', #?
44 'cancel' => 'MyAccount/cancel', #add to ss cgi!
45 'payment_info' => 'MyAccount/payment_info',
46 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
47 'process_payment' => 'MyAccount/process_payment',
48 'store_payment' => 'MyAccount/store_payment',
49 'process_stored_payment' => 'MyAccount/process_stored_payment',
50 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
51 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
52 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
53 'process_prepay' => 'MyAccount/process_prepay',
54 'realtime_collect' => 'MyAccount/realtime_collect',
55 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
56 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
57 'list_svc_usage' => 'MyAccount/list_svc_usage',
58 'svc_status_html' => 'MyAccount/svc_status_html',
59 'svc_status_hash' => 'MyAccount/svc_status_hash',
60 'set_svc_status_hash' => 'MyAccount/set_svc_status_hash',
61 'set_svc_status_listadd' => 'MyAccount/set_svc_status_listadd',
62 'set_svc_status_listdel' => 'MyAccount/set_svc_status_listdel',
63 'set_svc_status_vacationadd'=> 'MyAccount/set_svc_status_vacationadd',
64 'set_svc_status_vacationdel'=> 'MyAccount/set_svc_status_vacationdel',
65 'acct_forward_info' => 'MyAccount/acct_forward_info',
66 'process_acct_forward' => 'MyAccount/process_acct_forward',
67 'list_dsl_devices' => 'MyAccount/list_dsl_devices',
68 'add_dsl_device' => 'MyAccount/add_dsl_device',
69 'delete_dsl_device' => 'MyAccount/delete_dsl_device',
70 'port_graph' => 'MyAccount/port_graph',
71 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
72 'list_support_usage' => 'MyAccount/list_support_usage',
73 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
74 'change_pkg' => 'MyAccount/change_pkg',
75 'order_recharge' => 'MyAccount/order_recharge',
76 'renew_info' => 'MyAccount/renew_info',
77 'order_renew' => 'MyAccount/order_renew',
78 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
79 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi!
80 'charge' => 'MyAccount/charge', #?
81 'part_svc_info' => 'MyAccount/part_svc_info',
82 'provision_acct' => 'MyAccount/provision_acct',
83 'provision_phone' => 'MyAccount/provision_phone',
84 'provision_external' => 'MyAccount/provision_external',
85 'unprovision_svc' => 'MyAccount/unprovision_svc',
86 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
87 'reset_passwd' => 'MyAccount/reset_passwd',
88 'check_reset_passwd' => 'MyAccount/check_reset_passwd',
89 'process_reset_passwd' => 'MyAccount/process_reset_passwd',
90 'list_tickets' => 'MyAccount/list_tickets',
91 'create_ticket' => 'MyAccount/create_ticket',
92 'get_ticket' => 'MyAccount/get_ticket',
93 'adjust_ticket_priority' => 'MyAccount/adjust_ticket_priority',
94 'did_report' => 'MyAccount/did_report',
95 'signup_info' => 'Signup/signup_info',
96 'skin_info' => 'MyAccount/skin_info',
97 'access_info' => 'MyAccount/access_info',
98 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
99 'new_customer' => 'Signup/new_customer',
100 'new_customer_minimal' => 'Signup/new_customer_minimal',
101 'capture_payment' => 'Signup/capture_payment',
102 #N/A 'clear_signup_cache' => 'Signup/clear_cache',
103 'new_agent' => 'Agent/new_agent',
104 'agent_login' => 'Agent/agent_login',
105 'agent_logout' => 'Agent/agent_logout',
106 'agent_info' => 'Agent/agent_info',
107 'agent_list_customers' => 'Agent/agent_list_customers',
108 'check_username' => 'Agent/check_username',
109 'suspend_username' => 'Agent/suspend_username',
110 'unsuspend_username' => 'Agent/unsuspend_username',
111 'mason_comp' => 'MasonComponent/mason_comp',
112 'call_time' => 'PrepaidPhone/call_time',
113 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
114 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
116 'start_thirdparty' => 'MyAccount/start_thirdparty',
117 'finish_thirdparty' => 'MyAccount/finish_thirdparty',
119 'list_quotations' => 'MyAccount/quotation/list_quotations',
120 'quotation_new' => 'MyAccount/quotation/quotation_new',
121 'quotation_delete' => 'MyAccount/quotation/quotation_delete',
122 'quotation_info' => 'MyAccount/quotation/quotation_info',
123 'quotation_print' => 'MyAccount/quotation/quotation_print',
124 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg',
125 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg',
126 'quotation_order' => 'MyAccount/quotation/quotation_order',
131 qw( regionselector regionselector_hashref location_form
132 expselect popselector domainselector didselector
136 $ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
137 $ENV{'SHELL'} = '/bin/sh';
138 $ENV{'IFS'} = " \t\n";
141 $ENV{'BASH_ENV'} = '';
143 #you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
144 #if you grant appropriate permissions to whatever user
145 my $freeside_uid = scalar(getpwnam('freeside'));
146 die "not running as the freeside user\n"
147 if $> != $freeside_uid && ! $skip_uid_check;
149 -e $dir or die "FATAL: $dir doesn't exist!";
150 -d $dir or die "FATAL: $dir isn't a directory!";
151 -r $dir or die "FATAL: Can't read $dir as freeside user!";
152 -x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
154 foreach my $autoload ( keys %autoload ) {
157 "sub $autoload { ". '
162 #warn scalar(@_). ": ". join(" / ", @_);
166 $param->{_packet} = \''. $autoload{$autoload}. '\';
168 simple_packet($param);
178 warn "sending ". $packet->{_packet}. " to server"
180 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
181 connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
182 nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
185 #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
187 #block until there is a message on socket
188 # my $w = new IO::Select;
190 # my @wait = $w->can_read;
192 warn "reading message from server"
195 my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
196 die $return->{'_error'} if defined $return->{_error} && $return->{_error};
198 warn "returning message to client"
206 FS::SelfService - Freeside self-service API
210 # password and shell account changes
211 use FS::SelfService qw(passwd chfn chsh);
213 # "my account" functionality
214 use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
216 #new-style login with an email address and password
217 # can also be used for svc_acct login, set $emailaddress to username@domain
218 my $rv = login ( { 'email' => $emailaddress,
219 'password' => $password,
222 if ( $rv->{'error'} ) {
223 #handle login error...
226 $session_id = $rv->{'session_id'};
229 #classic svc_acct-based login with separate username and password
230 my $rv = login( { 'username' => $username,
232 'password' => $password,
235 if ( $rv->{'error'} ) {
236 #handle login error...
239 $session_id = $rv->{'session_id'};
242 #svc_phone login with phone number and PIN
243 my $rv = login( { 'username' => $phone_number,
244 'domain' => 'svc_phone',
248 if ( $rv->{'error'} ) {
249 #handle login error...
252 $session_id = $rv->{'session_id'};
255 my $customer_info = customer_info( { 'session_id' => $session_id } );
257 #payment_info and process_payment are available in 1.5+ only
258 my $payment_info = payment_info( { 'session_id' => $session_id } );
260 #!!! process_payment example
262 #!!! list_pkgs example
264 #ordering a package with an svc_acct service
265 my $rv = order_pkg( { 'session_id' => $session_id,
266 'pkgpart' => $pkgpart,
267 'svcpart' => $svcpart,
268 'username' => $username,
269 'domsvc' => $domsvc, #svcnum of svc_domain
270 '_password' => $password,
274 #!!! ordering a package with an svc_domain service example
276 #!!! ordering a package with an svc_phone service example
278 #!!! ordering a package with an svc_external service example
280 #!!! ordering a package with an svc_pbx service
282 #ordering a package with no service
283 my $rv = order_pkg( { 'session_id' => $session_id,
284 'pkgpart' => $pkgpart,
289 #quoting a package, then ordering after confirmation
291 my $rv = quotation_new({ 'session_id' => $session_id });
292 my $qnum = $rv->{quotationnum};
293 # add packages to the quotation
294 $rv = quotation_add_pkg({ 'session_id' => $session_id,
295 'quotationnum' => $qnum,
296 'pkgpart' => $pkgpart,
297 'quantity' => $quantity, # defaults to 1
299 # repeat until all packages are added
300 # view the pricing information
301 $rv = quotation_info({ 'session_id' => $session_id,
302 'quotationnum' => $qnum,
304 print "Total setup charges: ".$rv->{total_setup}."\n".
305 "Total recurring charges: ".$rv->{total_recur}."\n";
306 # quotation_info also provides a detailed breakdown of charges, in
309 # ask customer for confirmation, then:
310 $rv = quotation_order({ 'session_id' => $session_id,
311 'quotationnum' => $qnum,
314 #!!! cancel_pkg example
316 # signup functionality
317 use FS::SelfService qw( signup_info new_customer new_customer_minimal );
319 my $signup_info = signup_info;
321 $rv = new_customer( {
324 'company' => $company,
325 'address1' => $address1,
326 'address2' => $address2,
330 'country' => $country,
331 'daytime' => $daytime,
335 'payinfo' => $payinfo,
337 'paystart_month' => $paystart_month
338 'paystart_year' => $paystart_year,
339 'payissue' => $payissue,
341 'paydate' => $paydate,
342 'payname' => $payname,
343 'invoicing_list' => $invoicing_list,
344 'referral_custnum' => $referral_custnum,
345 'agentnum' => $agentnum,
346 'pkgpart' => $pkgpart,
348 'username' => $username,
349 '_password' => $password,
353 'phonenum' => $phonenum,
358 my $error = $rv->{'error'};
359 if ( $error eq '_decline' ) {
369 Use this API to implement your own client "self-service" module.
371 If you just want to customize the look of the existing "self-service" module,
374 =head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
380 Changes the password for an existing user in svc_acct. Takes a hash
381 reference with the following keys:
387 Username of the account (required)
391 Domain of the account (required)
395 Old password (required)
399 New password (required)
417 =head1 "MY ACCOUNT" FUNCTIONS
423 Creates a user session. Takes a hash reference as parameter with the
430 Email address (username@domain), instead of username and domain. Required for
431 contact-based self-service login, can also be used for svc_acct-based login.
447 Returns a hash reference with the following keys:
453 Empty on success, or an error message on errors.
457 Session identifier for successful logins
461 =item customer_info HASHREF
463 Returns general customer information.
465 Takes a hash reference as parameter with a single key: B<session_id>
467 Returns a hash reference with the following keys:
481 Array reference of hash references of open inoices. Each hash reference has
482 the following keys: invnum, date, owed
486 An HTML fragment containing shipping and billing addresses.
488 =item The following fields are also returned
490 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
494 =item edit_info HASHREF
496 Takes a hash reference as parameter with any of the following keys:
498 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
500 If a field exists, the customer record is updated with the new value of that
501 field. If a field does not exist, that field is not changed on the customer
504 Returns a hash reference with a single key, B<error>, empty on success, or an
505 error message on errors
507 =item invoice HASHREF
509 Returns an invoice. Takes a hash reference as parameter with two keys:
510 session_id and invnum
512 Returns a hash reference with the following keys:
518 Empty on success, or an error message on errors
530 =item list_invoices HASHREF
532 Returns a list of all customer invoices. Takes a hash references with a single
535 Returns a hash reference with the following keys:
541 Empty on success, or an error message on errors
545 Reference to array of hash references with the following keys:
555 Invoice date, in UNIX epoch time
563 Cancels this customer.
565 Takes a hash reference as parameter with a single key: B<session_id>
567 Returns a hash reference with a single key, B<error>, which is empty on
568 success or an error message on errors.
570 =item payment_info HASHREF
572 Returns information that may be useful in displaying a payment page.
574 Takes a hash reference as parameter with a single key: B<session_id>.
576 Returns a hash reference with the following keys:
582 Empty on success, or an error message on errors
590 Exact name on credit card (CARD/DCRD)
614 Customer's current default payment type.
618 For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
622 For CARD/DCRD payment types, the card number
626 For CARD/DCRD payment types, expiration month
630 For CARD/DCRD payment types, expiration year
632 =item cust_main_county
634 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.
638 Array reference of all states in the current default country.
642 Hash reference of card types; keys are card types, values are the exact strings
643 passed to the process_payment function
647 #this doesn't actually work yet
651 #Unique transaction identifier (prevents multiple charges), passed to the
652 #process_payment function
656 =item process_payment HASHREF
658 Processes a payment and possible change of address or payment type. Takes a
659 hash reference as parameter with the following keys:
673 If true, address and card information entered will be saved for subsequent
678 If true, future credit card payments will be done automatically (sets payby to
679 CARD). If false, future credit card payments will be done on-demand (sets
680 payby to DCRD). This option only has meaning if B<save> is set true.
708 Two-letter country code
716 Card expiration month
724 #this doesn't actually work yet
728 #Unique transaction identifier, returned from the payment_info function.
729 #Prevents multiple charges.
733 Returns a hash reference with a single key, B<error>, empty on success, or an
734 error message on errors.
736 =item process_payment_order_pkg
738 Combines the B<process_payment> and B<order_pkg> functions in one step. If the
739 payment processes sucessfully, the package is ordered. Takes a hash reference
740 as parameter with the keys of both methods.
742 Returns a hash reference with a single key, B<error>, empty on success, or an
743 error message on errors.
745 =item process_payment_change_pkg
747 Combines the B<process_payment> and B<change_pkg> functions in one step. If the
748 payment processes sucessfully, the package is ordered. Takes a hash reference
749 as parameter with the keys of both methods.
751 Returns a hash reference with a single key, B<error>, empty on success, or an
752 error message on errors.
755 =item process_payment_order_renew
757 Combines the B<process_payment> and B<order_renew> functions in one step. If
758 the payment processes sucessfully, the renewal is processed. Takes a hash
759 reference as parameter with the keys of both methods.
761 Returns a hash reference with a single key, B<error>, empty on success, or an
762 error message on errors.
766 Returns package information for this customer. For more detail on services,
769 Takes a hash reference as parameter with a single key: B<session_id>
771 Returns a hash reference containing customer package information. The hash reference contains the following keys:
781 Empty on success, or an error message on errors.
783 =item cust_pkg HASHREF
785 Array reference of hash references, each of which has the fields of a cust_pkg
786 record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
787 the internal FS:: objects, but hash references of columns and values.
791 =item part_pkg fields
793 All fields of part_pkg for this specific cust_pkg (be careful with this
794 information - it may reveal more about your available packages than you would
795 like users to know in aggregate)
799 #XXX pare part_pkg fields down to a more secure subset
803 An array of hash references indicating information on unprovisioned services
804 available for provisioning for this specific cust_pkg. Each has the following
809 =item part_svc fields
811 All fields of part_svc (be careful with this information - it may reveal more
812 about your available packages than you would like users to know in aggregate)
816 #XXX pare part_svc fields down to a more secure subset
822 An array of hash references indicating information on the customer services
823 already provisioned for this specific cust_pkg. Each has the following keys:
829 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.
835 Primary key for this service
839 Service definition (see L<FS::part_svc>)
843 Customer package (see L<FS::cust_pkg>)
847 Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
855 Returns service information for this customer.
857 Takes a hash reference as parameter with a single key: B<session_id>
859 Returns a hash reference containing customer package information. The hash reference contains the following keys:
869 An array of hash references indicating information on all of this customer's
870 services. Each has the following keys:
876 Primary key for this service
884 Meaningful user-specific identifier for the service (i.e. username, domain, or
889 Account (svc_acct) services also have the following keys:
907 Upload bytes remaining
911 Download bytes remaining
915 Total bytes remaining
917 =item recharge_amount
921 =item recharge_seconds
923 Number of seconds gained by recharge
925 =item recharge_upbytes
927 Number of upload bytes gained by recharge
929 =item recharge_downbytes
931 Number of download bytes gained by recharge
933 =item recharge_totalbytes
935 Number of total bytes gained by recharge
943 Orders a package for this customer.
945 Takes a hash reference as parameter with the following keys:
955 Package to order (see L<FS::part_pkg>).
959 Quantity for this package order (default 1).
963 Optional locationnum for this package order, for existing locations.
965 Or, for new locations, pass the following fields: address1*, address2, city*,
966 county, state*, zip*, country. (* = required in this case)
978 Service to order (see L<FS::part_svc>).
980 Normally optional; required only to provision a non-svc_acct service, or if the
981 package definition does not contain one svc_acct service definition with
982 quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
983 also be specified to indicate that no initial service should be provisioned.
987 Fields used when provisioning an svc_acct service:
1001 Optional security phrase
1005 Optional Access number number
1009 Fields used when provisioning an svc_domain service:
1019 Fields used when provisioning an svc_phone service:
1037 Fields used when provisioning an svc_external service:
1043 External numeric ID.
1047 External text title.
1051 Fields used when provisioning an svc_pbx service:
1065 Returns a hash reference with a single key, B<error>, empty on success, or an
1066 error message on errors. The special error '_decline' is returned for
1067 declined transactions.
1071 Changes a package for this customer.
1073 Takes a hash reference as parameter with the following keys:
1083 Existing customer package.
1087 New package to order (see L<FS::part_pkg>).
1091 Quantity for this package order (default 1).
1095 Returns a hash reference with the following keys:
1101 Empty on success, or an error message on errors.
1105 On success, the new pkgnum
1112 Provides useful info for early renewals.
1114 Takes a hash reference as parameter with the following keys:
1124 Returns a hash reference. On errors, it contains a single key, B<error>, with
1125 the error message. Otherwise, contains a single key, B<dates>, pointing to
1126 an array refernce of hash references. Each hash reference contains the
1133 (Future) Bill date. Indicates a future date for which billing could be run.
1134 Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
1137 =item bill_date_pretty
1139 (Future) Bill date as a human-readable string. (Convenience for display;
1140 subject to change, so best not to parse for the date.)
1144 Base amount which will be charged if renewed early as of this date.
1148 Renewal date; i.e. even-futher future date at which the customer will be paid
1149 through if the early renewal is completed with the given B<bill-date>.
1150 Specified as a integer UNIX timestamp.
1152 =item renew_date_pretty
1154 Renewal date as a human-readable string. (Convenience for display;
1155 subject to change, so best not to parse for the date.)
1159 Package that will be renewed.
1163 Expiration date of the package that will be renewed.
1165 =item expire_date_pretty
1167 Expiration date of the package that will be renewed, as a human-readable
1168 string. (Convenience for display; subject to change, so best not to parse for
1175 Renews this customer early; i.e. runs billing for this customer in advance.
1177 Takes a hash reference as parameter with the following keys:
1187 Integer date as returned by the B<renew_info> function, indicating the advance
1188 date for which to run billing.
1192 Returns a hash reference with a single key, B<error>, empty on success, or an
1193 error message on errors.
1197 Cancels a package for this customer.
1199 Takes a hash reference as parameter with the following keys:
1209 pkgpart of package to cancel
1213 Returns a hash reference with a single key, B<error>, empty on success, or an
1214 error message on errors.
1216 =item provision_acct
1218 Provisions an account (svc_acct).
1220 Takes a hash references as parameter with the following keys:
1230 pkgnum of package into which this service is provisioned
1234 svcpart or service definition to provision
1244 =item provision_phone
1246 Provisions a phone number (svc_phone).
1248 Takes a hash references as parameter with the following keys:
1258 pkgnum of package into which this service is provisioned
1262 svcpart or service definition to provision
1282 E911 Address (optional)
1286 =item provision_external
1288 Provisions an external service (svc_external).
1290 Takes a hash references as parameter with the following keys:
1300 pkgnum of package into which this service is provisioned
1304 svcpart or service definition to provision
1314 =head2 "MY ACCOUNT" QUOTATION FUNCTIONS
1316 All of these functions require the user to be logged in, and the 'session_id'
1317 key to be included in the argument hashref.`
1321 =item list_quotations HASHREF
1323 Returns a hashref listing this customer's active self-service quotations.
1326 - 'quotations', an arrayref containing an element for each quotation.
1327 - quotationnum, the primary key
1328 - _date, the date it was started
1329 - num_pkgs, the number of packages
1330 - total_setup, the sum of setup fees
1331 - total_recur, the sum of recurring charges
1333 =item quotation_new HASHREF
1335 Creates an empty quotation and returns a hashref containing 'quotationnum',
1336 the primary key of the new quotation.
1338 =item quotation_delete HASHREF
1340 Disables (does not really delete) a quotation. Takes the following arguments:
1346 =item quotationnum - the quotation to delete
1350 Returns 'error' => a string, which will be empty on success.
1352 =item quotation_info HASHREF
1354 Returns total and detailed pricing information on a quotation.
1356 Takes the following arguments:
1362 =item quotationnum - the quotation to return
1366 Returns a hashref containing:
1368 - total_setup, the total of setup fees (and their taxes)
1369 - total_recur, the total of all recurring charges (and their taxes)
1370 - sections, an arrayref containing an element for each quotation section.
1371 - description, a line of text describing the group of charges
1372 - subtotal, the total of charges in this group (if appropriate)
1373 - detail_items, an arrayref of line items
1374 - pkgnum, the reference number of the package
1375 - description, the package name (or tax name)
1377 - amount, the amount charged
1378 If the detail item represents a subtotal, it will instead contain:
1379 - total_item: description of the subtotal
1380 - total_amount: the subtotal amount
1383 =item quotation_print HASHREF
1385 Renders the quotation as HTML or PDF. Takes the following arguments:
1391 =item quotationnum - the quotation to return
1393 =item format - 'html' or 'pdf'
1397 Returns a hashref containing 'document', the contents of the file.
1399 =item quotation_add_pkg HASHREF
1401 Adds a package to a quotation. Takes the following arguments:
1407 =item pkgpart - the package to add
1409 =item quotationnum - the quotation to add it to
1411 =item quantity - the package quantity (defaults to 1)
1413 =item address1, address2, city, state, zip, country - address fields to set
1414 the service location
1418 Returns 'error' => a string, which will be empty on success.
1420 =item quotation_remove_pkg HASHREF
1422 Removes a package from a quotation. Takes the following arguments:
1428 =item pkgnum - the primary key (quotationpkgnum) of the package to remove
1430 =item quotationnum - the quotation to remove it from
1434 Returns 'error' => a string, which will be empty on success.
1438 =item quotation_order HASHREF
1440 Converts the packages in a quotation into real packages. Takes the following
1443 Takes the following arguments:
1449 =item quotationnum - the quotation to order
1455 =head1 SIGNUP FUNCTIONS
1459 =item signup_info HASHREF
1461 Takes a hash reference as parameter with the following keys:
1465 =item session_id - Optional agent/reseller interface session
1469 Returns a hash reference containing information that may be useful in
1470 displaying a signup page. The hash reference contains the following keys:
1474 =item cust_main_county
1476 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.
1480 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
1481 an agentnum specified explicitly via reseller interface session_id in the
1486 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.
1488 =item agentnum2part_pkg
1490 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.
1494 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.
1496 =item security_phrase
1498 True if the "security_phrase" feature is enabled
1502 Array reference of acceptable payment types for signup
1508 credit card - automatic
1512 credit card - on-demand - version 1.5+ only
1516 electronic check - automatic
1520 electronic check - on-demand - version 1.5+ only
1528 billing, not recommended for signups
1532 free, definitely not recommended for signups
1536 special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
1542 True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
1546 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".
1552 =item countrydefault
1558 =item new_customer_minimal HASHREF
1560 Creates a new customer.
1562 Current differences from new_customer: An address is not required. promo_code
1563 and reg_code are not supported. If invoicing_list and _password is passed, a
1564 contact will be created with self-service access (no pkgpart or username is
1565 necessary). No initial billing is run (this may change in a future version).
1567 Takes a hash reference as parameter with the following keys:
1573 first name (required)
1577 last name (required)
1581 (not typically collected; mostly used for ACH transactions)
1613 Daytime phone number
1617 Evening phone number
1625 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1629 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1633 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1637 Expiration date for CARD/DCRD
1641 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1643 =item invoicing_list
1645 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),
1647 =item referral_custnum
1649 referring customer number
1657 pkgpart of initial package
1673 Access number (index, not the literal number)
1677 Country code (to be provisioned as a service)
1681 Phone number (to be provisioned as a service)
1689 Returns a hash reference with the following keys:
1695 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)
1699 =item new_customer HASHREF
1701 Creates a new customer. Takes a hash reference as parameter with the
1708 first name (required)
1712 last name (required)
1716 (not typically collected; mostly used for ACH transactions)
1722 =item address1 (required)
1730 =item city (required)
1738 =item state (required)
1742 =item zip (required)
1748 Daytime phone number
1752 Evening phone number
1760 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
1764 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
1768 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
1772 Expiration date for CARD/DCRD
1776 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
1778 =item invoicing_list
1780 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),
1782 =item referral_custnum
1784 referring customer number
1792 pkgpart of initial package
1808 Access number (index, not the literal number)
1812 Country code (to be provisioned as a service)
1816 Phone number (to be provisioned as a service)
1824 Returns a hash reference with the following keys:
1830 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)
1834 =item regionselector HASHREF | LIST
1836 Takes as input a hashref or list of key/value pairs with the following keys:
1840 =item selected_county
1842 Currently selected county
1844 =item selected_state
1846 Currently selected state
1848 =item selected_country
1850 Currently selected country
1854 Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
1858 Specify a javascript subroutine to call on changes
1864 =item default_country
1870 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>.
1874 Returns a list consisting of three HTML fragments for county selection,
1875 state selection and country selection, respectively.
1879 #false laziness w/FS::cust_main_county (this is currently the "newest" version)
1880 sub regionselector {
1887 $param->{'selected_country'} ||= $param->{'default_country'};
1888 $param->{'selected_state'} ||= $param->{'default_state'};
1890 my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
1894 my %cust_main_county;
1896 # unless ( @cust_main_county ) { #cache
1897 #@cust_main_county = qsearch('cust_main_county', {} );
1898 #foreach my $c ( @cust_main_county ) {
1899 foreach my $c ( @{ $param->{'locales'} } ) {
1900 #$countyflag=1 if $c->county;
1901 $countyflag=1 if $c->{county};
1902 #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
1903 #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
1904 $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
1907 $countyflag=1 if $param->{selected_county};
1909 my $script_html = <<END;
1911 function opt(what,value,text) {
1912 var optionName = new Option(text, value, false, false);
1913 var length = what.length;
1914 what.options[length] = optionName;
1916 function ${prefix}country_changed(what) {
1917 country = what.options[what.selectedIndex].text;
1918 for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
1919 what.form.${prefix}state.options[i] = null;
1921 #what.form.${prefix}state.options[0] = new Option('', '', false, true);
1923 foreach my $country ( sort keys %cust_main_county ) {
1924 $script_html .= "\nif ( country == \"$country\" ) {\n";
1925 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1926 my $text = $state || '(n/a)';
1927 $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
1929 $script_html .= "}\n";
1932 $script_html .= <<END;
1934 function ${prefix}state_changed(what) {
1937 if ( $countyflag ) {
1938 $script_html .= <<END;
1939 state = what.options[what.selectedIndex].text;
1940 country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
1941 for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
1942 what.form.${prefix}county.options[i] = null;
1945 foreach my $country ( sort keys %cust_main_county ) {
1946 $script_html .= "\nif ( country == \"$country\" ) {\n";
1947 foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
1948 $script_html .= "\nif ( state == \"$state\" ) {\n";
1949 #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
1950 foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
1951 my $text = $county || '(n/a)';
1953 qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
1955 $script_html .= "}\n";
1957 $script_html .= "}\n";
1961 $script_html .= <<END;
1966 my $county_html = $script_html;
1967 if ( $countyflag ) {
1968 $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
1969 foreach my $county (
1970 sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
1972 my $text = $county || '(n/a)';
1973 $county_html .= qq!<OPTION VALUE="$county"!.
1974 ($county eq $param->{'selected_county'} ?
1981 $county_html .= '</SELECT>';
1984 qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
1987 my $state_html = qq!<SELECT NAME="${prefix}state" !.
1988 qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
1989 foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
1990 my $text = $state || '(n/a)';
1991 my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
1992 $state_html .= "\n<OPTION $selected VALUE=\"$state\">$text</OPTION>"
1994 $state_html .= '</SELECT>';
1996 my $country_html = '';
1997 if ( scalar( keys %cust_main_county ) > 1 ) {
1999 $country_html = qq(<SELECT NAME="${prefix}country" ).
2000 qq(onChange="${prefix}country_changed(this); ).
2001 $param->{'onchange'}.
2004 my $countrydefault = $param->{default_country} || 'US';
2005 foreach my $country (
2006 sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
2007 keys %cust_main_county
2009 my $selected = $country eq $param->{'selected_country'}
2012 $country_html .= "\n<OPTION $selected>$country</OPTION>"
2014 $country_html .= '</SELECT>';
2017 $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
2018 ' VALUE="'. (keys %cust_main_county )[0]. '">';
2022 ($county_html, $state_html, $country_html);
2026 sub regionselector_hashref {
2027 my ($county_html, $state_html, $country_html) = regionselector(@_);
2029 'county_html' => $county_html,
2030 'state_html' => $state_html,
2031 'country_html' => $country_html,
2035 =item location_form HASHREF | LIST
2037 Takes as input a hashref or list of key/value pairs with the following keys:
2043 Current customer session_id
2047 Omit red asterisks from required fields.
2049 =item address1_label
2051 Label for first address line.
2055 Returns an HTML fragment for a location form (address, city, state, zip,
2068 my $session_id = delete $param->{'session_id'};
2070 my $rv = mason_comp( 'session_id' => $session_id,
2071 'comp' => '/elements/location.html',
2072 'args' => [ %$param ],
2076 $rv->{'error'} || $rv->{'output'};
2081 #=item expselect HASHREF | LIST
2083 #Takes as input a hashref or list of key/value pairs with the following keys:
2087 #=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
2089 #=item date - current date, in yyyy-mm-dd or m-d-yyyy format
2093 =item expselect PREFIX [ DATE ]
2095 Takes as input a unique prefix string and the current expiration date, in
2096 yyyy-mm-dd or m-d-yyyy format
2098 Returns an HTML fragments for expiration date selection.
2104 #if ( ref($_[0]) ) {
2108 #my $prefix = $param->{'prefix'};
2109 #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
2110 #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
2112 my $date = scalar(@_) ? shift : '';
2114 my( $m, $y ) = ( 0, 0 );
2115 if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
2116 ( $m, $y ) = ( $2, $1 );
2117 } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
2118 ( $m, $y ) = ( $1, $3 );
2120 my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
2122 $return .= qq!<OPTION VALUE="$_"!;
2123 $return .= " SELECTED" if $_ == $m;
2126 $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
2128 my $thisYear = $t[5] + 1900;
2129 for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
2130 $return .= qq!<OPTION VALUE="$_"!;
2131 $return .= " SELECTED" if $_ == $y;
2134 $return .= "</SELECT>";
2139 =item popselector HASHREF | LIST
2141 Takes as input a hashref or list of key/value pairs with the following keys:
2147 Access number number
2151 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>.
2155 Returns an HTML fragment for access number selection.
2159 #horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
2167 my $popnum = $param->{'popnum'};
2168 my $pops = $param->{'pops'};
2170 return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
2171 return $pops->[0]{city}. ', '. $pops->[0]{state}.
2172 ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
2173 '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
2174 if scalar(@$pops) == 1;
2177 my %popnum2pop = ();
2179 push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
2180 $popnum2pop{$_->{popnum}} = $_;
2185 function opt(what,href,text) {
2186 var optionName = new Option(text, href, false, false)
2187 var length = what.length;
2188 what.options[length] = optionName;
2192 my $init_popstate = $param->{'init_popstate'};
2193 if ( $init_popstate ) {
2194 $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
2195 $init_popstate. '">';
2198 function acstate_changed(what) {
2199 state = what.options[what.selectedIndex].text;
2200 what.form.popac.options.length = 0
2201 what.form.popac.options[0] = new Option("Area code", "-1", false, true);
2205 my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
2206 foreach my $state ( sort { $a cmp $b } @states ) {
2207 $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
2209 foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
2210 $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
2211 if ($ac eq $param->{'popac'}) {
2212 $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
2215 $text .= "}\n" unless $init_popstate;
2217 $text .= "popac_changed(what.form.popac)}\n";
2220 function popac_changed(what) {
2221 ac = what.options[what.selectedIndex].text;
2222 what.form.popnum.options.length = 0;
2223 what.form.popnum.options[0] = new Option("City", "-1", false, true);
2227 foreach my $state ( @states ) {
2228 foreach my $popac ( keys %{ $pop{$state} } ) {
2229 $text .= "\nif ( ac == \"$popac\" ) {\n";
2231 foreach my $pop ( @{$pop{$state}->{$popac}}) {
2232 my $o_popnum = $pop->{popnum};
2233 my $poptext = $pop->{city}. ', '. $pop->{state}.
2234 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2236 $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
2237 if ($popnum == $o_popnum) {
2238 $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
2246 $text .= "}\n</SCRIPT>\n";
2248 $param->{'acstate'} = '' unless defined($param->{'acstate'});
2251 qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
2252 qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
2253 $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
2254 ">$_" foreach sort { $a cmp $b } @states;
2255 $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
2258 qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
2259 qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
2261 $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
2264 #comment this block to disable initial list polulation
2265 my @initial_select = ();
2266 if ( scalar( @$pops ) > 100 ) {
2267 push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
2269 @initial_select = @$pops;
2271 foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
2272 $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
2273 ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
2274 $pop->{city}. ', '. $pop->{state}.
2275 ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
2278 $text .= qq!</SELECT></TD></TR></TABLE>!;
2284 =item domainselector HASHREF | LIST
2286 Takes as input a hashref or list of key/value pairs with the following keys:
2296 Service number of the selected item.
2300 Returns an HTML fragment for domain selection.
2304 sub domainselector {
2311 my $domsvc= $param->{'domsvc'};
2313 domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
2314 my $domains = $rv->{'domains'};
2315 $domsvc = $rv->{'domsvc'} unless $domsvc;
2317 return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
2318 unless scalar(keys %$domains);
2320 if (scalar(keys %$domains) == 1) {
2322 foreach(keys %$domains) {
2325 return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
2326 '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
2329 my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em">!;
2331 $text .= '<OPTION>(Choose Domain)' unless $domsvc;
2333 foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
2334 $text .= qq!<OPTION VALUE="!. $domain. '"'.
2335 ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
2336 $domains->{$domain};
2339 $text .= qq!</SELECT></TD></TR>!;
2345 =item didselector HASHREF | LIST
2347 Takes as input a hashref or list of key/value pairs with the following keys:
2353 Field name for the returned HTML fragment.
2357 Service definition (see L<FS::part_svc>)
2361 Returns an HTML fragment for DID selection.
2373 my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
2374 'args'=>[ %$param ],
2378 $rv->{'error'} || $rv->{'output'};
2384 =head1 RESELLER FUNCTIONS
2386 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
2387 with their active session, and the B<customer_info> and B<order_pkg> functions
2388 with their active session and an additional I<custnum> parameter.
2390 For the most part, development of the reseller web interface has been
2391 superceded by agent-virtualized access to the backend.
2403 =item agent_list_customers
2405 List agent's customers.
2413 L<freeside-selfservice-clientd>, L<freeside-selfservice-server>