I.T. Security and Linux Administration

Aug 8 2012   7:25PM GMT

[PHP] Blesta: Support for pay-as-you-go billing



Posted by: Eric Hansen
Tags:
security

For the past almost 2 months now I’ve been working almost from sunrise to sunset, every day, trying to get a new service for my business up and running.  With about 95% of the entire project finished, I set out on trying to get billing to work on a non-consistent basis.

When it comes to typical billing, such as charging $200/month for a continuous service, Blesta is amazing.  It’s easy to use, manage and navigate through.  However, when it comes to creating a pay-as-you-go service, not so much.  This was further evident by the owner of the company (who also runs support) stating that while in v2 of the software (which I have) this isn’t natively possible, it is in v3.  But, he did send me in the right direction as to fixing this, even if it is more of a dirty hack than anything else.

This new project is a backup service with both a pay-per-GB and pay-as-you-use model.  The first type of billing is relatively easy.  All you have to do is update the invoice near billing time reflecting any overages they incurred.  The second method though is where the issue starts.

The best way I found (which I also give Blesta’s owner credit for) is to create a one-time package so the user isn’t invoiced monthly.  From there you can run some database tricks to get things working properly.

@require_once(‘/path/to/blesta/inc/init.php’);

This line is required to initialize the API class, which we will use to generate the invoice.

$q = mysql_query(“SELECT s_dates FROM services WHERE (s_pid=10 OR s_pid=11) AND s_dated = ’0000-00-00′ AND s_dates = ‘”. date(“Y-m-d”, strtotime(“-30 days”)) .”‘”);

The table scheme is a little awkward to follow at first for Blesta v2 (which is fixed in v3).  Essentially you need to know 2 things before running this query: the package IDs of the services (mine are 10  & 11), which can be found by looking in the packages table.  Instead of letting Blesta handle one and not the other, the script handles both.  Next, you need to know what point you want to generate invoices.  For sake of simplicity invoices are created every 30 days of service.

The s_dates column is when the service was created (which will be explained later as to how this works after the first time through).

$api = new API();

Initializing an instance to the Blesta API, which is required to generate the invoice.

$data = array();

// Store the API key (required)
$data['key'] = “<your API key here>”;

// Tell the API that we want to create an invoice
$data['createinvoice'] = true;

$data will be used to store the data to pass along to the API.  We also tell the API we want to create an invoice.

// Create the date billed as today’s date
$data['dateb'] = date(“Y-m-d”);

// The invoice will be due at the same time (3-day late period before stuff happens)
$data['dated'] = date(“Y-m-d”);

My Blesta configuration is set to charge users after 3 days of being late, so the billing and due date will be the same.

// Create the invoice line
$data['line'] = array(‘Service Name’);

// E-mail the invoice out
$data['delieverinv'] = true;

Tell them what the invoice is for ($data['line']), and also tell Blesta to e-mail the invoice.

$q = mysql_query(“SELECT s_uid,s_id,s_pid FROM services WHERE (s_pid=11 OR s_pid=10) AND s_dated = ’0000-00-00′ AND s_dates = ‘”. date(“Y-m-d”, strtotime(“-30 days”)) .”‘”);

We need the user, service and package ID of any services that are due of our new service.  s_dated is for when a service is deleted, so we don’t want to invoice deleted services of users.

while($r = mysql_fetch_assoc($q)){
// Get the sum of all the data usage from the user (upload and download)

$usage = $dl + $ul;

We cycle through each entry and get the total usage amount.  The code I use has been removed simply because it’s specific to my service, and it should be easy enough for you to get it on your own.

// Add a public note to the invoice stating how much usage (and from where) has been used during the month
$data['public_notes'] = “Data usage for the month: “. $dl .” bytes downloaded and “. $ul .” bytes uploaded (total: “. $usage .” bytes).”;

This is the first part the invoice that becomes unique to each one.  Here we simply provide a note on the invoice as to how much their usage was for the billing cycle, in bytes.

// Store the user’s ID
$data['uid'] = $r['s_uid'];

// Set the service ID
$data['sid'] = array(0 => $r['s_id']);

The invoice API requires you to pass along the user’s ID.  However, the service ID (sid) is optional.  I just added it to make things easier for me in the long run.

// Get the price range of how much ‘package’ is
$q1 = mysql_query(“SELECT p_prices FROM packages WHERE p_id=”. $r['s_pid']);
$r1 = mysql_fetch_assoc($q1);

// Format: 0-<price a month>
$price = explode(“-”, $r1['p_prices']);

// We want the actual price
$price = $price[1];

Now we need to get the price of the service to charge the user.  For me it’s a flat-rate fee per GB of usage.  This means that there’s only 1 entry in my p_prices for both of my packages.  Blesta stores the pricing information in the format of <term length>-<price>, so if you have a one-time package set for $5, the p_prices will look like 0-5.00.  Now, if you have a 6-month payment option it would be 6-36.00 (if you charge $36/6 months).

// If usage is <= 1GB charge the user for 1GB of use, otherwise get how many GB’s were used

if($usage < 536870912){

$i = 0;
else if($usage >= 536870912 && $usage <= 1073741824){
$i = 1;
} else{
$i = $usage / 1073741824;
}

My service is based on how many GB’s of service is used, so we only need to know how many GB’s of data is used (1 GB = 1,073,741,824 bytes if you go by 1024 bytes = 1 KB).  However, if we have between 500 MB and 1GB of usage, consider it one GB of usage.  Otherwise, if less than 500 MB, figure no usage.

// Get the total amount due by rounding up the GB used to the highest number
$gb = number_format($price * $i, 2, ‘.’, ”);

$data['amount'] = array(0 => $gb);

Here we round our amount owed ($price * $i) based on the price of their service x how many GB’s they have used.  Since Blesta requires a format of ##.## (i.e.: 2.00 for $2.00), we use number_format to auto-append the decimal with 2 decimal places.  The 4th parameter is basically saying don’t add any extra data for thousands-places (i.e.: 1,000,000 turns into 1000000).

We then specify the amount for line 0 (see $data['line'] for reference).

// base64-encode the data per Blesta API docs
$postdata = array(‘base64′ => base64_encode(serialize($data)));

// Get the response ater decoding and unserializing the data
$resp = unserialize(base64_decode(@$api->processRequest($postdata['base64'], “base64″)));

At this point we ship off the API request to the API call and then return the response from the API call.  Blesta requires the data be formatted and serialized, and since this isn’t passing over the wire any I stuck with base64.

// If successful, update the service start to today’s date
if($res['response'] == “success”){
mysql_query(“UPDATE sfu_blesta.services SET s_dates=’”. date(“Y-m-d”) .”‘ WHERE client_id=”. $r['s_uid'] .” AND s_id=”. $r['s_id']);
}
} // end while()

If the API was successful, then ‘response’ will always be “success”.  At this point, we update the date the service starts to reflect the current date.

Overall this was a learning experience, and I’m looking forward to Blesta v3 so I don’t have to write more hacks like this, but it was still fun.

 Comment on this Post

 
There was an error processing your information. Please try again later.
Thanks. We'll let you know when a new response is added.
Send me notifications when other members comment.

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy

Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: