diff --git a/CODEGEN_VERSION b/CODEGEN_VERSION index 163776b61..6824fd081 100644 --- a/CODEGEN_VERSION +++ b/CODEGEN_VERSION @@ -1 +1 @@ -eed9b64db4a551dcc837a073c124117f265c21d3 \ No newline at end of file +3723bdf073100308cd413efdb4aefbc912f9bf2f \ No newline at end of file diff --git a/stripe/_error_object.py b/stripe/_error_object.py index 9221218be..4cc563123 100644 --- a/stripe/_error_object.py +++ b/stripe/_error_object.py @@ -5,24 +5,85 @@ from stripe._api_mode import ApiMode if TYPE_CHECKING: + # errorImports: The beginning of the section generated from our OpenAPI spec from stripe._payment_intent import PaymentIntent + from stripe._payment_method import PaymentMethod from stripe._setup_intent import SetupIntent from stripe._source import Source - from stripe._payment_method import PaymentMethod + # errorImports: The end of the section generated from our OpenAPI spec class ErrorObject(StripeObject): + # errorAnnotations: The beginning of the section generated from our OpenAPI spec + advice_code: Optional[str] + """ + For card errors resulting from a card issuer decline, a short string indicating [how to proceed with an error](https://docs.stripe.com/declines#retrying-issuer-declines) if they provide one. + """ charge: Optional[str] + """ + For card errors, the ID of the failed charge. + """ code: Optional[str] + """ + For some errors that could be handled programmatically, a short string indicating the [error code](https://docs.stripe.com/error-codes) reported. + """ decline_code: Optional[str] + """ + For card errors resulting from a card issuer decline, a short string indicating the [card issuer's reason for the decline](https://docs.stripe.com/declines#issuer-declines) if they provide one. + """ doc_url: Optional[str] + """ + A URL to more information about the [error code](https://docs.stripe.com/error-codes) reported. + """ message: Optional[str] + """ + A human-readable message providing more details about the error. For card errors, these messages can be shown to your users. + """ + network_advice_code: Optional[str] + """ + For card errors resulting from a card issuer decline, a 2 digit code which indicates the advice given to merchant by the card network on how to proceed with an error. + """ + network_decline_code: Optional[str] + """ + For payments declined by the network, an alphanumeric code which indicates the reason the payment failed. + """ param: Optional[str] + """ + If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field. + """ payment_intent: Optional["PaymentIntent"] + """ + The PaymentIntent object for errors returned on a request involving a PaymentIntent. + """ payment_method: Optional["PaymentMethod"] + """ + The PaymentMethod object for errors returned on a request involving a PaymentMethod. + """ + payment_method_type: Optional[str] + """ + If the error is specific to the type of payment method, the payment method type that had a problem. This field is only populated for invoice-related errors. + """ + request_log_url: Optional[str] + """ + A URL to the request log entry in your dashboard. + """ setup_intent: Optional["SetupIntent"] + """ + The SetupIntent object for errors returned on a request involving a SetupIntent. + """ source: Optional["Source"] + """ + The PaymentSource object for errors returned on a request involving a PaymentSource. + """ type: str + """ + The type of error returned. One of `api_error`, `card_error`, `idempotency_error`, or `invalid_request_error` + """ + user_message: Optional[str] + """ + The user message associated with the error. + """ + # errorAnnotations: The end of the section generated from our OpenAPI spec def refresh_from( self, @@ -63,17 +124,25 @@ def _refresh_from( # values here to facilitate generic error handling. values = merge_dicts( { + # errorDefaults: The beginning of the section generated from our OpenAPI spec + "advice_code": None, "charge": None, "code": None, "decline_code": None, "doc_url": None, "message": None, + "network_advice_code": None, + "network_decline_code": None, "param": None, "payment_intent": None, "payment_method": None, + "payment_method_type": None, + "request_log_url": None, "setup_intent": None, "source": None, "type": None, + "user_message": None, + # errorDefaults: The end of the section generated from our OpenAPI spec }, values, ) diff --git a/stripe/_http_client.py b/stripe/_http_client.py index 3f69f5a91..65c3ed684 100644 --- a/stripe/_http_client.py +++ b/stripe/_http_client.py @@ -85,7 +85,6 @@ class _Proxy(TypedDict): MAX_DELAY = 5 INITIAL_DELAY = 0.5 - MAX_RETRY_AFTER = 60 _proxy: Optional[_Proxy] _verify_ssl_certs: bool @@ -162,23 +161,7 @@ def _should_retry( return False - def _retry_after_header( - self, response: Optional[Tuple[Any, Any, Mapping[str, str]]] = None - ): - if response is None: - return None - _, _, rheaders = response - - try: - return int(rheaders["retry-after"]) - except (KeyError, ValueError): - return None - - def _sleep_time_seconds( - self, - num_retries: int, - response: Optional[Tuple[Any, Any, Mapping[str, str]]] = None, - ) -> float: + def _sleep_time_seconds(self, num_retries: int) -> float: """ Apply exponential backoff with initial_network_retry_delay on the number of num_retries so far as inputs. Do not allow the number to exceed `max_network_retry_delay`. @@ -193,11 +176,6 @@ def _sleep_time_seconds( # But never sleep less than the base sleep seconds. sleep_seconds = max(HTTPClient.INITIAL_DELAY, sleep_seconds) - # And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask. - retry_after = self._retry_after_header(response) or 0 - if retry_after <= HTTPClient.MAX_RETRY_AFTER: - sleep_seconds = max(retry_after, sleep_seconds) - return sleep_seconds def _add_jitter_time(self, sleep_seconds: float) -> float: @@ -311,7 +289,7 @@ def _request_with_retries_internal( % connection_error.user_message ) num_retries += 1 - sleep_time = self._sleep_time_seconds(num_retries, response) + sleep_time = self._sleep_time_seconds(num_retries) _util.log_info( ( "Initiating retry %i for request %s %s after " @@ -469,7 +447,7 @@ async def _request_with_retries_internal_async( % connection_error.user_message ) num_retries += 1 - sleep_time = self._sleep_time_seconds(num_retries, response) + sleep_time = self._sleep_time_seconds(num_retries) _util.log_info( ( "Initiating retry %i for request %s %s after " diff --git a/tests/test_error.py b/tests/test_error.py index f8cfc404f..73de812ac 100644 --- a/tests/test_error.py +++ b/tests/test_error.py @@ -54,6 +54,38 @@ def test_error_object(self): assert err.error.code == "some_error" assert err.error.charge is None + def test_error_object_network_advice_code(self): + err = StripeError( + "message", + json_body={ + "error": { + "type": "card_error", + "network_advice_code": "02", + } + }, + ) + assert err.error is not None + assert err.error.network_advice_code == "02" + + def test_error_object_payment_intent(self): + err = StripeError( + "message", + json_body={ + "error": { + "type": "card_error", + "payment_intent": { + "id": "pi_123", + "object": "payment_intent", + "amount": 1000, + }, + } + }, + ) + assert err.error is not None + assert err.error.payment_intent is not None + assert err.error.payment_intent.id == "pi_123" + assert err.error.payment_intent.amount == 1000 + def test_error_object_not_dict(self): err = StripeError("message", json_body={"error": "not a dict"}) assert err.error is None diff --git a/tests/test_http_client.py b/tests/test_http_client.py index ea74cf7d9..2d1101f9f 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -168,23 +168,6 @@ def test_maximum_delay(self): expected = [0.5, 1.0, 2.0, 4.0, max_delay, max_delay, max_delay] self.assert_sleep_times(client, expected) - def test_retry_after_header(self): - client = _http_client.new_default_http_client() - client._add_jitter_time = lambda sleep_seconds: sleep_seconds - - # Prefer retry-after if it's bigger - assert 30 == client._sleep_time_seconds( - 2, (None, 409, {"retry-after": "30"}) - ) - # Prefer default if it's bigger - assert 2 == client._sleep_time_seconds( - 3, (None, 409, {"retry-after": "1"}) - ) - # Ignore crazy-big values - assert 1 == client._sleep_time_seconds( - 2, (None, 409, {"retry-after": "300"}) - ) - def test_randomness_added(self): client = _http_client.new_default_http_client() random_value = 0.8