Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEGEN_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eed9b64db4a551dcc837a073c124117f265c21d3
3723bdf073100308cd413efdb4aefbc912f9bf2f
71 changes: 70 additions & 1 deletion stripe/_error_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down
28 changes: 3 additions & 25 deletions stripe/_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`.
Expand All @@ -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:
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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 "
Expand Down
32 changes: 32 additions & 0 deletions tests/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 0 additions & 17 deletions tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading