Handle error cases better for external services

This commit is contained in:
Joseph Schorr 2014-08-25 15:30:29 -04:00
parent 09a1c4d2b5
commit 99d75bede7
5 changed files with 54 additions and 20 deletions

View file

@ -4,7 +4,7 @@ from flask import request
from app import billing from app import billing
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, log_action, from endpoints.api import (resource, nickname, ApiResource, validate_json_request, log_action,
related_user_resource, internal_only, Unauthorized, NotFound, related_user_resource, internal_only, Unauthorized, NotFound,
require_user_admin, show_if, hide_if) require_user_admin, show_if, hide_if, abort)
from endpoints.api.subscribe import subscribe, subscription_view from endpoints.api.subscribe import subscribe, subscription_view
from auth.permissions import AdministerOrganizationPermission from auth.permissions import AdministerOrganizationPermission
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
@ -23,7 +23,11 @@ def get_card(user):
} }
if user.stripe_id: if user.stripe_id:
try:
cus = billing.Customer.retrieve(user.stripe_id) cus = billing.Customer.retrieve(user.stripe_id)
except stripe.APIConnectionError as e:
abort(503, message='Cannot contact Stripe')
if cus and cus.default_card: if cus and cus.default_card:
# Find the default card. # Find the default card.
default_card = None default_card = None
@ -46,7 +50,11 @@ def get_card(user):
def set_card(user, token): def set_card(user, token):
if user.stripe_id: if user.stripe_id:
try:
cus = billing.Customer.retrieve(user.stripe_id) cus = billing.Customer.retrieve(user.stripe_id)
except stripe.APIConnectionError as e:
abort(503, message='Cannot contact Stripe')
if cus: if cus:
try: try:
cus.card = token cus.card = token
@ -55,6 +63,8 @@ def set_card(user, token):
return carderror_response(exc) return carderror_response(exc)
except stripe.InvalidRequestError as exc: except stripe.InvalidRequestError as exc:
return carderror_response(exc) return carderror_response(exc)
except stripe.APIConnectionError as e:
return carderror_response(e)
return get_card(user) return get_card(user)
@ -75,7 +85,11 @@ def get_invoices(customer_id):
'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None 'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None
} }
try:
invoices = billing.Invoice.all(customer=customer_id, count=12) invoices = billing.Invoice.all(customer=customer_id, count=12)
except stripe.APIConnectionError as e:
abort(503, message='Cannot contact Stripe')
return { return {
'invoices': [invoice_view(i) for i in invoices.data] 'invoices': [invoice_view(i) for i in invoices.data]
} }
@ -228,7 +242,10 @@ class UserPlan(ApiResource):
private_repos = model.get_private_repo_count(user.username) private_repos = model.get_private_repo_count(user.username)
if user.stripe_id: if user.stripe_id:
try:
cus = billing.Customer.retrieve(user.stripe_id) cus = billing.Customer.retrieve(user.stripe_id)
except stripe.APIConnectionError as e:
abort(503, message='Cannot contact Stripe')
if cus.subscription: if cus.subscription:
return subscription_view(cus.subscription, private_repos) return subscription_view(cus.subscription, private_repos)
@ -291,7 +308,10 @@ class OrganizationPlan(ApiResource):
private_repos = model.get_private_repo_count(orgname) private_repos = model.get_private_repo_count(orgname)
organization = model.get_organization(orgname) organization = model.get_organization(orgname)
if organization.stripe_id: if organization.stripe_id:
try:
cus = billing.Customer.retrieve(organization.stripe_id) cus = billing.Customer.retrieve(organization.stripe_id)
except stripe.APIConnectionError as e:
abort(503, message='Cannot contact Stripe')
if cus.subscription: if cus.subscription:
return subscription_view(cus.subscription, private_repos) return subscription_view(cus.subscription, private_repos)

View file

@ -15,6 +15,9 @@ logger = logging.getLogger(__name__)
def carderror_response(exc): def carderror_response(exc):
return {'carderror': exc.message}, 402 return {'carderror': exc.message}, 402
def connection_response(exc):
return {'message': 'Could not contact Stripe. Please try again.'}, 503
def subscription_view(stripe_subscription, used_repos): def subscription_view(stripe_subscription, used_repos):
view = { view = {
@ -74,19 +77,29 @@ def subscribe(user, plan, token, require_business_plan):
log_action('account_change_plan', user.username, {'plan': plan}) log_action('account_change_plan', user.username, {'plan': plan})
except stripe.CardError as e: except stripe.CardError as e:
return carderror_response(e) return carderror_response(e)
except stripe.APIConnectionError as e:
return connection_response(e)
response_json = subscription_view(cus.subscription, private_repos) response_json = subscription_view(cus.subscription, private_repos)
status_code = 201 status_code = 201
else: else:
# Change the plan # Change the plan
try:
cus = billing.Customer.retrieve(user.stripe_id) cus = billing.Customer.retrieve(user.stripe_id)
except stripe.APIConnectionError as e:
return connection_response(e)
if plan_found['price'] == 0: if plan_found['price'] == 0:
if cus.subscription is not None: if cus.subscription is not None:
# We only have to cancel the subscription if they actually have one # We only have to cancel the subscription if they actually have one
try:
cus.cancel_subscription() cus.cancel_subscription()
cus.save() cus.save()
except stripe.APIConnectionError as e:
return connection_response(e)
check_repository_usage(user, plan_found) check_repository_usage(user, plan_found)
log_action('account_change_plan', user.username, {'plan': plan}) log_action('account_change_plan', user.username, {'plan': plan})
@ -101,6 +114,8 @@ def subscribe(user, plan, token, require_business_plan):
cus.save() cus.save()
except stripe.CardError as e: except stripe.CardError as e:
return carderror_response(e) return carderror_response(e)
except stripe.APIConnectionError as e:
return connection_response(e)
response_json = subscription_view(cus.subscription, private_repos) response_json = subscription_view(cus.subscription, private_repos)
check_repository_usage(user, plan_found) check_repository_usage(user, plan_found)

View file

@ -5570,8 +5570,8 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
} }
} }
if (!Features.BILLING && response.status == 402) { if (response.status == 503) {
$('#overlicenseModal').modal({}); $('#cannotContactService').modal({});
return false; return false;
} }

View file

@ -35,23 +35,18 @@
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
{% if not has_billing %}
<!-- Modal message dialog --> <!-- Modal message dialog -->
<div class="modal fade" id="overlicenseModal" data-backdrop="static"> <div class="modal fade" id="cannotContactService" data-backdrop="static">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title">Cannot create user</h4> <h4 class="modal-title">Cannot Contact External Service</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
A new user cannot be created as this organization has reached its licensed seat count. Please contact your administrator. A connection to an external service has failed. Please reload the page to try again.
</div>
<div class="modal-footer">
<a href="javascript:void(0)" class="btn btn-primary" data-dismiss="modal" onclick="location = '/signin'">Sign In</a>
</div> </div>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
{% endif %}
{% endblock %} {% endblock %}

View file

@ -30,7 +30,11 @@ class SendToMixpanel(Process):
while True: while True:
mp_request = self._mp_queue.get() mp_request = self._mp_queue.get()
logger.debug('Got queued mixpanel reqeust.') logger.debug('Got queued mixpanel reqeust.')
try:
self._consumer.send(*json.loads(mp_request)) self._consumer.send(*json.loads(mp_request))
except:
# Make sure we don't crash if Mixpanel request fails.
pass
class FakeMixpanel(object): class FakeMixpanel(object):