Skip to main content

iran_pay/
error.rs

1//! Strongly-typed errors returned by every [`Gateway`](crate::Gateway) call.
2
3use thiserror::Error;
4
5use crate::Amount;
6
7/// Errors returned by gateway drivers.
8///
9/// Variants fall into three families:
10///
11/// 1. **Transport** — [`Error::Http`] wraps every `reqwest` failure
12///    (connection refused, TLS handshake, timeout, malformed response, …).
13/// 2. **Gateway** — [`Error::Gateway`] is returned when a provider's API
14///    accepts the request but reports a *business* failure (insufficient
15///    funds, expired authority, blocked merchant, etc.).  The contained
16///    `code` is the raw provider code; check provider docs to interpret.
17/// 3. **Local** — [`Error::Config`], [`Error::AmountMismatch`],
18///    [`Error::Unsupported`] are produced inside the SDK before/after the
19///    HTTP roundtrip.
20#[derive(Debug, Error)]
21#[non_exhaustive]
22pub enum Error {
23    /// HTTP transport / serialisation failure.
24    #[error("HTTP request to {provider} gateway failed: {source}")]
25    Http {
26        /// Driver name (`"zarinpal"`, `"idpay"`, …).
27        provider: &'static str,
28        /// Underlying reqwest error.
29        #[source]
30        source: reqwest::Error,
31    },
32
33    /// Gateway returned a business-level error.
34    ///
35    /// `code` is the provider's native error code; consult the provider's
36    /// documentation to interpret it.  `message` is the human-readable
37    /// message (often Persian).
38    #[error("{provider} gateway error (code {code}): {message}")]
39    Gateway {
40        /// Driver name.
41        provider: &'static str,
42        /// Provider-specific numeric error code.
43        code: i64,
44        /// Provider's human-readable message.
45        message: String,
46    },
47
48    /// Verification was attempted with an amount that doesn't match what
49    /// was originally charged.  Almost always indicates someone tampered
50    /// with the callback query string.
51    #[error("amount mismatch — expected {expected}, gateway reported {actual}")]
52    AmountMismatch {
53        /// What the merchant expected.
54        expected: Amount,
55        /// What the gateway reported during verification.
56        actual: Amount,
57    },
58
59    /// Configuration is invalid (missing merchant ID, malformed URL, etc.).
60    #[error("invalid configuration: {0}")]
61    Config(String),
62
63    /// The provider does not support this operation (e.g. refunds via Pay.ir
64    /// require a separate API contract).
65    #[error("{operation} is not supported by the {provider} gateway")]
66    Unsupported {
67        /// Driver name.
68        provider: &'static str,
69        /// The unsupported operation.
70        operation: &'static str,
71    },
72
73    /// Response decoding failed — the provider returned a payload we couldn't
74    /// match to the expected schema.  Usually means the SDK is out of date
75    /// relative to the provider's API.
76    #[error("could not decode {provider} response: {message}")]
77    Decode {
78        /// Driver name.
79        provider: &'static str,
80        /// Description of what went wrong.
81        message: String,
82    },
83}
84
85impl Error {
86    /// Helper: build an [`Error::Http`] from a reqwest error and a driver
87    /// name.  Used internally by every driver.
88    #[allow(dead_code)] // unused when every provider feature is disabled
89    pub(crate) fn http(provider: &'static str, source: reqwest::Error) -> Self {
90        Self::Http { provider, source }
91    }
92
93    /// Helper: build an [`Error::Decode`] from a driver name and message.
94    #[allow(dead_code)] // unused when every provider feature is disabled
95    pub(crate) fn decode(provider: &'static str, message: impl Into<String>) -> Self {
96        Self::Decode {
97            provider,
98            message: message.into(),
99        }
100    }
101}