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}