1use http_types::{Body, Method, Request, Url};
2use serde::{de::DeserializeOwned, Serialize};
3
4use crate::{
5 client::{request_strategy::RequestStrategy, BaseClient, Response},
6 config::err,
7 generated::core::version::VERSION,
8 params::AppInfo,
9 AccountId, ApplicationId, Headers, StripeError,
10};
11
12static USER_AGENT: &str = concat!("Stripe/v1 RustBindings/", env!("CARGO_PKG_VERSION"));
13
14#[derive(Clone)]
15pub struct Client {
16 client: crate::client::BaseClient,
17 secret_key: String,
18 headers: Headers,
19 strategy: RequestStrategy,
20 app_info: Option<AppInfo>,
21 api_base: Url,
22 api_root: String,
23}
24
25impl Client {
26 pub fn new(secret_key: impl Into<String>) -> Self {
28 Self::from_url("https://api.stripe.com/", secret_key)
29 }
30
31 pub fn from_url<'a>(url: impl Into<&'a str>, secret_key: impl Into<String>) -> Self {
36 Client {
37 client: BaseClient::new(),
38 secret_key: secret_key.into(),
39 headers: Headers {
40 stripe_version: VERSION,
41 user_agent: USER_AGENT.to_string(),
42 client_id: None,
43 stripe_account: None,
44 },
45 strategy: RequestStrategy::Once,
46 app_info: None,
47 api_base: Url::parse(url.into()).expect("invalid url"),
48 api_root: "v1".to_string(),
49 }
50 }
51
52 pub fn with_client_id(mut self, id: ApplicationId) -> Self {
54 self.headers.client_id = Some(id);
55 self
56 }
57
58 pub fn with_stripe_account(mut self, id: AccountId) -> Self {
60 self.headers.stripe_account = Some(id);
61 self
62 }
63
64 pub fn with_strategy(mut self, strategy: RequestStrategy) -> Self {
78 self.strategy = strategy;
79 self
80 }
81
82 pub fn with_app_info(
88 mut self,
89 name: String,
90 version: Option<String>,
91 url: Option<String>,
92 ) -> Self {
93 let app_info = AppInfo { name, version, url };
94 self.headers.user_agent = format!("{} {}", USER_AGENT, app_info);
95 self.app_info = Some(app_info);
96 self
97 }
98
99 pub fn get<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
101 let url = self.url(path);
102 self.client.execute::<T>(self.create_request(Method::Get, url), &self.strategy)
103 }
104
105 pub fn get_query<T: DeserializeOwned + Send + 'static, P: Serialize>(
107 &self,
108 path: &str,
109 params: P,
110 ) -> Response<T> {
111 let url = match self.url_with_params(path, params) {
112 Err(e) => return err(e),
113 Ok(ok) => ok,
114 };
115 self.client.execute::<T>(self.create_request(Method::Get, url), &self.strategy)
116 }
117
118 pub fn delete<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
120 let url = self.url(path);
121 self.client.execute::<T>(self.create_request(Method::Delete, url), &self.strategy)
122 }
123
124 pub fn delete_query<T: DeserializeOwned + Send + 'static, P: Serialize>(
126 &self,
127 path: &str,
128 params: P,
129 ) -> Response<T> {
130 let url = match self.url_with_params(path, params) {
131 Err(e) => return err(e),
132 Ok(ok) => ok,
133 };
134 self.client.execute::<T>(self.create_request(Method::Delete, url), &self.strategy)
135 }
136
137 pub fn post<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
139 let url = self.url(path);
140 self.client.execute::<T>(self.create_request(Method::Post, url), &self.strategy)
141 }
142
143 pub fn post_form<T: DeserializeOwned + Send + 'static, F: Serialize>(
148 &self,
149 path: &str,
150 form: F,
151 ) -> Response<T> {
152 let url = self.url(path);
153 let mut req = self.create_request(Method::Post, url);
154
155 let mut params_buffer = Vec::new();
156 let qs_ser = &mut serde_qs::Serializer::new(&mut params_buffer);
157 if let Err(qs_ser_err) = serde_path_to_error::serialize(&form, qs_ser) {
158 return err(StripeError::QueryStringSerialize(qs_ser_err));
159 }
160
161 let body = std::str::from_utf8(params_buffer.as_slice())
162 .expect("Unable to extract string from params_buffer")
163 .to_string();
164
165 req.set_body(Body::from_string(body));
166
167 req.insert_header("content-type", "application/x-www-form-urlencoded");
168 self.client.execute::<T>(req, &self.strategy)
169 }
170
171 fn url(&self, path: &str) -> Url {
172 let mut url = self.api_base.clone();
173 url.set_path(&format!("{}/{}", self.api_root, path.trim_start_matches('/')));
174 url
175 }
176
177 fn url_with_params<P: Serialize>(&self, path: &str, params: P) -> Result<Url, StripeError> {
178 let mut url = self.url(path);
179
180 let mut params_buffer = Vec::new();
181 let qs_ser = &mut serde_qs::Serializer::new(&mut params_buffer);
182 serde_path_to_error::serialize(¶ms, qs_ser).map_err(StripeError::from)?;
183
184 let params = std::str::from_utf8(params_buffer.as_slice())
185 .expect("Unable to extract string from params_buffer")
186 .to_string();
187
188 url.set_query(Some(¶ms));
189 Ok(url)
190 }
191
192 fn create_request(&self, method: Method, url: Url) -> Request {
193 let mut req = Request::new(method, url);
194 req.insert_header("authorization", format!("Bearer {}", self.secret_key));
195
196 for (key, value) in self.headers.to_array().iter().filter_map(|(k, v)| v.map(|v| (*k, v))) {
197 req.insert_header(key, value);
198 }
199
200 req
201 }
202}
203
204#[cfg(test)]
205mod test {
206 use super::Client;
211
212 #[test]
213 fn user_agent_base() {
214 let client = Client::new("sk_test_12345");
215
216 assert_eq!(
217 client.headers.user_agent,
218 format!("Stripe/v1 RustBindings/{}", env!("CARGO_PKG_VERSION"))
219 );
220 }
221
222 #[test]
223 fn user_agent_minimal_app_info() {
224 let client =
225 Client::new("sk_test_12345").with_app_info("sick-new-startup".to_string(), None, None);
226
227 assert_eq!(
228 client.headers.user_agent,
229 format!("Stripe/v1 RustBindings/{} sick-new-startup", env!("CARGO_PKG_VERSION"))
230 );
231 }
232
233 #[test]
234 fn user_agent_all() {
235 let client = Client::new("sk_test_12345").with_app_info(
236 "sick-new-startup".to_string(),
237 Some("0.1.0".to_string()),
238 Some("https://sick-startup.io".to_string()),
239 );
240
241 assert_eq!(
242 client.headers.user_agent,
243 format!(
244 "Stripe/v1 RustBindings/{} sick-new-startup/0.1.0 (https://sick-startup.io)",
245 env!("CARGO_PKG_VERSION")
246 )
247 );
248 }
249}