1#![allow(
2 clippy::type_complexity,
3 clippy::large_enum_variant,
4 clippy::result_large_err,
5 clippy::if_same_then_else
6)]
7use reqwest::Client;
8use url::Url;
9
10pub mod models;
12pub use models::*;
13
14pub mod checkouts;
16pub mod customers;
17pub mod members;
18pub mod memberships;
19pub mod merchant;
20pub mod payouts;
21pub mod readers;
22pub mod receipts;
23pub mod roles;
24pub mod transactions;
25
26pub use transactions::TransactionHistoryQuery;
28
29#[derive(Debug)]
31pub enum Error {
32 Http(reqwest::Error),
33 Json(serde_json::Error),
34 Url(url::ParseError),
35 ApiError { status: u16, body: ApiErrorBody },
37}
38
39#[derive(Debug, serde::Deserialize)]
41pub struct ApiErrorBody {
42 #[serde(rename = "type")]
43 pub error_type: Option<String>,
44 pub title: Option<String>,
45 pub status: Option<u16>,
46 pub detail: Option<String>,
47 pub error_code: Option<String>,
48 pub message: Option<String>,
49 pub param: Option<String>,
50 #[serde(flatten)]
52 pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
53}
54
55impl From<reqwest::Error> for Error {
56 fn from(err: reqwest::Error) -> Self {
57 Error::Http(err)
58 }
59}
60
61impl From<serde_json::Error> for Error {
62 fn from(err: serde_json::Error) -> Self {
63 Error::Json(err)
64 }
65}
66
67impl From<url::ParseError> for Error {
68 fn from(err: url::ParseError) -> Self {
69 Error::Url(err)
70 }
71}
72
73impl std::fmt::Display for Error {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Error::Http(e) => write!(f, "HTTP error: {}", e),
77 Error::Json(e) => write!(f, "JSON error: {}", e),
78 Error::Url(e) => write!(f, "URL error: {}", e),
79 Error::ApiError { status, body } => {
80 let status_str = status.to_string();
82 let message = body
83 .detail
84 .as_ref()
85 .or(body.message.as_ref())
86 .or(body.title.as_ref())
87 .unwrap_or(&status_str);
88 write!(f, "API error {}: {}", status, message)
89 }
90 }
91 }
92}
93
94impl std::error::Error for Error {}
95
96pub type Result<T> = std::result::Result<T, Error>;
97
98pub struct SumUpClient {
100 pub(crate) http_client: Client,
101 pub(crate) api_key: String,
102 pub(crate) base_url: Url,
103}
104
105impl SumUpClient {
106 pub fn new(api_key: String, use_sandbox: bool) -> Result<Self> {
113 let base_url_str = if use_sandbox {
114 "https://api.sumup.com" } else {
116 "https://api.sumup.com"
117 };
118
119 Ok(Self {
120 http_client: Client::new(),
121 api_key,
122 base_url: Url::parse(base_url_str)?,
123 })
124 }
125
126 pub fn with_custom_url(api_key: String, base_url: String) -> Result<Self> {
133 Ok(Self {
134 http_client: Client::new(),
135 api_key,
136 base_url: Url::parse(&base_url)?,
137 })
138 }
139
140 pub(crate) fn build_url(&self, path: &str) -> Result<Url> {
141 Ok(self.base_url.join(path)?)
142 }
143
144 pub(crate) async fn handle_error<T>(&self, response: reqwest::Response) -> Result<T> {
146 let status = response.status().as_u16();
147
148 let response_text = response.text().await.unwrap_or_default();
150
151 let body = match serde_json::from_str::<ApiErrorBody>(&response_text) {
153 Ok(parsed_body) => parsed_body,
154 Err(_) => {
155 ApiErrorBody {
157 error_type: None,
158 title: None,
159 status: Some(status),
160 detail: Some(response_text),
161 error_code: None,
162 message: None,
163 param: None,
164 additional_fields: std::collections::HashMap::new(),
165 }
166 }
167 };
168
169 Err(Error::ApiError { status, body })
170 }
171
172 pub fn api_key(&self) -> &str {
174 &self.api_key
175 }
176
177 pub fn base_url(&self) -> &Url {
179 &self.base_url
180 }
181}