diff --git a/airtime_mvc/application/common/Billing.php b/airtime_mvc/application/common/Billing.php index 12ea8c6e2..0baa56104 100644 --- a/airtime_mvc/application/common/Billing.php +++ b/airtime_mvc/application/common/Billing.php @@ -1,5 +1,8 @@ $v) $query_string .= "$k=".urlencode($v)."&"; @@ -54,6 +57,7 @@ class Billing $products = $result["products"]["product"]; //Blacklist all free plans + //Hide the promo plans - we will tell the user if they are eligible for a promo plan foreach ($products as $k => $p) { if ($p["paytype"] === "free" || strpos($p["name"], "Awesome August 2015") !== false) { @@ -325,4 +329,150 @@ class Billing $result = Billing::makeRequest($credentials["url"], $query_string); } + /** + * Returns an array of the current Airtime Pro plan IDs. + * This excludes any old, free, promotional, or custom plans. + */ + public static function getCurrentPaidProductIds() + { + $products = self::getProducts(); + $productIds = array(); + foreach ($products as $k => $p) { + array_push($productIds, $p["pid"]); + } + + return $productIds; + } + + /** + * Returns an array of the Awesome August 2015 Promotional plans + */ + public static function getAwesomeAugustPromoProducts() + { + $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"] = WHMCS_AIRTIME_GROUP_ID; + + $query_string = ""; + foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; + + $result = self::makeRequest($credentials["url"], $query_string); + $promoProducts = $result["products"]["product"]; + + foreach ($promoProducts as $k => $p) { + if (strpos($p["name"], "Awesome August 2015") === false) { + unset($promoProducts[$k]); + } + } + + return $promoProducts; + } + + /** + * Returns the eligible promo plan ID that corresponds to the regular paid plan. + * + * i.e. if the client wants to upgrade to the Plus plan this function returns the + * plan id of the Awesome August 2015 Plus plan. + */ + public static function getEligibleAwesomeAugustPromoPlanId($productName) + { + $promoPlans = self::getAwesomeAugustPromoProducts(); + $promoPlanId = ""; + + foreach($promoPlans as $k => $p) { + if (strpos($p["name"], $productName) !== false) { + $promoPlanId = $p["pid"]; + break; + } + } + + return $promoPlanId; + } + + public static function getProductName($productId) + { + $products = self::getProducts(); + $productName = ""; + + foreach($products as $k => $p) { + if ($p["pid"] == $productId) { + $productName = $p["name"]; + break; + } + } + + return $productName; + } + + public static function isClientEligibleForPromo($newProductId, $newProductBillingCycle) + { + // use this to check if client is upgrading from an old plan + $currentPaidPlanProductIds = self::getCurrentPaidProductIds(); + + $currentPlanProduct = self::getClientCurrentAirtimeProduct(); + $currentPlanProductId = $currentPlanProduct["pid"]; + $currentPlanBillingCycle = strtolower($currentPlanProduct["billingcycle"]); + + if (self::isClientOnAwesomeAugustPromoPlan($currentPlanProductId)) { + + $newEligiblePromoId = self::getEligibleAwesomeAugustPromoPlanId( + self::getProductName($newProductId) + ); + + if ($newProductBillingCycle == "annually" || $newEligiblePromoId > $currentPlanProductId) { + return true; + } else { + return false; + } + } + + // if client is on trial plan, YES + if ($currentPlanProductId == AIRTIME_PRO_FREE_TRIAL_PLAN_ID) { + return true; + } + + // if client is currently on monthly or annually or old/free plan AND (upgrading OR upgrading/downgrading to annual plan), YES + if ($currentPlanBillingCycle == "monthly" || $currentPlanBillingCycle == "free account" + || $currentPlanBillingCycle == "annually") { + // is the client changing billing cycle to annual? + if ($newProductBillingCycle == "annually") { + return true; + } + + // Is the client staying on monthly and upgrading? + // This won't hold true if the client is on an old/free plan because the + // old/free plan ids are higher than the current paid plan ids. + if ($newProductBillingCycle == "monthly" && $newProductId > $currentPlanProductId) { + return true; + } + + // Is the client staying on monthly and upgrading from an old plan? + if ($newProductBillingCycle == "monthly" && !in_array($currentPlanProductId, $currentPaidPlanProductIds) + && in_array($newProductId, $currentPaidPlanProductIds)) { + return true; + } + } + + return false; + } + + public static function isClientOnAwesomeAugustPromoPlan($currentPlanId) + { + $promoPlans = self::getAwesomeAugustPromoProducts(); + + foreach ($promoPlans as $k => $p) { + if ($p["pid"] == $currentPlanId) { + return true; + } + } + + return false; + } + } diff --git a/airtime_mvc/application/controllers/BillingController.php b/airtime_mvc/application/controllers/BillingController.php index fda5c881d..92f499199 100644 --- a/airtime_mvc/application/controllers/BillingController.php +++ b/airtime_mvc/application/controllers/BillingController.php @@ -10,6 +10,7 @@ class BillingController extends Zend_Controller_Action { //Two of the actions in this controller return JSON because they're used for AJAX: $ajaxContext = $this->_helper->getHelper('AjaxContext'); $ajaxContext->addActionContext('vat-validator', 'json') + ->addActionContext('promo-eligibility-check', 'json') ->addActionContext('is-country-in-eu', 'json') ->initContext(); } @@ -19,6 +20,33 @@ class BillingController extends Zend_Controller_Action { $this->_redirect('billing/upgrade'); } + public function promoEligibilityCheckAction() + { + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $request = $this->getRequest(); + if (!$request->isPost()) { + throw new Exception("Must POST data to promoEligibilityCheckAction."); + } + $data = $request->getPost(); + + $current_namespace = new Zend_Session_Namespace('csrf_namespace'); + $observed_csrf_token = $this->_getParam('csrf_token'); + $expected_csrf_token = $current_namespace->authtoken; + + if($observed_csrf_token == $expected_csrf_token) { + $eligible = Billing::isClientEligibleForPromo( + $data["newproductid"], $data["newproductbillingcycle"]); + + //Set the return JSON value + $this->_helper->json(array("result"=>$eligible)); + } else { + $this->getResponse()->setHttpResponseCode(403); + $this->_helper->json(array("result"=>false, "error"=>"CSRF token did not match.")); + } + } + public function upgradeAction() { $CC_CONFIG = Config::getConfig(); @@ -28,21 +56,32 @@ class BillingController extends Zend_Controller_Action { $request = $this->getRequest(); $form = new Application_Form_BillingUpgradeDowngrade(); + if ($request->isPost()) { $formData = $request->getPost(); + if ($form->isValid($formData)) { + + // Check if client is eligible for promo and update the new product id if so + $eligibleForPromo = Billing::isClientEligibleForPromo( + $formData["newproductid"], $formData["newproductbillingcycle"]); + if ($eligibleForPromo) { + $newProductName = Billing::getProductName($formData["newproductid"]); + $formData["newproductid"] = Billing::getEligibleAwesomeAugustPromoPlanId($newProductName); + } + $credentials = Billing::getAPICredentials(); - + //Check if VAT should be applied or not to this invoice. if (in_array("7", $formData["customfields"])) { $apply_vat = Billing::checkIfVatShouldBeApplied($formData["customfields"]["7"], $formData["country"]); } else { $apply_vat = false; } - + $placeAnUpgradeOrder = true; - + $currentPlanProduct = Billing::getClientCurrentAirtimeProduct(); $currentPlanProductId = $currentPlanProduct["pid"]; $currentPlanProductBillingCycle = strtolower($currentPlanProduct["billingcycle"]); @@ -51,33 +90,33 @@ class BillingController extends Zend_Controller_Action { //and it freaks out and does the wrong thing if we do it via the API //so we have to do avoid that. if (($currentPlanProductId == $formData["newproductid"]) && - ($currentPlanProductBillingCycle == $formData["newproductbillingcycle"])) - { + ($currentPlanProductBillingCycle == $formData["newproductbillingcycle"]) + ) { $placeAnUpgradeOrder = false; } - + $postfields = array(); $postfields["username"] = $credentials["username"]; $postfields["password"] = md5($credentials["password"]); $postfields["action"] = "upgradeproduct"; $postfields["clientid"] = Application_Model_Preference::GetClientId(); - + $postfields["serviceid"] = Billing::getClientInstanceId(); $postfields["type"] = "product"; $postfields["newproductid"] = $formData["newproductid"]; $postfields["newproductbillingcycle"] = $formData["newproductbillingcycle"]; $postfields["paymentmethod"] = $formData["paymentmethod"]; $postfields["responsetype"] = "json"; - + $upgrade_query_string = ""; - foreach ($postfields AS $k=>$v) $upgrade_query_string .= "$k=".urlencode($v)."&"; - + foreach ($postfields AS $k => $v) $upgrade_query_string .= "$k=" . urlencode($v) . "&"; + //update client info $clientfields = array(); $clientfields["username"] = $credentials["username"]; $clientfields["password"] = md5($credentials["password"]); - $clientfields["action"] = "updateclient"; + $clientfields["action"] = "updateclient"; $clientfields["clientid"] = Application_Model_Preference::GetClientId(); $clientfields["customfields"] = base64_encode(serialize($formData["customfields"])); unset($formData["customfields"]); @@ -90,8 +129,8 @@ class BillingController extends Zend_Controller_Action { unset($clientfields["password2verify"]); unset($clientfields["submit"]); $client_query_string = ""; - foreach ($clientfields AS $k=>$v) $client_query_string .= "$k=".urlencode($v)."&"; - + foreach ($clientfields AS $k => $v) $client_query_string .= "$k=" . urlencode($v) . "&"; + //Update the client details in WHMCS first $result = Billing::makeRequest($credentials["url"], $client_query_string); Logging::info($result); @@ -100,33 +139,39 @@ class BillingController extends Zend_Controller_Action { $this->view->form = $form; return; } - + //If there were no changes to the plan or billing cycle, we just redirect you to the //invoices screen and show a message. - if (!$placeAnUpgradeOrder) - { + if (!$placeAnUpgradeOrder) { $this->_redirect('billing/invoices?planupdated'); return; } - + //Then place an upgrade order in WHMCS $result = Billing::makeRequest($credentials["url"], $upgrade_query_string); if ($result["result"] == "error") { - Logging::info($_SERVER['HTTP_HOST']." - Account upgrade failed. - ".$result["message"]); + Logging::info($_SERVER['HTTP_HOST'] . " - Account upgrade failed. - " . $result["message"]); $this->setErrorMessage(); $this->view->form = $form; } else { - Logging::info($_SERVER['HTTP_HOST']. "Account plan upgrade request:"); + Logging::info($_SERVER['HTTP_HOST'] . "Account plan upgrade request:"); Logging::info($result); - + // Disable the view and the layout here, squashes an error. $this->view->layout()->disableLayout(); $this->_helper->viewRenderer->setNoRender(true); - + if ($apply_vat) { Billing::addVatToInvoice($result["invoiceid"]); } - self::viewInvoice($result["invoiceid"]); + + // there may not be an invoice created if the client is downgrading + if (!empty($result["invoiceid"])) { + self::viewInvoice($result["invoiceid"]); + } else { + $this->_redirect('billing/invoices?planupdated'); + return; + } } } else { $this->view->form = $form; diff --git a/airtime_mvc/application/forms/BillingUpgradeDowngrade.php b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php index 6726c5da8..ecf9b4f50 100644 --- a/airtime_mvc/application/forms/BillingUpgradeDowngrade.php +++ b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php @@ -3,6 +3,11 @@ class Application_Form_BillingUpgradeDowngrade extends Zend_Form { public function init() { + $csrf_namespace = new Zend_Session_Namespace('csrf_namespace'); + $csrf_element = new Zend_Form_Element_Hidden('csrf'); + $csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label'); + $this->addElement($csrf_element); + $productPrices = array(); $productTypes = array(); list($productPrices, $productTypes) = Billing::getProductPricesAndTypes(); diff --git a/airtime_mvc/application/views/scripts/billing/upgrade.phtml b/airtime_mvc/application/views/scripts/billing/upgrade.phtml index dfdab0707..5d583f245 100644 --- a/airtime_mvc/application/views/scripts/billing/upgrade.phtml +++ b/airtime_mvc/application/views/scripts/billing/upgrade.phtml @@ -120,17 +120,43 @@ function configureByCountry(countryCode) }); } +function promoEligibilityCheck() +{ + var newproductid = $("input[type='radio'][name='newproductid']:checked").val(); + + // newproductid can be undefined if the client is currently on an old plan + // and they just change the billing cycle value without selecting a new plan type. + // In this case, let's not check if they are eligible for the promo because + // they won't be able to upgrade without selecting a new plan first. + if (newproductid === undefined) { + return; + } + var newproductbillingcycle = $("input[type='radio'][name='newproductbillingcycle']:checked").val(); + + $.post("/billing/promo-eligibility-check", {"newproductid": newproductid, + "newproductbillingcycle": newproductbillingcycle, "csrf_token": $("#csrf").attr('value')}) + .success(function(data) { + if (data.result == true) { + $("#promo-plan-eligible").show(); + } else if ($("#promo-plan-eligible").is(":visible")) { + $("#promo-plan-eligible").hide(); + } + }); +} + $(document).ready(function() { configureByCountry($("#country").val()); recalculateTotals(); - + $("input[name='newproductid']").change(function() { validatePlan(); recalculateTotals(); + promoEligibilityCheck(); }); $("input[name='newproductbillingcycle']").change(function() { recalculateTotals(); + promoEligibilityCheck(); }); $("#country").change(function() { @@ -170,13 +196,17 @@ $(document).ready(function() {