{"id":35,"date":"2014-09-17T09:55:44","date_gmt":"2014-09-16T23:55:44","guid":{"rendered":"http:\/\/thewanderingsysadmin.net\/?p=35"},"modified":"2019-07-25T13:54:58","modified_gmt":"2019-07-25T03:54:58","slug":"extending-the-whmcs-api-and-fetching-invoice-pdfs","status":"publish","type":"post","link":"https:\/\/thewanderingsysadmin.net\/?p=35","title":{"rendered":"Extending the WHMCS API and Fetching Invoice PDF&#8217;s"},"content":{"rendered":"<p>At work, we&#8217;re currently doing a FULL rebuild of our customer portal. \u00a0Part of the requirements of the system are that our staff must only be logging into one place. \u00a0At present they have to be logged into the portal AND WHMCS. \u00a0So in v2, we have to basically rebuild 90% of the WHMCS admin interface into our portal, and use the WHMCS API for manipulating data stored in WHMCS. \u00a0(We&#8217;re also adding a MongoDB caching layer for read access, and then using hooks in WHMCS to update the cache whenever data is modified).<\/p>\n<p>We found a few &#8216;holes&#8217; (read: missing functionality) in the WHMCS API, so had to see about adding an API to sit alongside WHMCS. \u00a0We did ask WHMCS how to go about writing custom API functions to use within the WHMCS API framework, but they came back saying this was not possible. \u00a0A fair bit of Googling around, and I managed to find a <a title=\"blog post\" href=\"http:\/\/bobcares.com\/blog\/extend-whmcs-create-your-own-whmcs-api-functions\/\">blog post<\/a> detailing how to write custom API functions for WHMCS. \u00a0With a bit of work, we now have a nice basis for writing WHMCS API modules. \u00a0The first one I built for testing was for pulling Invoice PDF&#8217;s, which is not currently available in the WHMCS API.<\/p>\n<pre class=\"lang:php decode:true \">&lt;?PHP\r\n\r\n\/\/ We don't want someone getting crafy and calling this manually without using the WHMCS API authentications\r\nif (!defined(\"WHMCS\")) {\r\n  die(\"This file cannot be accessed directly\");\r\n}\r\n\r\n\/\/ Borrowed from http:\/\/bobcares.com\/blog\/extend-whmcs-create-your-own-whmcs-api-functions\/\r\nfunction buildParams($vars)\r\n{\r\n  $param = array(\r\n    'action' =&gt; array(),\r\n    'params' =&gt; array()\r\n  );\r\n  \r\n  \/\/ Is cmd set? If so we're being used as Local API\r\n  if (isset($vars['cmd'])) {\r\n    $param['action']    = $vars['cmd'];\r\n    $param['params']    = (object) $vars['apivalues1'];\r\n    $param['adminuser'] = $vars['adminuser'];\r\n  } else {\r\n    \/\/ Nope, then we're in remote API mode..\r\n\r\n    \/\/ Fix for WHMCS &gt; 7.x\r\n    if (!isset($vars['_POST']) &amp;&amp; isset($vars['_POSTbackup'])) {\r\n       $vars['_POST'] = $vars['_POSTbackup'];\r\n    }\r\n    $param['action'] = $vars['_POST']['action'];\r\n    \/\/ Just cleaning up a bit, so these don't go into the params sub array..\r\n    unset($vars['_POST']['username']);\r\n    unset($vars['_POST']['password']);\r\n    unset($vars['_POST']['action']);\r\n    $param['params'] = (object) $vars['_POST'];\r\n  }\r\n  return (object) $param;\r\n}\r\n\r\ntry {\r\n  \r\n  \/\/ Get the arguments\r\n  $vars       = get_defined_vars();\r\n  $postFields = buildParams($vars);\r\n  \r\n  \/\/ Include the WHMCS invoicefunctions\r\n  if (!function_exists('pdfInvoice')) {\r\n    require_once('invoicefunctions.php');\r\n  }\r\n  \/\/ And build an invoice PDF for the given invoiceID  - Note, if this invoice ID is invalid, it will return a blank PDF..\r\n  $pdfdata = pdfInvoice($postFields-&gt;params-&gt;invoiceid);\r\n  \r\n  \/\/ Base64 encode it so it doesn't break the xml or json return\r\n  $doc = base64_encode($pdfdata);\r\n  \r\n  \/\/ And return.  WHMCS takes care of the json\/xml encoding\r\n  $apiresults = array(\r\n    \"result\" =&gt; \"success\",\r\n    \"pdf\" =&gt; $doc,\r\n    \"message\" =&gt; \"Success Message\"\r\n  );\r\n  \r\n}\r\ncatch (Exception $e) {\r\n  \/\/ Return with an error!\r\n  $apiresults = array(\r\n    \"result\" =&gt; \"error\",\r\n    \"message\" =&gt; $e-&gt;getMessage()\r\n  );\r\n}<\/pre>\n<p>And Voila, a simple WHMCS call such as:<\/p>\n<div class=\"mw-geshi mw-code mw-content-ltr\">\n<pre class=\"lang:php decode:true php source-php \">$postfields[\"action\"] = \"saugetinvoicepdf\";\r\n$postfields[\"invoiceid\"] = \"16\";<\/pre>\n<\/div>\n<h3 id=\"SAUGetInvoicePDF-ReturnedVariables\"><span class=\"mw-headline\">Returned Variables<\/span><\/h3>\n<div class=\"mw-geshi mw-code mw-content-ltr\">\n<div class=\"php source-php\">\n<pre class=\"lang:xhtml decode:true de1\"> &lt;whmcsapi&gt;\r\n   &lt;action&gt;saugetinvoicepdf&lt;\/action&gt;\r\n   &lt;result&gt;success&lt;\/result&gt;\r\n   &lt;pdf&gt;......BASE64DATA......&lt;\/pdf&gt;\r\n   &lt;message&gt;Success Message&lt;\/message&gt;\r\n &lt;\/whmcsapi&gt;<\/pre>\n<\/div>\n<\/div>\n<p>Then it&#8217;s a simple matter to go base64_decode($jsondata-&gt;pdf) (assuming you used json format and had done a json_decode() on the returned data&#8230;), and you have the binary data for the PDF, ready to save to disk, or pass back out to the user via HTTP.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At work, we&#8217;re currently doing a FULL rebuild of our customer portal. \u00a0Part of the requirements of the system are that our staff must only be logging into one place. \u00a0At present they have to be logged into the portal AND WHMCS. \u00a0So in v2, we have to basically rebuild 90% of the WHMCS admin [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[12,14],"tags":[],"class_list":["post-35","post","type-post","status-publish","format-standard","hentry","category-php","category-whmcs"],"_links":{"self":[{"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/posts\/35"}],"collection":[{"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=35"}],"version-history":[{"count":6,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/posts\/35\/revisions"}],"predecessor-version":[{"id":56,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=\/wp\/v2\/posts\/35\/revisions\/56"}],"wp:attachment":[{"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=35"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=35"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thewanderingsysadmin.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=35"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}