Merge pull request #2945 from coreos-inc/joseph.schorr/QS-53/new-pricing
New Quay.io pricing
This commit is contained in:
commit
a829260091
5 changed files with 148 additions and 62 deletions
154
data/billing.py
154
data/billing.py
|
@ -6,7 +6,7 @@ from calendar import timegm
|
|||
from util.morecollections import AttrDict
|
||||
|
||||
PLANS = [
|
||||
# Deprecated Plans
|
||||
# Deprecated Plans (2013-2014)
|
||||
{
|
||||
'title': 'Micro',
|
||||
'price': 700,
|
||||
|
@ -16,7 +16,7 @@ PLANS = [
|
|||
'bus_features': False,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 14,
|
||||
'superseded_by': None,
|
||||
'superseded_by': 'personal-30',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ PLANS = [
|
|||
'bus_features': False,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 14,
|
||||
'superseded_by': None,
|
||||
'superseded_by': 'bus-micro-30',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
|
@ -104,7 +104,105 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
|
||||
# Active plans
|
||||
# Deprecated plans (2014-2017)
|
||||
{
|
||||
'title': 'Personal',
|
||||
'price': 1200,
|
||||
'privateRepos': 5,
|
||||
'stripeId': 'personal-30',
|
||||
'audience': 'Individuals',
|
||||
'bus_features': False,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'personal-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Skiff',
|
||||
'price': 2500,
|
||||
'privateRepos': 10,
|
||||
'stripeId': 'bus-micro-30',
|
||||
'audience': 'For startups',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-micro-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Yacht',
|
||||
'price': 5000,
|
||||
'privateRepos': 20,
|
||||
'stripeId': 'bus-small-30',
|
||||
'audience': 'For small businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-small-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Freighter',
|
||||
'price': 10000,
|
||||
'privateRepos': 50,
|
||||
'stripeId': 'bus-medium-30',
|
||||
'audience': 'For normal businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-medium-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Tanker',
|
||||
'price': 20000,
|
||||
'privateRepos': 125,
|
||||
'stripeId': 'bus-large-30',
|
||||
'audience': 'For large businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-large-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Carrier',
|
||||
'price': 35000,
|
||||
'privateRepos': 250,
|
||||
'stripeId': 'bus-xlarge-30',
|
||||
'audience': 'For extra large businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-xlarge-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Huge',
|
||||
'price': 65000,
|
||||
'privateRepos': 500,
|
||||
'stripeId': 'bus-500-30',
|
||||
'audience': 'For huge business',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-500-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Huuge',
|
||||
'price': 120000,
|
||||
'privateRepos': 1000,
|
||||
'stripeId': 'bus-1000-30',
|
||||
'audience': 'For the SaaS savvy enterprise',
|
||||
'bus_features': True,
|
||||
'deprecated': True,
|
||||
'free_trial_days': 30,
|
||||
'superseded_by': 'bus-1000-2018',
|
||||
'plans_page_hidden': False,
|
||||
},
|
||||
|
||||
# Active plans (as of Dec 2017)
|
||||
{
|
||||
'title': 'Open Source',
|
||||
'price': 0,
|
||||
|
@ -118,10 +216,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Personal',
|
||||
'price': 1200,
|
||||
'title': 'Developer',
|
||||
'price': 1500,
|
||||
'privateRepos': 5,
|
||||
'stripeId': 'personal-30',
|
||||
'stripeId': 'personal-2018',
|
||||
'audience': 'Individuals',
|
||||
'bus_features': False,
|
||||
'deprecated': False,
|
||||
|
@ -130,10 +228,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Skiff',
|
||||
'price': 2500,
|
||||
'title': 'Micro',
|
||||
'price': 3000,
|
||||
'privateRepos': 10,
|
||||
'stripeId': 'bus-micro-30',
|
||||
'stripeId': 'bus-micro-2018',
|
||||
'audience': 'For startups',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -142,10 +240,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Yacht',
|
||||
'price': 5000,
|
||||
'title': 'Small',
|
||||
'price': 6000,
|
||||
'privateRepos': 20,
|
||||
'stripeId': 'bus-small-30',
|
||||
'stripeId': 'bus-small-2018',
|
||||
'audience': 'For small businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -154,10 +252,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Freighter',
|
||||
'price': 10000,
|
||||
'title': 'Medium',
|
||||
'price': 12500,
|
||||
'privateRepos': 50,
|
||||
'stripeId': 'bus-medium-30',
|
||||
'stripeId': 'bus-medium-2018',
|
||||
'audience': 'For normal businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -166,10 +264,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Tanker',
|
||||
'price': 20000,
|
||||
'title': 'Large',
|
||||
'price': 25000,
|
||||
'privateRepos': 125,
|
||||
'stripeId': 'bus-large-30',
|
||||
'stripeId': 'bus-large-2018',
|
||||
'audience': 'For large businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -178,10 +276,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Carrier',
|
||||
'price': 35000,
|
||||
'title': 'Extra Large',
|
||||
'price': 45000,
|
||||
'privateRepos': 250,
|
||||
'stripeId': 'bus-xlarge-30',
|
||||
'stripeId': 'bus-xlarge-2018',
|
||||
'audience': 'For extra large businesses',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -190,10 +288,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Huge',
|
||||
'price': 65000,
|
||||
'title': 'XXL',
|
||||
'price': 85000,
|
||||
'privateRepos': 500,
|
||||
'stripeId': 'bus-500-30',
|
||||
'stripeId': 'bus-500-2018',
|
||||
'audience': 'For huge business',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
@ -202,10 +300,10 @@ PLANS = [
|
|||
'plans_page_hidden': False,
|
||||
},
|
||||
{
|
||||
'title': 'Huuge',
|
||||
'price': 120000,
|
||||
'title': 'XXXL',
|
||||
'price': 160000,
|
||||
'privateRepos': 1000,
|
||||
'stripeId': 'bus-1000-30',
|
||||
'stripeId': 'bus-1000-2018',
|
||||
'audience': 'For the SaaS savvy enterprise',
|
||||
'bus_features': True,
|
||||
'deprecated': False,
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
</thead>
|
||||
|
||||
<tr ng-repeat="plan in plans" ng-show="isPlanVisible(plan, subscribedPlan)"
|
||||
ng-class="{'active':isPlanActive(plan, subscribedPlan), 'deprecated-plan':plan.deprecated}">
|
||||
ng-class="{'active':isPlanActive(plan, subscribedPlan)}">
|
||||
<td>
|
||||
{{ plan.title }}
|
||||
<div class="deprecated-plan-label" ng-show="plan.deprecated">
|
||||
|
|
|
@ -170,19 +170,19 @@
|
|||
<!-- Plan columns -->
|
||||
<div class="row-container">
|
||||
<div class="row">
|
||||
<!-- Personal Plan -->
|
||||
<!-- Developer Plan -->
|
||||
<div class="col-md-3 col-sm-6 plan-col">
|
||||
<div class="plan-box">
|
||||
<div class="plan-header">
|
||||
<span class="plan-box-price">$12/mo</span>
|
||||
<b>Personal</b>
|
||||
<span class="plan-box-price">$15/mo</span>
|
||||
<b>Developer</b>
|
||||
</div>
|
||||
<ul>
|
||||
<li>5 private repositories</li>
|
||||
<li>Unlimited public repos</li>
|
||||
</ul>
|
||||
<div class="plan-description">Great for individuals</div>
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('personal-30')">
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('personal-2018')">
|
||||
Start Free Trial
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
|
@ -193,12 +193,12 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Skiff Plan -->
|
||||
<!-- Micro Plan -->
|
||||
<div class="col-md-3 col-sm-6 plan-col">
|
||||
<div class="plan-box">
|
||||
<div class="plan-header">
|
||||
<span class="plan-box-price">$25/mo</span>
|
||||
<b>Skiff</b>
|
||||
<span class="plan-box-price">$30/mo</span>
|
||||
<b>Micro</b>
|
||||
</div>
|
||||
<ul>
|
||||
<li>10 private repositories</li>
|
||||
|
@ -206,7 +206,7 @@
|
|||
<li>Team-based permissions</li>
|
||||
</ul>
|
||||
<div class="plan-description">Great for startups</div>
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('bus-micro-30')">
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('bus-micro-2018')">
|
||||
Start Free Trial
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
|
@ -217,12 +217,12 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Yacht Plan -->
|
||||
<!-- Small Plan -->
|
||||
<div class="col-md-3 col-sm-6 plan-col popular">
|
||||
<div class="plan-box">
|
||||
<div class="plan-header">
|
||||
<span class="plan-box-price">$50/mo</span>
|
||||
<b>Yacht</b>
|
||||
<span class="plan-box-price">$60/mo</span>
|
||||
<b>Small</b>
|
||||
</div>
|
||||
<ul>
|
||||
<li>20 private repositories</li>
|
||||
|
@ -230,7 +230,7 @@
|
|||
<li>Team-based permissions</li>
|
||||
</ul>
|
||||
<div class="plan-description">Great for small businesses</div>
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('bus-small-30')">
|
||||
<a class="btn btn-primary trial-button" ng-click="buyNow('bus-small-2018')">
|
||||
Start Free Trial
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
|
|
|
@ -29,14 +29,8 @@ angular.module('quay').directive('planManager', function () {
|
|||
return false;
|
||||
}
|
||||
|
||||
// A plan is visible if it is not deprecated, or if it is both not superseded
|
||||
// by another plan, and also the active plan for the user.
|
||||
// A plan is visible if it is not deprecated, or if it is the namespace's current plan.
|
||||
if (plan['deprecated']) {
|
||||
var superseded = PlanService.getPlanImmediately(plan['superseded_by']);
|
||||
if (superseded) {
|
||||
return !$scope.isPlanVisible(superseded, subscribedPlan);
|
||||
}
|
||||
|
||||
return subscribedPlan && plan.stripeId === subscribedPlan.stripeId;
|
||||
}
|
||||
|
||||
|
@ -44,16 +38,10 @@ angular.module('quay').directive('planManager', function () {
|
|||
};
|
||||
|
||||
$scope.isPlanActive = function(plan, subscribedPlan) {
|
||||
// A plan is active if it is either the plan directly subscribed to by the user
|
||||
// or the plan which supersedes the plan to which the user is subscribed.
|
||||
if (!subscribedPlan) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subscribedPlan['deprecated'] && subscribedPlan['superseded_by']) {
|
||||
return $scope.isPlanActive(plan, PlanService.getPlanImmediately(subscribedPlan['superseded_by']));
|
||||
}
|
||||
|
||||
|
||||
return plan.stripeId === subscribedPlan.stripeId;
|
||||
};
|
||||
|
||||
|
|
|
@ -440,14 +440,14 @@ class TestGetUserPrivateAllowed(ApiTestCase):
|
|||
self.login(ADMIN_ACCESS_USER)
|
||||
|
||||
# Change the subscription of the namespace.
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='personal-30'))
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='personal-2018'))
|
||||
|
||||
json = self.getJsonResponse(PrivateRepositories)
|
||||
assert json['privateCount'] >= 6
|
||||
assert not json['privateAllowed']
|
||||
|
||||
# Change the subscription of the namespace.
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='bus-large-30'))
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='bus-large-2018'))
|
||||
|
||||
json = self.getJsonResponse(PrivateRepositories)
|
||||
assert json['privateAllowed']
|
||||
|
@ -2019,7 +2019,7 @@ class TestChangeRepoVisibility(ApiTestCase):
|
|||
self.assertEquals(True, json['is_public'])
|
||||
|
||||
# Change the subscription of the namespace.
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='personal-30'))
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='personal-2018'))
|
||||
|
||||
# Try to make private.
|
||||
self.postJsonResponse(RepositoryVisibility, params=dict(repository=self.SIMPLE_REPO),
|
||||
|
@ -3166,11 +3166,11 @@ class TestUserSubscription(ApiTestCase):
|
|||
self.assertEquals('free', sub['plan'])
|
||||
|
||||
# Change the plan.
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='bus-large-30'))
|
||||
self.putJsonResponse(UserPlan, data=dict(plan='bus-large-2018'))
|
||||
|
||||
# Verify
|
||||
sub = self.getSubscription()
|
||||
self.assertEquals('bus-large-30', sub['plan'])
|
||||
self.assertEquals('bus-large-2018', sub['plan'])
|
||||
|
||||
|
||||
class TestOrgSubscription(ApiTestCase):
|
||||
|
@ -3190,11 +3190,11 @@ class TestOrgSubscription(ApiTestCase):
|
|||
|
||||
# Change the plan.
|
||||
self.putJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION),
|
||||
data=dict(plan='bus-large-30'))
|
||||
data=dict(plan='bus-large-2018'))
|
||||
|
||||
# Verify
|
||||
sub = self.getSubscription()
|
||||
self.assertEquals('bus-large-30', sub['plan'])
|
||||
self.assertEquals('bus-large-2018', sub['plan'])
|
||||
|
||||
|
||||
class TestUserRobots(ApiTestCase):
|
||||
|
|
Reference in a new issue