diff --git a/stripe/api_resources/__init__.py b/stripe/api_resources/__init__.py index 1a2168abb..21d381c6d 100644 --- a/stripe/api_resources/__init__.py +++ b/stripe/api_resources/__init__.py @@ -2,6 +2,7 @@ # flake8: noqa +from stripe.api_resources.error_object import ErrorObject, OAuthErrorObject from stripe.api_resources.list_object import ListObject from stripe.api_resources import checkout diff --git a/stripe/api_resources/error_object.py b/stripe/api_resources/error_object.py new file mode 100644 index 000000000..80aa87b6d --- /dev/null +++ b/stripe/api_resources/error_object.py @@ -0,0 +1,69 @@ +from __future__ import absolute_import, division, print_function + +from stripe.util import merge_dicts +from stripe.stripe_object import StripeObject + + +class ErrorObject(StripeObject): + def refresh_from( + self, + values, + api_key=None, + partial=False, + stripe_version=None, + stripe_account=None, + last_response=None, + ): + # Unlike most other API resources, the API will omit attributes in + # error objects when they have a null value. We manually set default + # values here to facilitate generic error handling. + values = merge_dicts( + { + "charge": None, + "code": None, + "decline_code": None, + "doc_url": None, + "message": None, + "param": None, + "payment_intent": None, + "payment_method": None, + "setup_intent": None, + "source": None, + "type": None, + }, + values, + ) + return super(ErrorObject, self).refresh_from( + values, + api_key, + partial, + stripe_version, + stripe_account, + last_response, + ) + + +class OAuthErrorObject(StripeObject): + def refresh_from( + self, + values, + api_key=None, + partial=False, + stripe_version=None, + stripe_account=None, + last_response=None, + ): + # Unlike most other API resources, the API will omit attributes in + # error objects when they have a null value. We manually set default + # values here to facilitate generic error handling. + values = merge_dicts( + {"error": None, "error_description": None}, values + ) + return super(OAuthErrorObject, self).refresh_from( + values, + api_key, + partial, + stripe_version, + stripe_account, + last_response, + ) diff --git a/stripe/error.py b/stripe/error.py index da157b4b6..8aad40cfd 100644 --- a/stripe/error.py +++ b/stripe/error.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, division, print_function +import stripe +from stripe.api_resources.error_object import ErrorObject from stripe.six import python_2_unicode_compatible @@ -32,6 +34,7 @@ def __init__( self.headers = headers or {} self.code = code self.request_id = self.headers.get("request-id", None) + self.error = self.construct_error_object() def __str__(self): msg = self._message or "" @@ -56,6 +59,14 @@ def __repr__(self): self.request_id, ) + def construct_error_object(self): + if self.json_body is None or "error" not in self.json_body: + return None + + return ErrorObject.construct_from( + self.json_body["error"], stripe.api_key + ) + class APIError(StripeError): pass diff --git a/stripe/oauth_error.py b/stripe/oauth_error.py index b2240b792..1d2588b27 100644 --- a/stripe/oauth_error.py +++ b/stripe/oauth_error.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, division, print_function +import stripe +from stripe.api_resources.error_object import OAuthErrorObject from stripe.error import StripeError @@ -17,6 +19,12 @@ def __init__( description, http_body, http_status, json_body, headers, code ) + def construct_error_object(self): + if self.json_body is None: + return None + + return OAuthErrorObject.construct_from(self.json_body, stripe.api_key) + class InvalidClientError(OAuthError): pass diff --git a/stripe/util.py b/stripe/util.py index 8d337fcdd..427000c0c 100644 --- a/stripe/util.py +++ b/stripe/util.py @@ -205,6 +205,12 @@ def populate_headers(idempotency_key): return None +def merge_dicts(x, y): + z = x.copy() + z.update(y) + return z + + class class_method_variant(object): def __init__(self, class_method_name): self.class_method_name = class_method_name diff --git a/tests/test_error.py b/tests/test_error.py index bd31a8aff..148ead925 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -52,6 +52,14 @@ def test_repr(self): "request_id='123')" ) + def test_error_object(self): + err = error.StripeError( + "message", json_body={"error": {"code": "some_error"}} + ) + assert err.error is not None + assert err.error.code == "some_error" + assert err.error.charge is None + class TestStripeErrorWithParamCode(object): def test_repr(self): diff --git a/tests/test_oauth_error.py b/tests/test_oauth_error.py new file mode 100644 index 000000000..bb251209d --- /dev/null +++ b/tests/test_oauth_error.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + +from stripe import oauth_error + + +class TestOAuthError(object): + def test_error_object(self): + err = oauth_error.OAuthError( + "message", "description", json_body={"error": "some_oauth_error"} + ) + assert err.error is not None + assert err.error.error == "some_oauth_error"