ec6bee35b6
If a plan has a direct corrolary, show that one as the selected plan instead of showing the plan as deprecated even though it has the same details
378 lines
No EOL
11 KiB
JavaScript
378 lines
No EOL
11 KiB
JavaScript
/**
|
|
* Helper service for loading, changing and working with subscription plans.
|
|
*/
|
|
angular.module('quay')
|
|
.factory('PlanService', ['KeyService', 'UserService', 'CookieService', 'ApiService', 'Features', 'Config',
|
|
|
|
function(KeyService, UserService, CookieService, ApiService, Features, Config) {
|
|
var plans = null;
|
|
var planDict = {};
|
|
var planService = {};
|
|
var listeners = [];
|
|
|
|
var previousSubscribeFailure = false;
|
|
|
|
planService.getFreePlan = function() {
|
|
return 'free';
|
|
};
|
|
|
|
planService.registerListener = function(obj, callback) {
|
|
listeners.push({'obj': obj, 'callback': callback});
|
|
};
|
|
|
|
planService.unregisterListener = function(obj) {
|
|
for (var i = 0; i < listeners.length; ++i) {
|
|
if (listeners[i].obj == obj) {
|
|
listeners.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
planService.notePlan = function(planId) {
|
|
if (Features.BILLING) {
|
|
CookieService.putSession('quay.notedplan', planId);
|
|
}
|
|
};
|
|
|
|
planService.isOrgCompatible = function(plan) {
|
|
return plan['stripeId'] == planService.getFreePlan() || plan['bus_features'];
|
|
};
|
|
|
|
planService.getMatchingBusinessPlan = function(callback) {
|
|
planService.getPlans(function() {
|
|
planService.getSubscription(null, function(sub) {
|
|
var plan = planDict[sub.plan];
|
|
if (!plan) {
|
|
planService.getMinimumPlan(0, true, callback);
|
|
return;
|
|
}
|
|
|
|
var count = Math.max(sub.usedPrivateRepos, plan.privateRepos);
|
|
planService.getMinimumPlan(count, true, callback);
|
|
}, function() {
|
|
planService.getMinimumPlan(0, true, callback);
|
|
});
|
|
});
|
|
};
|
|
|
|
planService.handleNotedPlan = function() {
|
|
var planId = planService.getAndResetNotedPlan();
|
|
if (!planId || !Features.BILLING) { return false; }
|
|
|
|
UserService.load(function() {
|
|
if (UserService.currentUser().anonymous) {
|
|
return;
|
|
}
|
|
|
|
planService.getPlan(planId, function(plan) {
|
|
if (planService.isOrgCompatible(plan)) {
|
|
document.location = '/organizations/new/?plan=' + planId;
|
|
} else {
|
|
document.location = '/user?plan=' + planId;
|
|
}
|
|
});
|
|
});
|
|
|
|
return true;
|
|
};
|
|
|
|
planService.getAndResetNotedPlan = function() {
|
|
var planId = CookieService.get('quay.notedplan');
|
|
CookieService.clear('quay.notedplan');
|
|
return planId;
|
|
};
|
|
|
|
planService.handleCardError = function(resp) {
|
|
if (!planService.isCardError(resp)) { return; }
|
|
|
|
bootbox.dialog({
|
|
"message": resp.data.carderror,
|
|
"title": "Credit card issue",
|
|
"buttons": {
|
|
"close": {
|
|
"label": "Close",
|
|
"className": "btn-primary"
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
planService.isCardError = function(resp) {
|
|
return resp && resp.data && resp.data.carderror;
|
|
};
|
|
|
|
planService.verifyLoaded = function(callback) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
if (plans && plans.length) {
|
|
callback(plans);
|
|
return;
|
|
}
|
|
|
|
ApiService.listPlans().then(function(data) {
|
|
plans = data.plans || [];
|
|
for(var i = 0; i < plans.length; i++) {
|
|
planDict[plans[i].stripeId] = plans[i];
|
|
}
|
|
callback(plans);
|
|
}, function() { callback([]); });
|
|
};
|
|
|
|
planService.getPlans = function(callback, opt_includePersonal) {
|
|
planService.verifyLoaded(function(plans) {
|
|
var filtered = [];
|
|
for (var i = 0; i < plans.length; ++i) {
|
|
var plan = plans[i];
|
|
if (plan['deprecated']) { continue; }
|
|
if (!opt_includePersonal && !planService.isOrgCompatible(plan)) { continue; }
|
|
filtered.push(plan);
|
|
}
|
|
callback(filtered);
|
|
});
|
|
};
|
|
|
|
planService.getPlan = function(planId, callback) {
|
|
planService.getPlanIncludingDeprecated(planId, function(plan) {
|
|
if (!plan['deprecated']) {
|
|
callback(plan);
|
|
}
|
|
});
|
|
};
|
|
|
|
planService.getPlanIncludingDeprecated = function(planId, callback) {
|
|
planService.verifyLoaded(function() {
|
|
if (planDict[planId]) {
|
|
callback(planDict[planId]);
|
|
}
|
|
});
|
|
};
|
|
|
|
planService.getPlanImmediately = function(planId) {
|
|
// Get the plan by name, without bothering to check if the plans are loaded.
|
|
// This method will return undefined if planId is undefined or null, or if
|
|
// the planDict has not yet been loaded.
|
|
return planDict[planId];
|
|
};
|
|
|
|
planService.getMinimumPlan = function(privateCount, isBusiness, callback) {
|
|
planService.getPlans(function(plans) {
|
|
for (var i = 0; i < plans.length; i++) {
|
|
var plan = plans[i];
|
|
if (plan.privateRepos >= privateCount) {
|
|
callback(plan);
|
|
return;
|
|
}
|
|
}
|
|
|
|
callback(null);
|
|
}, /* include personal */!isBusiness);
|
|
};
|
|
|
|
planService.getSubscription = function(orgname, success, failure) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
ApiService.getSubscription(orgname).then(success, failure);
|
|
};
|
|
|
|
planService.setSubscription = function(orgname, planId, success, failure, opt_token) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
var subscriptionDetails = {
|
|
plan: planId
|
|
};
|
|
|
|
if (opt_token) {
|
|
subscriptionDetails['token'] = opt_token.id;
|
|
}
|
|
|
|
ApiService.updateSubscription(orgname, subscriptionDetails).then(function(resp) {
|
|
success(resp);
|
|
planService.getPlan(planId, function(plan) {
|
|
for (var i = 0; i < listeners.length; ++i) {
|
|
listeners[i]['callback'](plan);
|
|
}
|
|
});
|
|
}, failure);
|
|
};
|
|
|
|
planService.getCardInfo = function(orgname, callback) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
ApiService.getCard(orgname).then(function(resp) {
|
|
callback(resp.card);
|
|
}, function() {
|
|
callback({'is_valid': false});
|
|
});
|
|
};
|
|
|
|
planService.changePlan = function($scope, orgname, planId, callbacks, opt_async) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
if (callbacks['started']) {
|
|
callbacks['started']();
|
|
}
|
|
|
|
planService.getSubscription(orgname, function(sub) {
|
|
planService.getPlanIncludingDeprecated(sub.plan, function(subscribedPlan) {
|
|
planService.changePlanInternal($scope, orgname, planId, callbacks, opt_async,
|
|
subscribedPlan.price > 0);
|
|
});
|
|
}, function() {
|
|
planService.changePlanInternal($scope, orgname, planId, callbacks, opt_async, false);
|
|
});
|
|
};
|
|
|
|
planService.changePlanInternal = function($scope, orgname, planId, callbacks, opt_async,
|
|
opt_reuseCard) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
planService.getPlan(planId, function(plan) {
|
|
if (orgname && !planService.isOrgCompatible(plan)) { return; }
|
|
|
|
planService.getCardInfo(orgname, function(cardInfo) {
|
|
if (plan.price > 0 && (previousSubscribeFailure || !cardInfo.last4 || !opt_reuseCard)) {
|
|
var title = cardInfo.last4 ? 'Subscribe' : 'Start Trial ({{amount}} plan)';
|
|
planService.showSubscribeDialog($scope, orgname, planId, callbacks, title, /* async */true);
|
|
return;
|
|
}
|
|
|
|
previousSubscribeFailure = false;
|
|
|
|
planService.setSubscription(orgname, planId, callbacks['success'], function(resp) {
|
|
previousSubscribeFailure = true;
|
|
planService.handleCardError(resp);
|
|
callbacks['failure'](resp);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
planService.changeCreditCard = function($scope, orgname, callbacks) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
if (callbacks['opening']) {
|
|
callbacks['opening']();
|
|
}
|
|
|
|
var submitted = false;
|
|
var submitToken = function(token) {
|
|
if (submitted) { return; }
|
|
submitted = true;
|
|
$scope.$apply(function() {
|
|
if (callbacks['started']) {
|
|
callbacks['started']();
|
|
}
|
|
|
|
var cardInfo = {
|
|
'token': token.id
|
|
};
|
|
|
|
ApiService.setCard(orgname, cardInfo).then(callbacks['success'], function(resp) {
|
|
planService.handleCardError(resp);
|
|
callbacks['failure'](resp);
|
|
});
|
|
});
|
|
};
|
|
|
|
var email = planService.getEmail(orgname);
|
|
StripeCheckout.open({
|
|
key: KeyService.stripePublishableKey,
|
|
address: false,
|
|
email: email,
|
|
currency: 'usd',
|
|
name: 'Update credit card',
|
|
description: 'Enter your credit card number',
|
|
panelLabel: 'Update',
|
|
token: submitToken,
|
|
image: 'static/img/quay-icon-stripe.png',
|
|
opened: function() { $scope.$apply(function() { callbacks['opened']() }); },
|
|
closed: function() { $scope.$apply(function() { callbacks['closed']() }); }
|
|
});
|
|
};
|
|
|
|
planService.getEmail = function(orgname) {
|
|
var email = null;
|
|
if (UserService.currentUser()) {
|
|
email = UserService.currentUser().email;
|
|
|
|
if (orgname) {
|
|
org = UserService.getOrganization(orgname);
|
|
if (org) {
|
|
emaiil = org.email;
|
|
}
|
|
}
|
|
}
|
|
return email;
|
|
};
|
|
|
|
planService.showSubscribeDialog = function($scope, orgname, planId, callbacks, opt_title, opt_async) {
|
|
if (!Features.BILLING) { return; }
|
|
|
|
// If the async parameter is true and this is a browser that does not allow async popup of the
|
|
// Stripe dialog (such as Mobile Safari or IE), show a bootbox to show the dialog instead.
|
|
var isIE = navigator.appName.indexOf("Internet Explorer") != -1;
|
|
var isMobileSafari = navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/);
|
|
|
|
if (opt_async && (isIE || isMobileSafari)) {
|
|
bootbox.dialog({
|
|
"message": "Please click 'Subscribe' to continue",
|
|
"buttons": {
|
|
"subscribe": {
|
|
"label": "Subscribe",
|
|
"className": "btn-primary",
|
|
"callback": function() {
|
|
planService.showSubscribeDialog($scope, orgname, planId, callbacks, opt_title, false);
|
|
}
|
|
},
|
|
"close": {
|
|
"label": "Cancel",
|
|
"className": "btn-default"
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (callbacks['opening']) {
|
|
callbacks['opening']();
|
|
}
|
|
|
|
var submitted = false;
|
|
var submitToken = function(token) {
|
|
if (submitted) { return; }
|
|
submitted = true;
|
|
|
|
if (Config.MIXPANEL_KEY) {
|
|
mixpanel.track('plan_subscribe');
|
|
}
|
|
|
|
$scope.$apply(function() {
|
|
if (callbacks['started']) {
|
|
callbacks['started']();
|
|
}
|
|
planService.setSubscription(orgname, planId, callbacks['success'], callbacks['failure'], token);
|
|
});
|
|
};
|
|
|
|
planService.getPlan(planId, function(planDetails) {
|
|
var email = planService.getEmail(orgname);
|
|
StripeCheckout.open({
|
|
key: KeyService.stripePublishableKey,
|
|
address: false,
|
|
email: email,
|
|
amount: planDetails.price,
|
|
currency: 'usd',
|
|
name: 'Quay.io ' + planDetails.title + ' Subscription',
|
|
description: 'Up to ' + planDetails.privateRepos + ' private repositories',
|
|
panelLabel: opt_title || 'Subscribe',
|
|
token: submitToken,
|
|
image: 'static/img/quay-icon-stripe.png',
|
|
opened: function() { $scope.$apply(function() { callbacks['opened']() }); },
|
|
closed: function() { $scope.$apply(function() { callbacks['closed']() }); }
|
|
});
|
|
});
|
|
};
|
|
|
|
return planService;
|
|
}]); |