Note: this is an advanced topic not directly supported by planyo's interface and requiring programming skills.
Planyo's Pricing Manager supports a vast range of pricing models that include seasonal pricing and discounts depending on a number of conditions (
see this question for more info).
If this doesn't fit your pricing model, you can specify a completely custom pricing scheme in form of an external script located on your web server that will return the price based on the input parameters passed to your script by planyo, such as rental start/end time, number of persons, number of resources rented and resource ID.
To use a custom pricing scheme, instead of specifying price in resource settings (you can also do this in the
Default price rule in
Pricing Manager), specify the URL of your script (e.g. http://yoursite.com/scriptname.php). Please note that further
Pricing Manager rules (after
Default price) will not be applied when custom script is used. The following parameters are sent (POST method):
- start (int) -- start time (sent as timestamp, that is the number of seconds since January 1 1970 00:00:00)
- end (int) -- end time (sent as timestamp)
- persons (int) -- number of persons
- resource (int) -- id of the resource to be rented
- resname (string) -- name of the resource to be rented
- site_id (int) -- id of the planyo site
- count (int) -- number of resources selected
- units (int) -- number of time units for rental
- The script also receives all additional reservation data (depending on which data is specified for given resource). For example, if you added to the reservation form a check box 'Airport transfer' it would be passed as the parameter 'Airport_transfer'. Note that space and other non-alphanumerical characters are replaced with underscore. Checkboxes, if selected, will have the value: on (no value otherwise).
- user (int) - ID of the user who's making the reservation (passed only when possible to extract user ID based on the email address, if already entered into the form)
- email - if already entered into the form or associated with the reservation
- user_xxx - all user-specific properties are passed to the script with the user_ prefix
- site_xxx - all site properties are passed to the script with the site_ prefix
- prop_res_xxx - all resource-specific properties are passed to the script with the prop_res_xxx prefix
- zip (string) - contains user's zip code
- country (string) - user's country 2-letter code (ISO 3166), e.g. US for the USA, DE for Germany
- currency (string) - currency used by the resource
- assignment1, assignment2, ... - if the resource uses unit names and they are known/selected, they will be passed in these parameters
- language (string) - 2-letter code (ISO 639-1) of the language used by the customer
- voucher_discount (string) - discount to be applied by the script to the calculated price based on the voucher code used; this will never be set if you don't use vouchers. This can be in form: X, Y% or X+Y% depending on how you use vouchers
- voucher (string) - voucher code used (if any)
- existing_rental_id (int), existing_rental_creation (int), existing_price (float), existing_regular_price (float), existing_product_price (float), existing_custom_product_price (float) - if the pricing script is calculating the price of a reservation which already has an ID, the reservation id and creation time (as a timestamp) are passed here, as well as the previously calculated price and regular price (they will be different only if a special discount was used). NOTE: if a special discount was used (existing_regular_price different than existing_price) then if you want to still use the same discount, you must deduct it from the returned price
- shopping_cart_id (int) - ID of the shopping cart if the pricing script is called to calculate the price of a reservation already in user's shopping cart. Planyo will call the pricing script again after each new item is added to the shopping cart so you can apply a discount based on other cart items
- shopping_cart_items (string) - comma separated list of all reservation IDs in the user's shopping cart
- shopping_cart_items_array (json array) - json-encoded list of all the remaining shopping cart items from the same cart
- admin_mode - set to 1 if it's the administrator or moderator who is entering the reservation, 0 otherwise
There are three possibilities with respect to what the script returns:
- To indicate that the resource can be rented: return a simple amount (float) such as "10.00" or "105.80" without any other content. The amount should be the gross price (incl. taxes if any).
- If you want an error message to be displayed to the client, the script should return the text Error: followed by your error message. This is very useful if you want to specify custom unavailability rules (e.g. on weekends reservations must be for min. 2 hours)
- The script can also return complex information about the price, in form of a JSON-encoded array. This array should contain the following keys:
- can_reserve (bool obligatory): true or false depending on whether the customer should be able to make the reservation. Even if set to false, the admins/moderators will be able to make the reservation but they'll see the warning message set in error_text. See the next array key if you want to change this.
- prevent_admin_reservation (bool optional): if can_reserve is set to false and prevent_admin_reservation is set to true, admins/moderators will not be allowed to enter the reservation into the system
- price (float obligatory if reservation is possible): gross price (incl taxes)
- regular_price (float optional): in case a voucher or another discount was used, you can specify the regular price which the customer would have payed without the voucher discount. This is used for example to indicate the discount on the invoice. You can skip this field or set it to the same value as price in case no voucher was used.
- dependencies (array optional): if the price being calculated depends on either the personal fields (one of: zip, country) or reservation form items, then you must return an array of the field names the price depends on. Note that the price is always assumed to depend on the following fields: resource, start, end, count, email, voucher, all fields of type: number of persons, number of adults, number of children. The price normally depends on these fields and you don't need to include them in dependencies. Here's an example of the value of dependencies in case your price depends on fields: transportation and extra_insurance:
{"price":..., "dependencies": ["transportation", "extra_insurance"], ...}.
Note that if you have different dependencies for different parameters sent to your pricing script (e.g. for different resources etc.), you must always return all possible dependencies.
- info_text (string optional): you can specify HTML code to be displayed under the price. This can be a price breakdown or any other information you wish to show to the customer. You can apply basic formatting, e.g. add a SPAN tag with a class attribute. This key can be skipped.
- error_text (string optional): if can_reserve is set to false, this field should include the error message (reason why reservation is not possible).
- deposit (float optional): you can override the default deposit amount (set in resource properties) by specifying it here. This way the deposit (pre-payment amount) can depend on any specific data passed to the script
- pricing_custom_properties (array optional): you can optionally return custom properties as a named array in this key. These values will be saved and you'll be able to view them in the reservation details (admins only), you will also be able to use them in invoices and other templates using the $(prop_pricing_xxx) tag (see FAQ Q204), for example for a custom item named abc the tag will be $(prop_pricing_abc).
Here's an example of a JSON array which could be returned:
{"price":15,"regular_price":15,"info_text":"<span>3 days @ $5.00 = $15.00</span>","can_reserve":true,"error_text":null,"deposit":1.53}
Dependencies of the price on your reservation form fields
Making calls to your pricing script can be resource-intensive, especially if your calculations need to access data from external sources. This is why Planyo needs to understand exactly which fields modify the price. As explained in the paragraph above describing
dependencies, if your price depends on values of any non-standard fields (personal fields except for user's email address or custom reservation form items) then you must indicate which fields your script uses. In such case your response must be a JSON array and not a single float (price).
Failing to print the price dependencies will simply mean that the customer won't immediately see the updated price if they change a field which should update the price. Note that the price calculated for the reservations will always be correct, regardless of whether you indicate the dependencies or not.
Sample script
Here's a sample PHP script that can calculate a different price for weekly and daily rentals as well as for different seasons:
< ?php
// date/time functions will work fine with timezone set to GMT
date_default_timezone_set (GMT);
// sample seasons and pricing here
define ('HIGH_SEASON_MONTH_START', 7); // first month of high season (without discount)
define ('HIGH_SEASON_MONTH_END', 9); // last month of high season (without discount)
define ('LOW_SEASON_DISCOUNT', 20); // discount in %
define ('WEEKLY_PRICE', 100); // price for a week in EUR
define ('DAILY_PRICE', 20); // price per day in EUR
function get_param ($name) {
return $_REQUEST [$name];
}
// parameters passed by planyo
$units = get_param ('units'); // number of time units
$count = get_param ('count'); // number of resources
$start_date_stamp = get_param ('start'); // start date
$end_date_stamp = get_param ('end'); // end date
$persons = get_param ('persons'); // number of persons
$resource = get_param ('resource'); // resource id
// start date
$start_date = getdate ($start_date_stamp);
$month = $start_date ['mon'];
// season-based discount
if ($month < HIGH_SEASON_MONTH_START || $month > HIGH_SEASON_MONTH_END)
$discount_factor = 100 - LOW_SEASON_DISCOUNT;
else
$discount_factor = 100;
// use weekly or daily price
if ($units >= 7) {
$weeks = ceil ($units / 7);
$price = $weeks * WEEKLY_PRICE * $discount_factor / 100;
}
else {
$price = $units * DAILY_PRICE * $discount_factor / 100;
}
echo $price;
? >
Testing the pricing script
You can test/debug your pricing script by going to
http://www.planyo.com/reserve-test.php (you need to be logged in first). Here you can enter your custom pricing URL, choose the dates and under the price shown you can click on the link 'Price URL used'. This will give you the exact parameters passed by Planyo to your script for given input parameters (start/end time, number of persons etc.).
Pricing script with prefetching
Using the pricing script can have one negative effect: it can make the resource search slow -- more specifically, this happens when the search results display the price. In such case, the pricing script must be called as many times as the number of search results. As you can imagine, calling a script on your server 100 times (if you have 100 resources available) can take some time..
This problem can be solved by adding prefetching to your pricing script. In case of search (and other operations where the price must be known for many different data sets at once), planyo will also pass the additional data sets for which the price must be calculated. The additional data sets will include different start/end times, quantity and resource ID. These parameters are passed as:
priceX-start,
priceX-end,
priceX-resource,
priceX-count where X is the data set number starting at 2, e.g. price2-start, price2-end, price2-resource, price2-count then price3-start, price3-end, price3-resource, price3-count and so on.. All other parameters should be taken from the first (original) data set, only these 4 are passed for each data set.
If using prefetching you must return a JSON array with the additional price saved in the keys price2, price3, price4 etc. These prices can in turn be either an array with full information or a single price, e.g. both of the following results are valid:
{"price":15,"regular_price":15,"info_text":"
3 days @ $5.00 = $15.00","can_reserve":true,"error_text":null,"deposit":1.53, "price2":20, "price3":25}
or
{"price":15,"regular_price":15,"info_text":"
3 days @ $5.00 = $15.00","can_reserve":true,"error_text":null,"deposit":1.53, "price2": {"price":20,"regular_price":25}, "price3": {"price":25,"regular_price":35}}
For your convenience you can download a
pricing script template and use it as the basis for your pricing script. Prefetching is built into this script so you don't have to worry about it and you'll only need to update a single function to return a price based on given parameters. See the comments inside the script.
Using a custom script only for availability
There is also a possibility of continuing to use the pricing manager while having planyo call an external script on your server only to find out in a resource can be reserved or not (on top of the other regular constraints). The unavailability script will be called using the same parameters as the pricing script (see above) and - similarly to the pricing script - should return a JSON array with the keys:
can_reserve,
info_text,
error_text,
prevent_admin_reservation (also see above for the details). The only required key is
can_reserve although if reservation is not possible it's strongly recommended to return the reason in
error_text. The other array keys used by the pricing script (e.g.
price) will be ignored. Here's are two examples of unavailability script responses:
{"can_reserve":false,"error_text":"Reservation not possible because of national holiday"}
or
{"can_reserve":true}
For your convenience you can download an
unavailability script template and use it as the basis for your unavailability script. Prefetching is built into this script so you don't have to worry about it and you'll only need to update a single function to return availability based on given parameters. See the comments inside the script.
Note that the unavailability script will also work with prefetching, see the details above.
In order to be able to define the unavailability script
use this special link. You will see a new field with the URL of the script in resource settings / time-related settings.