Packages

Complete Stripe SDK for Elixir with parity to the official Ruby SDK. V1+V2 coverage (193 services, 320 resource structs, 525 documented params). Per-event modules, Finch HTTP/2, RustyJSON, automatic retries, OAuth, webhooks, telemetry, per-client config, streaming pagination.

Current section

Files

Jump to
tiger_stripe lib stripe error.ex
Raw

lib/stripe/error.ex

defmodule Stripe.Error do
@moduledoc """
Stripe API error.
All API errors are returned as `{:error, %Stripe.Error{}}` tuples.
## Error Types
* `:api_error` - Generic API error
* `:authentication_error` - Invalid API key (401)
* `:card_error` - Card was declined
* `:idempotency_error` - Idempotency key conflict
* `:invalid_request_error` - Invalid parameters (400/404)
* `:permission_error` - Insufficient permissions (403)
* `:rate_limit_error` - Too many requests (429)
* `:api_connection_error` - Network/connection failure
* `:signature_verification_error` - Webhook signature mismatch
"""
@type error_type ::
:api_error
| :authentication_error
| :card_error
| :idempotency_error
| :invalid_request_error
| :permission_error
| :rate_limit_error
| :api_connection_error
| :signature_verification_error
@type t :: %__MODULE__{
type: error_type(),
message: String.t() | nil,
code: String.t() | nil,
param: String.t() | nil,
http_status: integer() | nil,
http_body: String.t() | nil,
http_headers: [{String.t(), String.t()}] | nil,
request_id: String.t() | nil,
decline_code: String.t() | nil,
charge: String.t() | nil,
payment_intent: map() | nil,
payment_method: map() | nil,
setup_intent: map() | nil,
doc_url: String.t() | nil,
retry_after: number() | nil
}
defstruct [
:type,
:message,
:code,
:param,
:http_status,
:http_body,
:http_headers,
:request_id,
:decline_code,
:charge,
:payment_intent,
:payment_method,
:setup_intent,
:doc_url,
:retry_after
]
@doc "Build an error from an HTTP response."
@spec from_response(integer(), String.t(), [{String.t(), String.t()}]) :: t()
def from_response(status, body, headers) do
request_id = get_header(headers, "request-id")
retry_after = get_header(headers, "retry-after")
error_data =
case JSON.decode(body) do
{:ok, %{"error" => error}} -> error
_ -> %{}
end
%__MODULE__{
type: error_type_from_response(status, error_data),
message: error_data["message"],
code: error_data["code"],
param: error_data["param"],
http_status: status,
http_body: body,
http_headers: headers,
request_id: request_id,
decline_code: error_data["decline_code"],
charge: error_data["charge"],
payment_intent: error_data["payment_intent"],
payment_method: error_data["payment_method"],
setup_intent: error_data["setup_intent"],
doc_url: error_data["doc_url"],
retry_after: parse_retry_after(retry_after)
}
end
@doc "Build a network/connection error."
@spec connection_error(String.t()) :: t()
def connection_error(message) do
%__MODULE__{type: :api_connection_error, message: message}
end
@doc "Build a webhook signature verification error."
@spec signature_verification_error(String.t()) :: t()
def signature_verification_error(message) do
%__MODULE__{type: :signature_verification_error, message: message}
end
@doc "Returns true if this error is retryable."
@spec retryable?(t()) :: boolean()
def retryable?(%__MODULE__{type: :api_connection_error}), do: true
def retryable?(%__MODULE__{http_status: 409}), do: true
def retryable?(%__MODULE__{http_status: status}) when status >= 500, do: true
def retryable?(%__MODULE__{http_status: 429, code: "lock_timeout"}), do: true
def retryable?(_), do: false
# Map HTTP status + error body to our error type.
# Matches Ruby SDK: 401 → auth, 402 → card, 403 → permission, 429 → rate limit.
defp error_type_from_response(401, _data), do: :authentication_error
defp error_type_from_response(403, _data), do: :permission_error
defp error_type_from_response(429, _data), do: :rate_limit_error
defp error_type_from_response(_status, %{"type" => "card_error"}), do: :card_error
defp error_type_from_response(_status, %{"type" => "idempotency_error"}), do: :idempotency_error
defp error_type_from_response(_status, %{"type" => "invalid_request_error"}),
do: :invalid_request_error
defp error_type_from_response(_status, _data), do: :api_error
defp get_header(headers, name) do
case List.keyfind(headers, name, 0) do
{_, value} -> value
nil -> nil
end
end
defp parse_retry_after(nil), do: nil
defp parse_retry_after(value) do
case Float.parse(value) do
{seconds, _} -> seconds
:error -> nil
end
end
end