From dbba5a7427b819c1c0a58910549b363eab38385b Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Mon, 26 Jan 2015 13:10:10 -0500 Subject: [PATCH] Decouple Billing and S3 cloud storage stuff from Zend --- ...zon_S3.php => Amazon_S3StorageBackend.php} | 9 +- .../cloud_storage/FileStorageBackend.php | 41 +++ .../cloud_storage/ProxyStorageBackend.php | 15 +- .../cloud_storage/StorageBackend.php | 5 +- airtime_mvc/application/common/Billing.php | 321 ++++++++++++++++ .../controllers/BillingController.php | 347 +----------------- .../application/forms/BillingClient.php | 2 +- .../forms/BillingUpgradeDowngrade.php | 4 +- airtime_mvc/application/models/RabbitMq.php | 6 +- .../rest/controllers/MediaController.php | 6 +- .../views/scripts/billing/upgrade.phtml | 4 +- 11 files changed, 416 insertions(+), 344 deletions(-) rename airtime_mvc/application/cloud_storage/{Amazon_S3.php => Amazon_S3StorageBackend.php} (90%) create mode 100644 airtime_mvc/application/cloud_storage/FileStorageBackend.php create mode 100644 airtime_mvc/application/common/Billing.php diff --git a/airtime_mvc/application/cloud_storage/Amazon_S3.php b/airtime_mvc/application/cloud_storage/Amazon_S3StorageBackend.php similarity index 90% rename from airtime_mvc/application/cloud_storage/Amazon_S3.php rename to airtime_mvc/application/cloud_storage/Amazon_S3StorageBackend.php index 994dd249b..ab3eb935f 100644 --- a/airtime_mvc/application/cloud_storage/Amazon_S3.php +++ b/airtime_mvc/application/cloud_storage/Amazon_S3StorageBackend.php @@ -1,7 +1,7 @@ s3Client->deleteMatchingObjects( $bucket = $this->getBucket(), - $prefix = self::getAmazonS3FilePrefix()); - + $prefix = $this->getFilePrefix()); } - public static function getAmazonS3FilePrefix() + public function getFilePrefix() { - $clientCurrentAirtimeProduct = BillingController::getClientCurrentAirtimeProduct(); + $clientCurrentAirtimeProduct = Billing::getClientCurrentAirtimeProduct(); $hostingId = $clientCurrentAirtimeProduct["id"]; return substr($hostingId, -2)."/".$hostingId; } diff --git a/airtime_mvc/application/cloud_storage/FileStorageBackend.php b/airtime_mvc/application/cloud_storage/FileStorageBackend.php new file mode 100644 index 000000000..65df4d55b --- /dev/null +++ b/airtime_mvc/application/cloud_storage/FileStorageBackend.php @@ -0,0 +1,41 @@ +storageBackend = new $storageBackend($CC_CONFIG[$storageBackend]); + if ($storageBackend == "Amazon_S3") { + $this->storageBackend = new Amazon_S3StorageBackend($CC_CONFIG["Amazon_S3"]); + } else if ($storageBackend == "file") { + $this->storageBackend = new FileStorageBackend(); + } else { + $this->storageBackend = new $storageBackend($CC_CONFIG[$storageBackend]); + } } public function getAbsoluteFilePath($resourceId) @@ -51,4 +58,8 @@ class ProxyStorageBackend extends StorageBackend $this->storageBackend->deleteAllCloudFileObjects(); } + public function getFilePrefix() + { + $this->storageBackend->getFilePrefix(); + } } diff --git a/airtime_mvc/application/cloud_storage/StorageBackend.php b/airtime_mvc/application/cloud_storage/StorageBackend.php index 463d99071..5abcb6d0c 100644 --- a/airtime_mvc/application/cloud_storage/StorageBackend.php +++ b/airtime_mvc/application/cloud_storage/StorageBackend.php @@ -26,7 +26,10 @@ abstract class StorageBackend /** Deletes all objects (files) stored on the cloud service. To be used * for station termination */ abstract public function deleteAllCloudFileObjects(); - + + /** Get a prefix for the file (which is usually treated like a directory in the cloud) */ + abstract public function getFilePrefix(); + protected function getBucket() { return $this->bucket; diff --git a/airtime_mvc/application/common/Billing.php b/airtime_mvc/application/common/Billing.php new file mode 100644 index 000000000..96aada006 --- /dev/null +++ b/airtime_mvc/application/common/Billing.php @@ -0,0 +1,321 @@ + $_SERVER["WHMCS_USERNAME"], + "password" => $_SERVER["WHMCS_PASSWORD"], + "url" => "https://account.sourcefabric.com/includes/api.php?accesskey=".$_SERVER["WHMCS_ACCESS_KEY"], + ); + } + + /** Get the Airtime instance ID of the instance the customer is currently viewing. */ + public static function getClientInstanceId() + { + $currentProduct = BillingController::getClientCurrentAirtimeProduct(); + return $currentProduct["id"]; + } + + public static function getProducts() + { + //Making this static to cache the products during a single HTTP request. + //This saves us roundtrips to WHMCS if getProducts() is called multiple times. + static $result = array(); + if (!empty($result)) + { + return $result["products"]["product"]; + } + + $credentials = self::getAPICredentials(); + + $postfields = array(); + $postfields["username"] = $credentials["username"]; + $postfields["password"] = md5($credentials["password"]); + $postfields["action"] = "getproducts"; + $postfields["responsetype"] = "json"; + //gid is the Airtime product group id on whmcs + $postfields["gid"] = "15"; + + $query_string = ""; + foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; + + $result = self::makeRequest($credentials["url"], $query_string); + //Logging::info($result["products"]["product"]); + $products = $result["products"]["product"]; + + //Blacklist all free plans + foreach ($products as $k => $p) { + Logging::info($p); + if ($p["paytype"] === "free") + { + unset($products[$k]); + } + } + + return $products; + } + + public static function getProductPricesAndTypes() + { + $products = Billing::getProducts(); + $productPrices = array(); + $productTypes = array(); + + foreach ($products as $k => $p) { + $productPrices[$p["name"]] = array( + "monthly" => $p["pricing"]["USD"]["monthly"], + "annually" => $p["pricing"]["USD"]["annually"] + ); + $productTypes[$p["pid"]] = $p["name"] . " ($" . $productPrices[$p['name']]['monthly'] . "/mo)"; + } + return array($productPrices, $productTypes); + } + + /** Get the plan (or product in WHMCS lingo) that the customer is currently on. + * @return An associative array containing the fields for the product + * */ + public static function getClientCurrentAirtimeProduct() + { + static $airtimeProduct = null; + //Ghetto caching to avoid multiple round trips to WHMCS + if ($airtimeProduct) { + return $airtimeProduct; + } + $credentials = self::getAPICredentials(); + + $postfields = array(); + $postfields["username"] = $credentials["username"]; + $postfields["password"] = md5($credentials["password"]); + $postfields["action"] = "getclientsproducts"; + $postfields["responsetype"] = "json"; + $postfields["clientid"] = Application_Model_Preference::GetClientId(); + + $query_string = ""; + foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; + + $result = self::makeRequest($credentials["url"], $query_string); + + //XXX: Debugging / local testing + if ($_SERVER['SERVER_NAME'] == "airtime.localhost") { + $_SERVER['SERVER_NAME'] = "bananas.airtime.pro"; + } + + //This code must run on airtime.pro for it to work... it's trying to match + //the server's hostname with the client subdomain. Once it finds a match + //between the product and the server's hostname/subdomain, then it + //returns the ID of that product (aka. the service ID of an Airtime instance) + foreach ($result["products"]["product"] as $product) + { + if (strpos($product["groupname"], "Airtime") === FALSE) + { + //Ignore non-Airtime products + continue; + } + else + { + if ($product["status"] === "Active") { + $airtimeProduct = $product; + $subdomain = ''; + + foreach ($airtimeProduct['customfields']['customfield'] as $customField) { + if ($customField['name'] === SUBDOMAIN_WHMCS_CUSTOM_FIELD_NAME) { + $subdomain = $customField['value']; + if (($subdomain . ".airtime.pro") === $_SERVER['SERVER_NAME']) { + return $airtimeProduct; + } + } + } + } + } + } + throw new Exception("Unable to match subdomain to a service ID"); + } + + public static function getClientDetails() + { + try { + $credentials = self::getAPICredentials(); + + $postfields = array(); + $postfields["username"] = $credentials["username"]; + $postfields["password"] = md5($credentials["password"]); + $postfields["action"] = "getclientsdetails"; + $postfields["stats"] = true; + $postfields["clientid"] = Application_Model_Preference::GetClientId(); + $postfields["responsetype"] = "json"; + + $query_string = ""; + foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; + + $arr = self::makeRequest($credentials["url"], $query_string); + return $arr["client"]; + } catch (Exception $e) { + Logging::info($e->getMessage()); + } + return array(); + } + + public static function makeRequest($url, $query_string) { + try { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6 + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); //Aggressive 5 second timeout + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + $jsondata = curl_exec($ch); + if (curl_error($ch)) { + //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch)); + throw new Exception("WHMCS server down or invalid request."); + } + curl_close($ch); + + return json_decode($jsondata, true); + } catch (Exception $e) { + Logging::info($e->getMessage()); + } + return array(); + } + + public static function ensureClientIdIsValid() + { + if (Application_Model_Preference::GetClientId() == null) + { + throw new Exception("Invalid client ID: " . Application_Model_Preference::GetClientId()); + } + } + + + /** + * @return True if VAT should be applied to the order, false otherwise. + */ + public static function checkIfVatShouldBeApplied($vatNumber, $countryCode) + { + if ($countryCode === 'UK') { + $countryCode = 'GB'; //VIES database has it as GB + } + //We don't charge you VAT if you're not in the EU + if (!Billing::isCountryInEU($countryCode)) + { + return false; + } + + //So by here, we know you're in the EU. + + //No VAT number? Then we charge you VAT. + if (empty($vatNumber)) { + return true; + } + //Check if VAT number is valid + return Billing::validateVATNumber($vatNumber, $countryCode); + } + + public static function isCountryInEU($countryCode) + { + $euCountryCodes = array('BE', 'BG', 'CZ', 'DK', 'DE', 'EE', 'IE', 'EL', 'ES', 'FR', + 'HR', 'IT', 'CY', 'LV', 'LT', 'LU', 'HU', 'MT', 'NL', 'AT', + 'PL', 'PT', 'RO', 'SI', 'SK', 'FI', 'SE', 'UK', 'GB'); + + if (!in_array($countryCode, $euCountryCodes)) { + return false; + } + return true; + } + + /** + * Check if an EU VAT number is valid, using the EU VIES validation web API. + * + * @param string $vatNumber - A VAT identifier (number), with or without the two letter country code at the + * start (either one works) . + * @param string $countryCode - A two letter country code + * @return boolean true if the VAT number is valid, false otherwise. + */ + public static function validateVATNumber($vatNumber, $countryCode) + { + $vatNumber = str_replace(array(' ', '.', '-', ',', ', '), '', trim($vatNumber)); + + //If the first two letters are a country code, use that as the country code and remove those letters. + $firstTwoCharacters = substr($vatNumber, 0, 2); + if (preg_match("/[a-zA-Z][a-zA-Z]/", $firstTwoCharacters) === 1) { + $countryCode = strtoupper($firstTwoCharacters); //The country code from the VAT number overrides your country. + $vatNumber = substr($vatNumber, 2); + } + $client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl"); + + if($client){ + $params = array('countryCode' => $countryCode, 'vatNumber' => $vatNumber); + try{ + $r = $client->checkVat($params); + if($r->valid == true){ + // VAT-ID is valid + return true; + } else { + // VAT-ID is NOT valid + return false; + } + } catch(SoapFault $e) { + Logging::error('VIES EU VAT validation error: '.$e->faultstring); + if ($e->faultstring == "INVALID_INPUT") { + return false; + } + //If there was another error with the VAT validation service, we allow + //the VAT number to pass. (eg. SERVER_BUSY, MS_UNAVAILABLE, TIMEOUT, SERVICE_UNAVAILABLE) + return true; + } + } else { + // Connection to host not possible, europe.eu down? + Logging::error('VIES EU VAT validation error: Host unreachable'); + //If there was an error with the VAT validation service, we allow + //the VAT number to pass. + return true; + } + return false; + } + + + public static function addVatToInvoice($invoice_id) + { + $credentials = self::getAPICredentials(); + + //First we need to get the invoice details: sub total, and total + //so we can calcuate the amount of VAT to add + $invoicefields = array(); + $invoicefields["username"] = $credentials["username"]; + $invoicefields["password"] = md5($credentials["password"]); + $invoicefields["action"] = "getinvoice"; + $invoicefields["invoiceid"] = $invoice_id; + $invoicefields["responsetype"] = "json"; + + $invoice_query_string = ""; + foreach ($invoicefields as $k=>$v) $invoice_query_string .= "$k=".urlencode($v)."&"; + + //TODO: error checking + $result = Billing::makeRequest($credentials["url"], $invoice_query_string); + + $vat_amount = $result["subtotal"] * (VAT_RATE/100); + $invoice_total = $result["total"] + $vat_amount; + + //Second, update the invoice with the VAT amount and updated total + $postfields = array(); + $postfields["username"] = $credentials["username"]; + $postfields["password"] = md5($credentials["password"]); + $postfields["action"] = "updateinvoice"; + $postfields["invoiceid"] = $invoice_id; + $postfields["tax"] = "$vat_amount"; + $postfields["taxrate"] = strval(VAT_RATE); + $postfields["total"] = "$invoice_total"; + $postfields["responsetype"] = "json"; + + $query_string = ""; + foreach ($postfields as $k=>$v) $query_string .= "$k=".urlencode($v)."&"; + + //TODO: error checking + $result = Billing::makeRequest($credentials["url"], $query_string); + } + +} \ No newline at end of file diff --git a/airtime_mvc/application/controllers/BillingController.php b/airtime_mvc/application/controllers/BillingController.php index fd7f90c10..fda5c881d 100644 --- a/airtime_mvc/application/controllers/BillingController.php +++ b/airtime_mvc/application/controllers/BillingController.php @@ -1,5 +1,6 @@ view->headLink()->appendStylesheet($baseUrl.'css/billing.css?'.$CC_CONFIG['airtime_version']); - BillingController::ensureClientIdIsValid(); + Billing::ensureClientIdIsValid(); $request = $this->getRequest(); $form = new Application_Form_BillingUpgradeDowngrade(); @@ -31,18 +32,18 @@ class BillingController extends Zend_Controller_Action { $formData = $request->getPost(); if ($form->isValid($formData)) { - $credentials = self::getAPICredentials(); + $credentials = Billing::getAPICredentials(); //Check if VAT should be applied or not to this invoice. if (in_array("7", $formData["customfields"])) { - $apply_vat = BillingController::checkIfVatShouldBeApplied($formData["customfields"]["7"], $formData["country"]); + $apply_vat = Billing::checkIfVatShouldBeApplied($formData["customfields"]["7"], $formData["country"]); } else { $apply_vat = false; } $placeAnUpgradeOrder = true; - $currentPlanProduct = BillingController::getClientCurrentAirtimeProduct(); + $currentPlanProduct = Billing::getClientCurrentAirtimeProduct(); $currentPlanProductId = $currentPlanProduct["pid"]; $currentPlanProductBillingCycle = strtolower($currentPlanProduct["billingcycle"]); //If there's been no change in the plan or the billing cycle, we should not @@ -61,7 +62,7 @@ class BillingController extends Zend_Controller_Action { $postfields["action"] = "upgradeproduct"; $postfields["clientid"] = Application_Model_Preference::GetClientId(); - $postfields["serviceid"] = self::getClientInstanceId(); + $postfields["serviceid"] = Billing::getClientInstanceId(); $postfields["type"] = "product"; $postfields["newproductid"] = $formData["newproductid"]; $postfields["newproductbillingcycle"] = $formData["newproductbillingcycle"]; @@ -92,7 +93,7 @@ class BillingController extends Zend_Controller_Action { foreach ($clientfields AS $k=>$v) $client_query_string .= "$k=".urlencode($v)."&"; //Update the client details in WHMCS first - $result = $this->makeRequest($credentials["url"], $client_query_string); + $result = Billing::makeRequest($credentials["url"], $client_query_string); Logging::info($result); if ($result["result"] == "error") { $this->setErrorMessage(); @@ -109,7 +110,7 @@ class BillingController extends Zend_Controller_Action { } //Then place an upgrade order in WHMCS - $result = $this->makeRequest($credentials["url"], $upgrade_query_string); + $result = Billing::makeRequest($credentials["url"], $upgrade_query_string); if ($result["result"] == "error") { Logging::info($_SERVER['HTTP_HOST']." - Account upgrade failed. - ".$result["message"]); $this->setErrorMessage(); @@ -123,7 +124,7 @@ class BillingController extends Zend_Controller_Action { $this->_helper->viewRenderer->setNoRender(true); if ($apply_vat) { - $this->addVatToInvoice($result["invoiceid"]); + Billing::addVatToInvoice($result["invoiceid"]); } self::viewInvoice($result["invoiceid"]); } @@ -149,7 +150,7 @@ class BillingController extends Zend_Controller_Action { $formData = $request->getPost(); //Set the return JSON value - $this->_helper->json(array("result"=>BillingController::isCountryInEU($formData["country"]))); + $this->_helper->json(array("result"=>Billing::isCountryInEU($formData["country"]))); } public function vatValidatorAction() @@ -170,135 +171,9 @@ class BillingController extends Zend_Controller_Action { } //Set the return JSON value - $this->_helper->json(array("result"=>BillingController::checkIfVatShouldBeApplied($vatNumber, $formData["country"]))); + $this->_helper->json(array("result"=>Billing::checkIfVatShouldBeApplied($vatNumber, $formData["country"]))); } - - /** - * @return True if VAT should be applied to the order, false otherwise. - */ - private static function checkIfVatShouldBeApplied($vatNumber, $countryCode) - { - if ($countryCode === 'UK') { - $countryCode = 'GB'; //VIES database has it as GB - } - //We don't charge you VAT if you're not in the EU - if (!BillingController::isCountryInEU($countryCode)) - { - return false; - } - - //So by here, we know you're in the EU. - - //No VAT number? Then we charge you VAT. - if (empty($vatNumber)) { - return true; - } - //Check if VAT number is valid - return BillingController::validateVATNumber($vatNumber, $countryCode); - } - - private static function isCountryInEU($countryCode) - { - $euCountryCodes = array('BE', 'BG', 'CZ', 'DK', 'DE', 'EE', 'IE', 'EL', 'ES', 'FR', - 'HR', 'IT', 'CY', 'LV', 'LT', 'LU', 'HU', 'MT', 'NL', 'AT', - 'PL', 'PT', 'RO', 'SI', 'SK', 'FI', 'SE', 'UK', 'GB'); - if (!in_array($countryCode, $euCountryCodes)) { - return false; - } - return true; - } - - /** - * Check if an EU VAT number is valid, using the EU VIES validation web API. - * - * @param string $vatNumber - A VAT identifier (number), with or without the two letter country code at the - * start (either one works) . - * @param string $countryCode - A two letter country code - * @return boolean true if the VAT number is valid, false otherwise. - */ - private static function validateVATNumber($vatNumber, $countryCode) - { - $vatNumber = str_replace(array(' ', '.', '-', ',', ', '), '', trim($vatNumber)); - - //If the first two letters are a country code, use that as the country code and remove those letters. - $firstTwoCharacters = substr($vatNumber, 0, 2); - if (preg_match("/[a-zA-Z][a-zA-Z]/", $firstTwoCharacters) === 1) { - $countryCode = strtoupper($firstTwoCharacters); //The country code from the VAT number overrides your country. - $vatNumber = substr($vatNumber, 2); - } - $client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl"); - - if($client){ - $params = array('countryCode' => $countryCode, 'vatNumber' => $vatNumber); - try{ - $r = $client->checkVat($params); - if($r->valid == true){ - // VAT-ID is valid - return true; - } else { - // VAT-ID is NOT valid - return false; - } - } catch(SoapFault $e) { - Logging::error('VIES EU VAT validation error: '.$e->faultstring); - if ($e->faultstring == "INVALID_INPUT") { - return false; - } - //If there was another error with the VAT validation service, we allow - //the VAT number to pass. (eg. SERVER_BUSY, MS_UNAVAILABLE, TIMEOUT, SERVICE_UNAVAILABLE) - return true; - } - } else { - // Connection to host not possible, europe.eu down? - Logging::error('VIES EU VAT validation error: Host unreachable'); - //If there was an error with the VAT validation service, we allow - //the VAT number to pass. - return true; - } - return false; - } - - - private function addVatToInvoice($invoice_id) - { - $credentials = self::getAPICredentials(); - - //First we need to get the invoice details: sub total, and total - //so we can calcuate the amount of VAT to add - $invoicefields = array(); - $invoicefields["username"] = $credentials["username"]; - $invoicefields["password"] = md5($credentials["password"]); - $invoicefields["action"] = "getinvoice"; - $invoicefields["invoiceid"] = $invoice_id; - $invoicefields["responsetype"] = "json"; - - $invoice_query_string = ""; - foreach ($invoicefields as $k=>$v) $invoice_query_string .= "$k=".urlencode($v)."&"; - - //TODO: error checking - $result = $this->makeRequest($credentials["url"], $invoice_query_string); - - $vat_amount = $result["subtotal"] * (VAT_RATE/100); - $invoice_total = $result["total"] + $vat_amount; - - //Second, update the invoice with the VAT amount and updated total - $postfields = array(); - $postfields["username"] = $credentials["username"]; - $postfields["password"] = md5($credentials["password"]); - $postfields["action"] = "updateinvoice"; - $postfields["invoiceid"] = $invoice_id; - $postfields["tax"] = "$vat_amount"; - $postfields["taxrate"] = strval(VAT_RATE); - $postfields["total"] = "$invoice_total"; - $postfields["responsetype"] = "json"; - - $query_string = ""; - foreach ($postfields as $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - - //TODO: error checking - $result = $this->makeRequest($credentials["url"], $query_string); - } private function setErrorMessage($msg=null) { @@ -318,21 +193,12 @@ class BillingController extends Zend_Controller_Action { } } - private static function getAPICredentials() - { - return array( - "username" => $_SERVER["WHMCS_USERNAME"], - "password" => $_SERVER["WHMCS_PASSWORD"], - "url" => "https://account.sourcefabric.com/includes/api.php?accesskey=".$_SERVER["WHMCS_ACCESS_KEY"], - ); - } - private static function viewInvoice($invoice_id) { $whmcsurl = "https://account.sourcefabric.com/dologin.php"; $autoauthkey = $_SERVER["WHMCS_AUTOAUTH_KEY"]; $timestamp = time(); //whmcs timezone? - $client = self::getClientDetails(); + $client = Billing::getClientDetails(); $email = $client["email"]; $hash = sha1($email.$timestamp.$autoauthkey); $goto = "viewinvoice.php?id=".$invoice_id; @@ -347,12 +213,12 @@ class BillingController extends Zend_Controller_Action { $request = $this->getRequest(); $form = new Application_Form_BillingClient(); - BillingController::ensureClientIdIsValid(); + Billing::ensureClientIdIsValid(); if ($request->isPost()) { $formData = $request->getPost(); if ($form->isValid($formData)) { - $credentials = self::getAPICredentials(); + $credentials = Billing::getAPICredentials(); $postfields = array(); $postfields["username"] = $credentials["username"]; @@ -371,7 +237,7 @@ class BillingController extends Zend_Controller_Action { $query_string = ""; foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - $result = $this->makeRequest($credentials["url"], $query_string); + $result = Billing::makeRequest($credentials["url"], $query_string); if ($result["result"] == "error") { $this->setErrorMessage(); @@ -394,9 +260,9 @@ class BillingController extends Zend_Controller_Action { $CC_CONFIG = Config::getConfig(); $baseUrl = Application_Common_OsPath::getBaseDir(); $this->view->headLink()->appendStylesheet($baseUrl.'css/billing.css?'.$CC_CONFIG['airtime_version']); - - BillingController::ensureClientIdIsValid(); - $credentials = self::getAPICredentials(); + + Billing::ensureClientIdIsValid(); + $credentials = Billing::getAPICredentials(); $postfields = array(); $postfields["username"] = $credentials["username"]; @@ -408,7 +274,7 @@ class BillingController extends Zend_Controller_Action { $query_string = ""; foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - $result = self::makeRequest($credentials["url"], $query_string); + $result = Billing::makeRequest($credentials["url"], $query_string); if ($result["invoices"]) { $this->view->invoices = $result["invoices"]["invoice"];; @@ -419,184 +285,11 @@ class BillingController extends Zend_Controller_Action { public function invoiceAction() { - BillingController::ensureClientIdIsValid(); + Billing::ensureClientIdIsValid(); $request = $this->getRequest(); $invoice_id = $request->getParam('invoiceid'); self::viewInvoice($invoice_id); } - /** Get the Airtime instance ID of the instance the customer is currently viewing. */ - private static function getClientInstanceId() - { - $currentProduct = BillingController::getClientCurrentAirtimeProduct(); - return $currentProduct["id"]; - } - public static function getProducts() - { - //Making this static to cache the products during a single HTTP request. - //This saves us roundtrips to WHMCS if getProducts() is called multiple times. - static $result = array(); - if (!empty($result)) - { - return $result["products"]["product"]; - } - - $credentials = self::getAPICredentials(); - - $postfields = array(); - $postfields["username"] = $credentials["username"]; - $postfields["password"] = md5($credentials["password"]); - $postfields["action"] = "getproducts"; - $postfields["responsetype"] = "json"; - //gid is the Airtime product group id on whmcs - $postfields["gid"] = "15"; - - $query_string = ""; - foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - - $result = self::makeRequest($credentials["url"], $query_string); - //Logging::info($result["products"]["product"]); - $products = $result["products"]["product"]; - - //Blacklist all free plans - foreach ($products as $k => $p) { - Logging::info($p); - if ($p["paytype"] === "free") - { - unset($products[$k]); - } - } - - return $products; - } - - public static function getProductPricesAndTypes() - { - $products = BillingController::getProducts(); - - foreach ($products as $k => $p) { - $productPrices[$p["name"]] = array( - "monthly" => $p["pricing"]["USD"]["monthly"], - "annually" => $p["pricing"]["USD"]["annually"] - ); - $productTypes[$p["pid"]] = $p["name"] . " ($" . $productPrices[$p['name']]['monthly'] . "/mo)"; - } - return array($productPrices, $productTypes); - } - - /** Get the plan (or product in WHMCS lingo) that the customer is currently on. - * @return An associative array containing the fields for the product - * */ - public static function getClientCurrentAirtimeProduct() - { - static $airtimeProduct = null; - //Ghetto caching to avoid multiple round trips to WHMCS - if ($airtimeProduct) { - return $airtimeProduct; - } - $credentials = self::getAPICredentials(); - - $postfields = array(); - $postfields["username"] = $credentials["username"]; - $postfields["password"] = md5($credentials["password"]); - $postfields["action"] = "getclientsproducts"; - $postfields["responsetype"] = "json"; - $postfields["clientid"] = Application_Model_Preference::GetClientId(); - - $query_string = ""; - foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - - $result = self::makeRequest($credentials["url"], $query_string); - - //XXX: Debugging / local testing - if ($_SERVER['SERVER_NAME'] == "airtime.localhost") { - $_SERVER['SERVER_NAME'] = "bananas.airtime.pro"; - } - - //This code must run on airtime.pro for it to work... it's trying to match - //the server's hostname with the client subdomain. Once it finds a match - //between the product and the server's hostname/subdomain, then it - //returns the ID of that product (aka. the service ID of an Airtime instance) - foreach ($result["products"]["product"] as $product) - { - if (strpos($product["groupname"], "Airtime") === FALSE) - { - //Ignore non-Airtime products - continue; - } - else - { - if ($product["status"] === "Active") { - $airtimeProduct = $product; - $subdomain = ''; - - foreach ($airtimeProduct['customfields']['customfield'] as $customField) { - if ($customField['name'] === SUBDOMAIN_WHMCS_CUSTOM_FIELD_NAME) { - $subdomain = $customField['value']; - if (($subdomain . ".airtime.pro") === $_SERVER['SERVER_NAME']) { - return $airtimeProduct; - } - } - } - } - } - } - throw new Exception("Unable to match subdomain to a service ID"); - } - - public static function getClientDetails() - { - try { - $credentials = self::getAPICredentials(); - - $postfields = array(); - $postfields["username"] = $credentials["username"]; - $postfields["password"] = md5($credentials["password"]); - $postfields["action"] = "getclientsdetails"; - $postfields["stats"] = true; - $postfields["clientid"] = Application_Model_Preference::GetClientId(); - $postfields["responsetype"] = "json"; - - $query_string = ""; - foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - - $arr = self::makeRequest($credentials["url"], $query_string); - return $arr["client"]; - } catch (Exception $e) { - Logging::info($e->getMessage()); - } - } - - private static function makeRequest($url, $query_string) { - try { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6 - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_TIMEOUT, 5); //Aggressive 5 second timeout - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - $jsondata = curl_exec($ch); - if (curl_error($ch)) { - //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch)); - throw new Exception("WHMCS server down or invalid request."); - } - curl_close($ch); - - return json_decode($jsondata, true); - } catch (Exception $e) { - Logging::info($e->getMessage()); - } - } - - private static function ensureClientIdIsValid() - { - if (Application_Model_Preference::GetClientId() == null) - { - throw new Exception("Invalid client ID: " . Application_Model_Preference::GetClientId()); - } - } } diff --git a/airtime_mvc/application/forms/BillingClient.php b/airtime_mvc/application/forms/BillingClient.php index 5e6a2c252..891cd4a95 100644 --- a/airtime_mvc/application/forms/BillingClient.php +++ b/airtime_mvc/application/forms/BillingClient.php @@ -7,7 +7,7 @@ class Application_Form_BillingClient extends Zend_Form { /*$this->setDecorators(array( array('ViewScript', array('viewScript' => 'form/billing-purchase.phtml'))));*/ - $client = BillingController::getClientDetails(); + $client = Billing::getClientDetails(); $this->setAttrib("id", "clientdetails_form"); $notEmptyValidator = Application_Form_Helper_ValidationTypes::overrideNotEmptyValidator(); diff --git a/airtime_mvc/application/forms/BillingUpgradeDowngrade.php b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php index e81725ef8..6726c5da8 100644 --- a/airtime_mvc/application/forms/BillingUpgradeDowngrade.php +++ b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php @@ -5,9 +5,9 @@ class Application_Form_BillingUpgradeDowngrade extends Zend_Form { $productPrices = array(); $productTypes = array(); - list($productPrices, $productTypes) = BillingController::getProductPricesAndTypes(); + list($productPrices, $productTypes) = Billing::getProductPricesAndTypes(); - $currentPlanProduct = BillingController::getClientCurrentAirtimeProduct(); + $currentPlanProduct = Billing::getClientCurrentAirtimeProduct(); $currentPlanProductId = $currentPlanProduct["pid"]; $currentPlanProductBillingCycle = $currentPlanProduct["billingcycle"]; diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php index b461d7099..8d22aabc5 100644 --- a/airtime_mvc/application/models/RabbitMq.php +++ b/airtime_mvc/application/models/RabbitMq.php @@ -1,6 +1,6 @@ getFilePrefix()); } private function getOwnerId() diff --git a/airtime_mvc/application/views/scripts/billing/upgrade.phtml b/airtime_mvc/application/views/scripts/billing/upgrade.phtml index 273e44e51..dfdab0707 100644 --- a/airtime_mvc/application/views/scripts/billing/upgrade.phtml +++ b/airtime_mvc/application/views/scripts/billing/upgrade.phtml @@ -4,7 +4,7 @@ ?>