From b0ac7883e3f9c0dc3891a6b0fa3e7270f2b95e0a Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 19 Nov 2013 17:06:17 -0500 Subject: [PATCH] Fix so that credit card issues are displayed to the user --- endpoints/api.py | 44 +++++++++++++++------- static/directives/billing-options.html | 18 +++++++++ static/js/app.js | 51 +++++++++++++++++++++++--- static/js/controllers.js | 5 ++- templates/base.html | 1 + templates/index.html | 1 - 6 files changed, 99 insertions(+), 21 deletions(-) diff --git a/endpoints/api.py b/endpoints/api.py index b8ab44c77..5568f1465 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1283,7 +1283,7 @@ def subscription_view(stripe_subscription, used_repos): @api_login_required def get_user_card_api(): user = current_user.db_user() - return jsonify(get_card(user)) + return get_card(user) @app.route('/api/organization//card', methods=['GET']) @@ -1292,7 +1292,7 @@ def get_org_card_api(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): organization = model.get_organization(orgname) - return jsonify(get_card(organization)) + return get_card(organization) abort(403) @@ -1302,7 +1302,7 @@ def get_org_card_api(orgname): def set_user_card_api(): user = current_user.db_user() token = request.get_json()['token'] - return jsonify(set_card(user, token)) + return set_card(user, token) @app.route('/api/organization//card', methods=['POST']) @@ -1318,13 +1318,14 @@ def set_org_card_api(orgname): def set_card(user, token): - print token - if user.stripe_id: cus = stripe.Customer.retrieve(user.stripe_id) if cus: - cus.card = token - cus.save() + try: + cus.card = token + cus.save() + except stripe.CardError as e: + return carderror_response(e) return get_card(user) @@ -1351,7 +1352,7 @@ def get_card(user): 'last4': card.last4 } - return {'card': card_info} + return jsonify({'card': card_info}) @app.route('/api/user/plan', methods=['PUT']) @api_login_required @@ -1363,6 +1364,14 @@ def subscribe_api(): return subscribe(user, plan, token, USER_PLANS) +def carderror_response(e): + resp = jsonify({ + 'carderror': e.message, + }) + resp.status_code = 402 + return resp + + def subscribe(user, plan, token, accepted_plans): plan_found = None for plan_obj in accepted_plans: @@ -1387,9 +1396,13 @@ def subscribe(user, plan, token, accepted_plans): # They want a real paying plan, create the customer and plan # simultaneously card = token - cus = stripe.Customer.create(email=user.email, plan=plan, card=card) - user.stripe_id = cus.id - user.save() + + try: + cus = stripe.Customer.create(email=user.email, plan=plan, card=card) + user.stripe_id = cus.id + user.save() + except stripe.CardError as e: + return carderror_response(e) response_json = subscription_view(cus.subscription, private_repos) status_code = 201 @@ -1405,12 +1418,17 @@ def subscribe(user, plan, token, accepted_plans): cus.save() else: - cus.plan = plan # User may have been a previous customer who is resubscribing if token: cus.card = token - cus.save() + cus.plan = plan + + try: + cus.save() + except stripe.CardError as e: + return carderror_response(e) + response_json = subscription_view(cus.subscription, private_repos) resp = jsonify(response_json) diff --git a/static/directives/billing-options.html b/static/directives/billing-options.html index 1543e5239..598586bcb 100644 --- a/static/directives/billing-options.html +++ b/static/directives/billing-options.html @@ -36,4 +36,22 @@ + + + diff --git a/static/js/app.js b/static/js/app.js index 4a784f92c..381c54185 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -137,7 +137,26 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an } } }; - + + 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 (plans) { callback(plans); @@ -259,7 +278,10 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an return; } - planService.setSubscription(orgname, planId, callbacks['success'], callbacks['failure']); + planService.setSubscription(orgname, planId, callbacks['success'], function(resp) { + planService.handleCardError(resp); + callbacks['failure'](resp); + }); }); }); }; @@ -269,7 +291,10 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an callbacks['opening'](); } + var submitted = false; var submitToken = function(token) { + if (submitted) { return; } + submitted = true; $scope.$apply(function() { if (callbacks['started']) { callbacks['started'](); @@ -281,7 +306,10 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card'; var changeCardRequest = Restangular.one(url); - changeCardRequest.customPOST(cardInfo).then(callbacks['success'], callbacks['failure']); + changeCardRequest.customPOST(cardInfo).then(callbacks['success'], function(resp) { + planService.handleCardError(resp); + callbacks['failure'](resp); + }); }); }; @@ -321,7 +349,11 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an callbacks['opening'](); } + var submitted = false; var submitToken = function(token) { + if (submitted) { return; } + submitted = true; + mixpanel.track('plan_subscribe'); $scope.$apply(function() { @@ -823,6 +855,7 @@ quayApp.directive('billingOptions', function () { }); $scope.changeCard = function() { + var previousCard = $scope.currentCard; $scope.changingCard = true; var callbacks = { 'opened': function() { $scope.changingCard = true; }, @@ -832,9 +865,13 @@ quayApp.directive('billingOptions', function () { $scope.currentCard = resp.card; $scope.changingCard = false; }, - 'failure': function() { - $('#couldnotchangecardModal').modal({}); + 'failure': function(resp) { $scope.changingCard = false; + $scope.currentCard = previousCard; + + if (!PlanService.isCardError(resp)) { + $('#cannotchangecardModal').modal({}); + } } }; @@ -925,7 +962,9 @@ quayApp.directive('planManager', function () { 'opened': function() { $scope.planChanging = true; }, 'closed': function() { $scope.planChanging = false; }, 'success': subscribedToPlan, - 'failure': function() { $scope.planChanging = false; } + 'failure': function(resp) { + $scope.planChanging = false; + } }; PlanService.changePlan($scope, $scope.organization, planId, callbacks); diff --git a/static/js/controllers.js b/static/js/controllers.js index dbe3dcc54..1f3ec5b39 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1008,7 +1008,10 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula 'opened': function() { $scope.planChanging = true; }, 'closed': function() { $scope.planChanging = false; }, 'success': subscribedToPlan, - 'failure': function() { $('#couldnotsubscribeModal').modal(); $scope.planChanging = false; } + 'failure': function(resp) { + $('#couldnotsubscribeModal').modal(); + $scope.planChanging = false; + } }; PlanService.changePlan($scope, null, $scope.planRequired.stripeId, callbacks); diff --git a/templates/base.html b/templates/base.html index 5fd420571..1f4bbd8a0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -36,6 +36,7 @@ + diff --git a/templates/index.html b/templates/index.html index 3d09f1b6a..b9b322e0b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -31,7 +31,6 @@ {% block body_content %}
-