Add support for org-based subscriptions
This commit is contained in:
parent
97a7cd23e7
commit
e356a10378
5 changed files with 109 additions and 35 deletions
|
@ -1076,19 +1076,19 @@ def subscription_view(stripe_subscription, used_repos):
|
||||||
|
|
||||||
@app.route('/api/user/plan', methods=['PUT'])
|
@app.route('/api/user/plan', methods=['PUT'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def subscribe():
|
def subscribe_api():
|
||||||
# Amount in cents
|
|
||||||
amount = 500
|
|
||||||
|
|
||||||
request_data = request.get_json()
|
request_data = request.get_json()
|
||||||
plan = request_data['plan']
|
plan = request_data['plan']
|
||||||
|
token = request_data['token'] if 'token' in request_data else None
|
||||||
user = current_user.db_user()
|
user = current_user.db_user()
|
||||||
|
return subscribe(user, plan, token)
|
||||||
|
|
||||||
|
def subscribe(user, plan, token = None):
|
||||||
private_repos = model.get_private_repo_count(user.username)
|
private_repos = model.get_private_repo_count(user.username)
|
||||||
|
|
||||||
if not user.stripe_id:
|
if not user.stripe_id:
|
||||||
# Create the customer and plan simultaneously
|
# Create the customer and plan simultaneously
|
||||||
card = request_data['token']
|
card = token
|
||||||
cus = stripe.Customer.create(email=user.email, plan=plan, card=card)
|
cus = stripe.Customer.create(email=user.email, plan=plan, card=card)
|
||||||
user.stripe_id = cus.id
|
user.stripe_id = cus.id
|
||||||
user.save()
|
user.save()
|
||||||
|
@ -1112,18 +1112,30 @@ def subscribe():
|
||||||
|
|
||||||
else:
|
else:
|
||||||
cus.plan = plan
|
cus.plan = plan
|
||||||
|
|
||||||
# User may have been a previous customer who is resubscribing
|
# User may have been a previous customer who is resubscribing
|
||||||
if 'token' in request_data:
|
if token:
|
||||||
cus.card = request_data['token']
|
cus.card = token
|
||||||
|
|
||||||
cus.save()
|
cus.save()
|
||||||
|
|
||||||
response_json = subscription_view(cus.subscription, private_repos)
|
response_json = subscription_view(cus.subscription, private_repos)
|
||||||
|
|
||||||
return jsonify(response_json)
|
return jsonify(response_json)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/organization/<orgname>/plan', methods=['PUT'])
|
||||||
|
@api_login_required
|
||||||
|
def subscribe_org_api(orgname):
|
||||||
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
|
if permission.can():
|
||||||
|
request_data = request.get_json()
|
||||||
|
plan = request_data['plan']
|
||||||
|
token = request_data['token'] if 'token' in request_data else None
|
||||||
|
organization = model.get_organization(orgname)
|
||||||
|
return subscribe(organization, plan, token)
|
||||||
|
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/user/plan', methods=['GET'])
|
@app.route('/api/user/plan', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def get_subscription():
|
def get_subscription():
|
||||||
|
@ -1140,3 +1152,24 @@ def get_subscription():
|
||||||
'plan': 'free',
|
'plan': 'free',
|
||||||
'usedPrivateRepos': private_repos,
|
'usedPrivateRepos': private_repos,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/organization/<orgname>/plan', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
def get_org_subscription(orgname):
|
||||||
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
|
if permission.can():
|
||||||
|
private_repos = model.get_private_repo_count(orgname)
|
||||||
|
organization = model.get_organization(orgname)
|
||||||
|
if organization.stripe_id:
|
||||||
|
cus = stripe.Customer.retrieve(organization.stripe_id)
|
||||||
|
|
||||||
|
if cus.subscription:
|
||||||
|
return jsonify(subscription_view(cus.subscription, private_repos))
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'plan': 'bus-free',
|
||||||
|
'usedPrivateRepos': private_repos,
|
||||||
|
})
|
||||||
|
|
||||||
|
abort(403)
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.organization-header-element {
|
.organization-header-element {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
@ -1242,10 +1246,6 @@ p.editable:hover i {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-admin .panel-plan .button-hidden {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-admin .plan-description {
|
.user-admin .plan-description {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
|
@ -31,6 +31,17 @@ function getFirstTextLine(commentString) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRestUrl(args) {
|
||||||
|
var url = '';
|
||||||
|
for (var i = 0; i < arguments.length; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
url += encodeURI(arguments[i])
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
function getMarkedDown(string) {
|
function getMarkedDown(string) {
|
||||||
return Markdown.getSanitizingConverter().makeHtml(string || '');
|
return Markdown.getSanitizingConverter().makeHtml(string || '');
|
||||||
}
|
}
|
||||||
|
@ -160,7 +171,7 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
planService.showSubscribeDialog = function($scope, planId, started, success, failed) {
|
planService.showSubscribeDialog = function($scope, planId, orgname, started, success, failed) {
|
||||||
var submitToken = function(token) {
|
var submitToken = function(token) {
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
started();
|
started();
|
||||||
|
@ -173,7 +184,8 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
||||||
plan: planId,
|
plan: planId,
|
||||||
};
|
};
|
||||||
|
|
||||||
var createSubscriptionRequest = Restangular.one('user/plan');
|
var url = orgname ? getRestUrl('organization', orgname, 'plan') : 'user/plan';
|
||||||
|
var createSubscriptionRequest = Restangular.one(url);
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
createSubscriptionRequest.customPUT(subscriptionDetails).then(success, failed);
|
createSubscriptionRequest.customPUT(subscriptionDetails).then(success, failed);
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,17 +14,6 @@ $.fn.clipboardCopy = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRestUrl(args) {
|
|
||||||
var url = '';
|
|
||||||
for (var i = 0; i < arguments.length; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
url += '/';
|
|
||||||
}
|
|
||||||
url += encodeURI(arguments[i])
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
||||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
$scope.user = currentUser;
|
$scope.user = currentUser;
|
||||||
|
@ -716,7 +705,7 @@ function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService,
|
||||||
|
|
||||||
$scope.planChanging = false;
|
$scope.planChanging = false;
|
||||||
$scope.subscribe = function(planId) {
|
$scope.subscribe = function(planId) {
|
||||||
PlanService.showSubscribeDialog($scope, planId, function() {
|
PlanService.showSubscribeDialog($scope, planId, null, function() {
|
||||||
// Subscribing.
|
// Subscribing.
|
||||||
$scope.planChanging = true;
|
$scope.planChanging = true;
|
||||||
}, function(plan) {
|
}, function(plan) {
|
||||||
|
@ -1035,7 +1024,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.upgradePlan = function() {
|
$scope.upgradePlan = function() {
|
||||||
PlanService.showSubscribeDialog($scope, $scope.planRequired.stripeId, function() {
|
PlanService.showSubscribeDialog($scope, $scope.planRequired.stripeId, null, function() {
|
||||||
// Subscribing.
|
// Subscribing.
|
||||||
$scope.planChanging = true;
|
$scope.planChanging = true;
|
||||||
}, function(plan) {
|
}, function(plan) {
|
||||||
|
@ -1203,6 +1192,7 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
||||||
};
|
};
|
||||||
|
|
||||||
var subscribedToPlan = function(sub) {
|
var subscribedToPlan = function(sub) {
|
||||||
|
$scope.planChanging = false;
|
||||||
$scope.subscription = sub;
|
$scope.subscription = sub;
|
||||||
PlanService.getPlan(sub.plan, function(subscribedPlan) {
|
PlanService.getPlan(sub.plan, function(subscribedPlan) {
|
||||||
$scope.subscribedPlan = subscribedPlan;
|
$scope.subscribedPlan = subscribedPlan;
|
||||||
|
@ -1223,9 +1213,11 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
||||||
|
|
||||||
var loadSubscription = function() {
|
var loadSubscription = function() {
|
||||||
$scope.planLoading = true;
|
$scope.planLoading = true;
|
||||||
UserService.getCurrentSubscription(subscribedToPlan, function() {
|
|
||||||
|
var getSubscription = Restangular.one(getRestUrl('organization', orgname, 'plan'));
|
||||||
|
getSubscription.get().then(subscribedToPlan, function() {
|
||||||
// Organization has no subscription.
|
// Organization has no subscription.
|
||||||
$scope.planLoading = false;
|
subscribedToPlan({'plan': 'bus-free'});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1235,6 +1227,38 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
||||||
return 'success';
|
return 'success';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.subscribe = function(planId) {
|
||||||
|
$scope.planChanging = true;
|
||||||
|
PlanService.showSubscribeDialog($scope, planId, orgname, function() {
|
||||||
|
// Subscribing.
|
||||||
|
}, function(plan) {
|
||||||
|
// Subscribed.
|
||||||
|
subscribedToPlan(plan);
|
||||||
|
}, function() {
|
||||||
|
// Failure.
|
||||||
|
$scope.planChanging = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changeSubscription = function(planId) {
|
||||||
|
$scope.planChanging = true;
|
||||||
|
$scope.errorMessage = undefined;
|
||||||
|
|
||||||
|
var subscriptionDetails = {
|
||||||
|
plan: planId,
|
||||||
|
};
|
||||||
|
|
||||||
|
var changeSubscriptionRequest = Restangular.one(getRestUrl('organization', orgname, 'plan'));
|
||||||
|
changeSubscriptionRequest.customPUT(subscriptionDetails).then(subscribedToPlan, function() {
|
||||||
|
// Failure
|
||||||
|
$scope.planChanging = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancelSubscription = function() {
|
||||||
|
$scope.changeSubscription('bus-free');
|
||||||
|
};
|
||||||
|
|
||||||
loadSubscription();
|
loadSubscription();
|
||||||
loadOrganization();
|
loadOrganization();
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,9 @@
|
||||||
upgrading your subscription to avoid future disruptions in your organization's service.
|
upgrading your subscription to avoid future disruptions in your organization's service.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-hover plans-table">
|
<i class="fa fa-spinner fa-spin fa-3x" ng-show="planChanging"></i>
|
||||||
|
|
||||||
|
<table class="table table-hover plans-table" ng-show="!planChanging">
|
||||||
<thead>
|
<thead>
|
||||||
<td>Plan</td>
|
<td>Plan</td>
|
||||||
<td>Private Repositories</td>
|
<td>Private Repositories</td>
|
||||||
|
@ -84,12 +86,15 @@
|
||||||
<td><div class="plan-price">${{ plan.price / 100 }}</div></td>
|
<td><div class="plan-price">${{ plan.price / 100 }}</div></td>
|
||||||
<td class="controls">
|
<td class="controls">
|
||||||
<div ng-switch='plan.stripeId'>
|
<div ng-switch='plan.stripeId'>
|
||||||
<div ng-switch-when='free'>
|
<div ng-switch-when='bus-free'>
|
||||||
<button class="btn button-hidden">Hidden!</button>
|
<button class="btn button-hidden">Hidden!</button>
|
||||||
</div>
|
</div>
|
||||||
<div ng-switch-default>
|
<div ng-switch-default>
|
||||||
<button class="btn btn-primary" ng-show="subscription.plan === 'free'" ng-click="subscribe(plan.stripeId)">Subscribe</button>
|
<button class="btn btn-primary" ng-show="subscription.plan === 'bus-free'" ng-click="subscribe(plan.stripeId)">Subscribe</button>
|
||||||
<button class="btn btn-default" ng-hide="subscription.plan === 'free' || subscription.plan === plan.stripeId" ng-click="changeSubscription(plan.stripeId)">Change</button>
|
<button class="btn btn-default" ng-hide="subscription.plan === 'bus-free' || subscription.plan === plan.stripeId"
|
||||||
|
ng-click="changeSubscription(plan.stripeId)">
|
||||||
|
Change
|
||||||
|
</button>
|
||||||
<button class="btn btn-danger" ng-show="subscription.plan === plan.stripeId" ng-click="cancelSubscription()">Cancel</button>
|
<button class="btn btn-danger" ng-show="subscription.plan === plan.stripeId" ng-click="cancelSubscription()">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue