From f9b12952f8c2978e324b7f722b9f61b53a84d532 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Tue, 30 Jan 2018 12:03:59 -0800 Subject: [PATCH] stripe: Move error handling into stripe.py too. This completes the separation of our logic for managing Stripe customers from the view code for the billing page. As we add more features to our Customer model and to our Stripe integration, we might further separate those two things; but for now they're nearly synonymous and there's no problem in them being mixed together. --- zilencer/lib/stripe.py | 37 ++++++++++++++++++++++++++++++++++--- zilencer/views.py | 26 +++++++------------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/zilencer/lib/stripe.py b/zilencer/lib/stripe.py index 9c141d0084..039daba4da 100644 --- a/zilencer/lib/stripe.py +++ b/zilencer/lib/stripe.py @@ -1,12 +1,13 @@ - +from functools import wraps import logging import os +from typing import Any, Callable, TypeVar from django.conf import settings +from django.utils.translation import ugettext as _ import stripe -from stripe.error import CardError, RateLimitError, InvalidRequestError, \ - AuthenticationError, APIConnectionError, StripeError +from zerver.lib.exceptions import JsonableError from zerver.lib.logging_util import log_to_file from zerver.models import Realm, UserProfile from zilencer.models import Customer @@ -24,6 +25,35 @@ billing_logger = logging.getLogger('zilencer.stripe') log_to_file(billing_logger, BILLING_LOG_PATH) log_to_file(logging.getLogger('stripe'), BILLING_LOG_PATH) +CallableT = TypeVar('CallableT', bound=Callable[..., Any]) + +class StripeError(JsonableError): + pass + +def catch_stripe_errors(func: CallableT) -> CallableT: + @wraps(func) + def wrapped(*args: Any, **kwargs: Any) -> Any: + if STRIPE_PUBLISHABLE_KEY is None: + # Dev-only message; no translation needed. + raise StripeError( + "Missing Stripe config. In dev, add to zproject/dev-secrets.conf .") + try: + return func(*args, **kwargs) + except stripe.error.StripeError as e: + billing_logger.error("Stripe error: %d %s", + e.http_status, e.__class__.__name__) + if isinstance(e, stripe.error.CardError): + raise StripeError(e.json_body.get('error', {}).get('message')) + else: + raise StripeError( + _("Something went wrong. Please try again or email us at %s.") + % (settings.ZULIP_ADMINISTRATOR,)) + except Exception as e: + billing_logger.exception("Uncaught error in Stripe integration") + raise + return wrapped # type: ignore # https://github.com/python/mypy/issues/1927 + +@catch_stripe_errors def count_stripe_cards(realm: Realm) -> int: try: customer_obj = Customer.objects.get(realm=realm) @@ -32,6 +62,7 @@ def count_stripe_cards(realm: Realm) -> int: except Customer.DoesNotExist: return 0 +@catch_stripe_errors def save_stripe_token(user: UserProfile, token: str) -> int: """Returns total number of cards.""" # The card metadata doesn't show up in Dashboard but can be accessed diff --git a/zilencer/views.py b/zilencer/views.py index 2f95bb18b2..624e3eb6ba 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -19,7 +19,7 @@ from zerver.lib.validator import check_int from zerver.models import UserProfile, Realm from zerver.views.push_notifications import validate_token from zilencer.lib.stripe import STRIPE_PUBLISHABLE_KEY, count_stripe_cards, \ - save_stripe_token, CardError, StripeError, billing_logger + save_stripe_token, StripeError from zilencer.models import RemotePushDeviceToken, RemoteZulipServer def validate_entity(entity: Union[UserProfile, RemoteZulipServer]) -> None: @@ -112,16 +112,11 @@ def add_payment_method(request: HttpRequest) -> HttpResponse: "email": user.email, } # type: Dict[str, Any] - def render_error(message: str) -> HttpResponse: - ctx["error_message"] = message - return render(request, 'zilencer/billing.html', context=ctx) - if not user.is_realm_admin: - return render_error(_("You should be an administrator of the organization %s to view this page.") - % (user.realm.name,)) - if STRIPE_PUBLISHABLE_KEY is None: - # Dev-only message; no translation needed. - return render_error("Missing Stripe config. In dev, add to zproject/dev-secrets.conf .") + ctx["error_message"] = ( + _("You should be an administrator of the organization %s to view this page.") + % (user.realm.name,)) + return render(request, 'zilencer/billing.html', context=ctx) try: if request.method == "GET": @@ -133,12 +128,5 @@ def add_payment_method(request: HttpRequest) -> HttpResponse: ctx["payment_method_added"] = True return render(request, 'zilencer/billing.html', context=ctx) except StripeError as e: - billing_logger.error("Stripe error: %d %s", e.http_status, e.__class__.__name__) - if isinstance(e, CardError): - return render_error(e.json_body.get('error', {}).get('message')) - else: - return render_error(_("Something went wrong. Please try again or email us at %s.") - % (settings.ZULIP_ADMINISTRATOR,)) - except Exception as e: - billing_logger.exception("Uncaught error in billing") - raise + ctx["error_message"] = e.msg + return render(request, 'zilencer/billing.html', context=ctx)