Hi,
We at the Happylab also thought long and hard about how we could make a rental solution with Fabman. We solved it like this:
- We have created our own equipment category “Rental Items”.
- Each rental item is created as equipment and assigned to the “Rental Items” category. For a better overview, we have put the rental items in a separate space.
- The billing information is stored in the metadata of the equipment.
- Rental fees are billed automatically via a webhook. Details on this below.
Example and Process
Here is an example of the rental item called “Universal Profile Cutter Set” (I think it’s self-explanatory what the individual parameters of the metadata mean):
Rental
To rent an item, proceed as follows:
- Open the equipment details and click on “Track activity”.
- The current time is already entered as the start of borrowing (and can be changed if necessary). Then confirm with “Save”.
- In the overview you can see which items are or were on loan and when.
Return
- Simply select the article bar in the overview and click on “edit”.
- Select “Set end date”. The current time is automatically suggested and can be changed if necessary. Complete the return with “Save changes”.
- You can check the billing immediately on the affected user’s charges tab.
Billing via Webhook
We have created a webhook with the following parameters:
- URL: https:///ChargeRental.php?active=1&resourcecategory=&secret=
- Descriptive label: Equipment Rental
- Kinds of events: Activity Log
Source Code in PHP (feel free to use it and adapt it to your needs, no support, no warranty):
if ($_GET['secret'] != $token) {
$token = "<PutYourTokenHere>";
print "Webhook not executed due to wrong token (url?secret=token)\n";
exit;
}
if (isset($_GET['active'])) {
if ($_GET['active'] == "0" or strtolower($_GET['active']) == "false") {
print "Webhook is not active.\n";
exit;
}
}
// only charge for eqipments of the right resource category
if (isset($_GET['resourcecategory'])) {
$resourcecategory = $_GET['resourcecategory'];
} else {
echo "resourcecategory has to be set like: ?resourcecategory=237\n";
exit;
}
$obj = json_decode(file_get_contents("php://input"));
var_dump($obj);
echo "\n\n";
$APIurl = "https://fabman.io/api/v1/";
if ($obj->{'details'}->{'resource'}->{'type'} != $resourcecategory) {
echo "resourcecategory is ".$obj->{'details'}->{'resource'}->{'type'}." but should be $resourcecategory\n";
echo "Resource is not a rental item.\n";
exit;
}
if (!isset($obj->{'details'}->{'log'}->{'stopType'})) {
echo "stopType not set.\n";
exit;
}
// read accounting parameter from metadata
$metadata = $obj->{'details'}->{'resource'}->{'metadata'};
var_dump($metadata);
echo ($obj->{'details'}->{'log'}->{'createdAt'}."\n");
$start = new DateTime($obj->{'details'}->{'log'}->{'createdAt'}, new DateTimeZone("UTC"));
$start->setTimezone(new DateTimeZone("Europe/Vienna"));
echo ($obj->{'details'}->{'log'}->{'stoppedAt'}."\n");
$stop = new DateTime($obj->{'details'}->{'log'}->{'stoppedAt'}, new DateTimeZone("UTC"));
$stop->setTimezone(new DateTimeZone("Europe/Vienna"));
$duration = strtotime($obj->{'details'}->{'log'}->{'stoppedAt'}) - strtotime($obj->{'details'}->{'log'}->{'createdAt'});
echo ("Dauer: $duration\n");
$accountingUnits = ceil($duration / $metadata->{'AccountingUnit'}->{'Seconds'});
echo ("accountingUnits: $accountingUnits\n");
$price = $metadata->{'Pricing'}->{'PricePerTransaction'} + $accountingUnits * $metadata->{'Pricing'}->{'PricePerUnit'};
if (isset($metadata->{'DisplayUnit'})) {
$unitName = $metadata->{'DisplayUnit'}->{'Name'};
$units = round($accountingUnits * $metadata->{'AccountingUnit'}->{'Seconds'} / $metadata->{'DisplayUnit'}->{'Seconds'},4);
$unitPrice = $metadata->{'Pricing'}->{'PricePerUnit'} * $metadata->{'DisplayUnit'}->{'Seconds'} / $metadata->{'AccountingUnit'}->{'Seconds'};
} else {
$unitName = $metadata->{'AccountingUnit'}->{'Name'};
$units = $accountingUnits;
$unitPrice = $metadata->{'Pricing'}->{'PricePerUnit'};
}
$cha_text = $units." ".$unitName." á € ".number_format($unitPrice,2,",",".")." - ".$obj->{'details'}->{'resource'}->{'name'}." (".$start->format("Y-m-d H:i")." - ".$stop->format("Y-m-d H:i").")";
# delete previous charges on update
deleteAllChargesFromActivity($obj->{'details'}->{'log'}->{'id'});
# create charge
if (isset($metadata->{'Pricing'}->{'MaxPrice'})) {
$price = min($price, $metadata->{'Pricing'}->{'MaxPrice'});
}
if ($price > 0)
{
$usr_fabman_id = $obj->{'details'}->{'log'}->{'member'};
echo ("$cha_text : ".$price);
createCharge($usr_fabman_id, substr($obj->{'details'}->{'log'}->{'stoppedAt'}, 0, 19), $cha_text, $price, false, $obj->{'details'}->{'log'}->{'id'});
echo " -> Charge in Fabman created.\n";
}
/* Functions */
function callAPI($method, $url, $data = false)
{
global $APIurl;
$url = $APIurl . $url;
#echo "CALL API: $url\n";
$path_to_fabman_cookie = "fabmancookie";
$curl = curl_init();
curl_setopt($curl, CURLOPT_COOKIEJAR, $path_to_fabman_cookie);
curl_setopt($curl, CURLOPT_COOKIEFILE, $path_to_fabman_cookie);
// ignore SSL Zertifikat
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
switch ($method)
{
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data))
);
}
break;
case "PUT":
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data))
);
}
break;
case "DELETE":
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
#print ($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
return array("http_code" => $http_code, "data" => json_decode($result));
}
function createCharge($member,$date,$description,$price,$taxPercent,$resourceLogId = NULL) {
if (strlen($date) === 10) {
$date .= "T00:00:00";
}
fabman_login($fabman_user,$fabman_password);
if ($taxPercent) {
$data = array("member" => $member, "dateTime" => $date, "description" => $description, "price" => $price, "taxPercent" => $taxPercent);
} else {
$data = array("member" => $member, "dateTime" => $date, "description" => $description, "price" => $price);
}
if ($resourceLogId != NULL) {
$data['resourceLog'] = $resourceLogId;
}
$result = callAPI("POST", "charges", json_encode($data));
return $result; // error if $result['http_code'] != 200
}
function deleteAllChargesFromActivity($resourceLog) {
global $obj;
$result = callAPI("GET", "charges?limit=50&resourceLog=$resourceLog&onlyUninvoiced=true");
if ($result['http_code'] != 200) { // error if $result['http_code'] != 200
echo "ERROR: http_code = ".$result['http_code']."\n";
return false;
} else {
foreach ($result['data'] as $charge) {
deleteCharge($charge->{'id'});
}
}
}