Merge branch 'saas' into saas-dev

This commit is contained in:
Albert Santoni 2015-08-24 17:11:52 -04:00
commit 3592eceb22
11 changed files with 335 additions and 53 deletions

View File

@ -1,5 +1,8 @@
<?php
define("AIRTIME_PRO_FREE_TRIAL_PLAN_ID", 34);
define("WHMCS_AIRTIME_GROUP_ID", 15);
class Billing
{
public static function getAPICredentials()
@ -30,10 +33,10 @@ class Billing
{
//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))
static $products = array();
if (!empty($products))
{
return $result["products"]["product"];
return $products;
}
$credentials = self::getAPICredentials();
@ -44,7 +47,7 @@ class Billing
$postfields["action"] = "getproducts";
$postfields["responsetype"] = "json";
//gid is the Airtime product group id on whmcs
$postfields["gid"] = "15";
$postfields["gid"] = WHMCS_AIRTIME_GROUP_ID;
$query_string = "";
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
@ -54,9 +57,9 @@ 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) {
Logging::info($p);
if ($p["paytype"] === "free")
if ($p["paytype"] === "free" || strpos($p["name"], "Awesome August 2015") !== false)
{
unset($products[$k]);
}
@ -326,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;
}
}

View File

@ -13,7 +13,7 @@ class FileDataHelper {
"audio/x-aac" => "aac",
"audio/aac" => "aac",
"audio/aacp" => "aac",
"audio/mp4" => "mp4",
"audio/mp4" => "m4a",
"audio/x-flac" => "flac",
"audio/wav" => "wav",
"audio/x-wav" => "wav",
@ -66,4 +66,4 @@ class FileDataHelper {
}
}
}
}

View File

@ -25,8 +25,12 @@ class ProvisioningHelper
*/
public function createAction()
{
$apikey = $_SERVER['PHP_AUTH_USER'];
if (!isset($apikey) || $apikey != $this->apikey) {
$apikey = "";
if (isset($_SERVER['PHP_AUTH_USER']))
{
$apikey = $_SERVER['PHP_AUTH_USER'];
}
if ($apikey != $this->apikey) {
Logging::info("Invalid API Key: $apikey");
http_response_code(403);
echo "ERROR: Incorrect API key";

View File

@ -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;

View File

@ -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();

View File

@ -203,7 +203,7 @@ class Application_Model_Preference
if (strlen($title) > 0)
$title .= " - ";
return $title."Airtime";
return $title.PRODUCT_NAME;
}
public static function SetHeadTitle($title, $view=null)

View File

@ -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() {
<div class="ui-widget ui-widget-content block-shadow clearfix padded-strong billing-panel">
<H2><?=_("Account Plans")?></H2>
<H4><?=_("Upgrade today to get more listeners and storage space!")?></H4>
<div>
<a href="https://www.airtime.pro/pricing#promo-details" target="_blank">
<img width="400px" height="133px" class="promo-banner" /></a>
</div>
<div class="pricing-grid">
<table>
<tr>
<th>Hobbyist</th>
<th>Starter</th>
<th>Plus</th>
<th>Premium</th>
<th>Awesome Hobbyist</th>
<th>Awesome Starter</th>
<th>Awesome Plus</th>
<th>Awesome Premium</th>
</tr>
<tr>
<td>1 Stream
@ -198,24 +228,40 @@ $(document).ready(function() {
<td>64kbps, 128kbps, and 196kbps Stream Quality
</td>
</tr>
<tr>
<td>5 Listeners
<tr class="august-promo">
<td>
<span>5 Listeners</span><br>
<div>10 Listeners</div>
</td>
<td>40 Listeners per stream
<td>
<span>40 Listeners per stream</span><br>
<div>80 Listeners per stream</div>
</td>
<td>100 Listeners per stream
<td>
<span>100 Listeners per stream</span><br>
<div>200 Listeners per stream</div>
</td>
<td>500 Listeners per stream
<td>
<span>500 Listeners per stream</span><br>
<div>1000 Listeners per stream</div>
</td>
</tr>
<tr>
<td>2GB Storage
<tr class="august-promo">
<td>
<span>2GB Storage</span><br>
<div>4GB Storage</div>
</td>
<td>5GB Storage
<td>
<span>5GB Storage</span><br>
<div>10GB Storage</div>
</td>
<td>30GB Storage
<td>
<span>30GB Storage</span><br>
<div>60GB Storage</div>
</td>
<td>150GB Storage
<td>
<span>150GB Storage</span><br>
<div>300GB Storage</div>
</td>
</tr>
<tr>
@ -264,6 +310,8 @@ echo($currentProduct["name"]);
<h3>Choose a plan:</h3>
<form id="<?php echo $form->getId(); ?>" method="<?php echo $form->getMethod() ?>" action="<?php echo
$form->getAction()?>" enctype="<?php echo $form->getEncType();?>">
<?php echo $form->csrf ?>
<div id="plantype">
<?php echo $form->newproductid ?>
@ -274,6 +322,10 @@ echo($currentProduct["name"]);
<div id="billingcycle_disclaimer">
Save 15% on annual plans (Hobbyist plan excluded).
</div>
<div class="clearfix"></div>
<div id="promo-plan-eligible" style="display:none;">
Congratulations, you are eligible for an Awesome Promotional Plan!
</div>
<div class="clearfix"></div>
<div id="subtotal_box">
<b>Subtotal:</b><br>

View File

@ -56,8 +56,8 @@
border-spacing: 0px;
border-collapse: separate;
border: 1px solid #777;
width: 600px;
margin-left: -100px;
width: 680px;
margin-left: -140px;
/*background-color: #555;*/
table-layout: fixed;
margin-top: 20px;
@ -65,6 +65,19 @@
box-shadow: 0px 5px 5px rgba(0,0,0,0.5);
}
.pricing-grid .august-promo div {
display: inline-block;
background-color: #ff611f;
padding: 3px 5px;
border-radius: 5px;
margin-left: -5px;
color: #ffffff;
}
.pricing-grid .august-promo span {
text-decoration: line-through;
}
.pricing-grid td, .pricing-grid th
{
border-bottom: 1px solid #999;
@ -223,4 +236,18 @@
{
/*text-align: right;*/
width: 100%;
}
}
#promo-plan-eligible {
margin-top: 10px;
font-size: 13px;
background-color: #ff611f;
padding: 3px 5px;
border-radius: 5px;
color: #ffffff;
}
.promo-banner {
margin-top:10px;
background: url('images/august_aweomse_promo_banner.png') no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -164,7 +164,7 @@ function removeSuccessMsg() {
function getUsabilityHint() {
var pathname = window.location.pathname;
$.getJSON("/api/get-usability-hint", {"format": "json", "userPath": pathname}, function(json) {
$.getJSON(baseUrl + "api/get-usability-hint", {"format": "json", "userPath": pathname}, function(json) {
var $hint_div = $('.usability_hint');
var current_hint = $hint_div.html();
if (json === "") {

View File

@ -7,7 +7,7 @@ audio/mpeg3 mp3
audio/x-aac aac
audio/aac aac
audio/aacp aac
audio/mp4 mp4
audio/mp4 m4a
audio/x-flac flac
audio/wav wav
audio/x-wav wav