diff --git a/endpoints/api.py b/endpoints/api.py index e13de3e8e..7cb599429 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1076,19 +1076,19 @@ def subscription_view(stripe_subscription, used_repos): @app.route('/api/user/plan', methods=['PUT']) @api_login_required -def subscribe(): - # Amount in cents - amount = 500 - +def subscribe_api(): request_data = request.get_json() plan = request_data['plan'] - + token = request_data['token'] if 'token' in request_data else None 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) if not user.stripe_id: # Create the customer and plan simultaneously - card = request_data['token'] + card = token cus = stripe.Customer.create(email=user.email, plan=plan, card=card) user.stripe_id = cus.id user.save() @@ -1112,18 +1112,30 @@ def subscribe(): else: cus.plan = plan - # User may have been a previous customer who is resubscribing - if 'token' in request_data: - cus.card = request_data['token'] + if token: + cus.card = token cus.save() - response_json = subscription_view(cus.subscription, private_repos) 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']) @api_login_required def get_subscription(): @@ -1140,3 +1152,24 @@ def get_subscription(): 'plan': 'free', '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) diff --git a/static/css/quay.css b/static/css/quay.css index 172af7f87..0e100e7f4 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -2,6 +2,10 @@ font-family: 'Droid Sans', sans-serif; } +.button-hidden { + visibility: hidden; +} + .organization-header-element { padding: 20px; margin-bottom: 20px; @@ -1242,10 +1246,6 @@ p.editable:hover i { text-align: center; } -.user-admin .panel-plan .button-hidden { - visibility: hidden; -} - .user-admin .plan-description { font-size: 1.2em; margin-bottom: 10px; diff --git a/static/js/app.js b/static/js/app.js index b8083e016..ef73280ee 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -31,6 +31,17 @@ function getFirstTextLine(commentString) { 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) { 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) { $scope.$apply(function() { started(); @@ -173,7 +184,8 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', 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() { createSubscriptionRequest.customPUT(subscriptionDetails).then(success, failed); }); diff --git a/static/js/controllers.js b/static/js/controllers.js index 7942b9616..309252f24 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -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) { $scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) { $scope.user = currentUser; @@ -716,7 +705,7 @@ function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService, $scope.planChanging = false; $scope.subscribe = function(planId) { - PlanService.showSubscribeDialog($scope, planId, function() { + PlanService.showSubscribeDialog($scope, planId, null, function() { // Subscribing. $scope.planChanging = true; }, function(plan) { @@ -1035,7 +1024,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula }; $scope.upgradePlan = function() { - PlanService.showSubscribeDialog($scope, $scope.planRequired.stripeId, function() { + PlanService.showSubscribeDialog($scope, $scope.planRequired.stripeId, null, function() { // Subscribing. $scope.planChanging = true; }, function(plan) { @@ -1203,6 +1192,7 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService }; var subscribedToPlan = function(sub) { + $scope.planChanging = false; $scope.subscription = sub; PlanService.getPlan(sub.plan, function(subscribedPlan) { $scope.subscribedPlan = subscribedPlan; @@ -1223,9 +1213,11 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService var loadSubscription = function() { $scope.planLoading = true; - UserService.getCurrentSubscription(subscribedToPlan, function() { + + var getSubscription = Restangular.one(getRestUrl('organization', orgname, 'plan')); + getSubscription.get().then(subscribedToPlan, function() { // Organization has no subscription. - $scope.planLoading = false; + subscribedToPlan({'plan': 'bus-free'}); }); }; @@ -1235,6 +1227,38 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService 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(); loadOrganization(); } diff --git a/static/partials/org-admin.html b/static/partials/org-admin.html index 008583e79..d94ac4476 100644 --- a/static/partials/org-admin.html +++ b/static/partials/org-admin.html @@ -70,7 +70,9 @@ upgrading your subscription to avoid future disruptions in your organization's service. </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> <td>Plan</td> <td>Private Repositories</td> @@ -84,12 +86,15 @@ <td><div class="plan-price">${{ plan.price / 100 }}</div></td> <td class="controls"> <div ng-switch='plan.stripeId'> - <div ng-switch-when='free'> + <div ng-switch-when='bus-free'> <button class="btn button-hidden">Hidden!</button> </div> <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-default" ng-hide="subscription.plan === 'free' || subscription.plan === plan.stripeId" ng-click="changeSubscription(plan.stripeId)">Change</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 === '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> </div> </div>