pix_api_client/lib.rs
1//! You don't need to wrap `PixClient` into an `Arc`, since its inner client is already wrapped.
2//!
3//! ## Note
4//!
5//! You must take care of manually renewing your oauth token. This is accomplished easily
6//! with helper functions provided by the `PixClient`.
7//!
8//! # Example: Create a new client and fetch the oauth token
9//!
10//! ```no_run
11//! # use std::fs::File;
12//! # use std::io::Read;
13//! use pix_api_client::cob::CobrancaImediata;
14//! use pix_api_client::{Executor, PixClient};
15//! use reqwest::header;
16//!
17//! # async fn teste() -> Result<(), anyhow::Error> {
18//!
19//! let mut cert_buffer = Vec::new();
20//! File::open("my_cert.pem")?.read_to_end(&mut cert_buffer)?;
21//!
22//! // format your headers the way your PSP expects it
23//! // this is just an example
24//! let pix_client = PixClient::new_with_custom_headers(
25//! "https://my-pix-h",
26//! |headers| {
27//! let username = "my-id";
28//! let secret = "my-secret";
29//! let formatted_authorization = format!("{}:{}", username, secret);
30//! let encoded_auth = base64::encode(formatted_authorization);
31//!
32//! // and then insert it
33//! headers
34//! .insert(header::AUTHORIZATION, encoded_auth.parse().unwrap())
35//! .unwrap();
36//! },
37//! cert_buffer,
38//! );
39//!
40//! let oauth_response = pix_client.oauth().autenticar().execute().await?;
41//!
42//! // retrieve your new access token, and store it as your new authorization header
43//! let token = oauth_response.access_token;
44//! pix_client.swap_authorization_token(token.to_string());
45//!
46//! // Your client is ready for any further api calls.
47//!
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! # Example: Create a new QRCode from a create immediate transaction endpoint
53//! ```no_run
54//! # use std::fs::File;
55//! # use std::io::Read;
56//! use pix_api_client::cob::{CobrancaImediata, Devedor};
57//! use pix_api_client::{Executor, PixClient};
58//! use pix_brcode::qr_dinamico::PixDinamicoSchema;
59//! use pix_api_client::extensions::FromResponse;
60//!
61//! # async fn doc_test() -> Result<(), anyhow::Error> {
62//! # let mut cert_buffer = Vec::new();
63//! # File::open("my_cert.pem")?.read_to_end(&mut cert_buffer)?;
64//! # let pix_client = PixClient::new("https://my-compliant-endpoint/pix/v2", "client-id", "client-secret", cert_buffer);
65//!
66//! let devedor = Devedor::new_pessoa_fisica("00000000000".to_string(), "Fulano de tal".to_string());
67//! let payload = CobrancaImediata::new(10.25, "my-key".to_string(), devedor);
68//!
69//! let response: CobrancaImediata = pix_client
70//! .cob()
71//! .criar_cobranca_imediata(payload)
72//! .execute()
73//! .await?;
74//!
75//! let pix: String = PixDinamicoSchema::from_cobranca_imediata_basic(response, "minha loja", "minha cidade").serialize_with_src();
76//!
77//! # Ok(())
78//! # }
79//! ```
80use std::marker::PhantomData;
81use std::sync::Arc;
82
83use arc_swap::ArcSwap;
84use async_trait::async_trait;
85pub use pix_brcode::qr_dinamico::PixDinamicoSchema;
86pub use reqwest::header;
87use reqwest::header::HeaderMap;
88use reqwest::{Client, Identity, Method, Request, StatusCode};
89use serde::de::DeserializeOwned;
90use serde::Serialize;
91
92use crate::errors::{ApiResult, PixError};
93
94pub mod cob;
95pub mod errors;
96pub mod webhook;
97
98pub mod extensions;
99
100pub mod oauth;
101
102#[derive(Debug)]
103struct PixClientBuilder {
104 username: String,
105 secret: String,
106 certificate: Vec<u8>,
107}
108
109impl PixClientBuilder {}
110
111/// A strongly typed client for performing requests to a pix-api compliant provider.
112///
113/// # Example
114#[derive(Debug)]
115pub struct PixClient {
116 inner_client: Client,
117 /// Headers used for every request
118 headers: ArcSwap<HeaderMap>,
119 certificate: Vec<u8>,
120
121 base_endpoint: String,
122}
123
124impl PixClient {
125 /// Creates a new `PixClient` with customized headers.
126 ///
127 /// This is specially useful, since how the authorization is encoded varies between PSP's.
128 ///
129 /// # Example
130 ///
131 /// ```no_run
132 /// # use std::fs::File;
133 /// # use std::io::{Read, Error};
134 /// use pix_api_client::PixClient;
135 /// use reqwest::header;
136 ///
137 /// # fn teste() -> Result<(), anyhow::Error> {
138 /// let mut cert_buffer = Vec::new();
139 /// File::open("my_cert.pem")?.read_to_end(&mut cert_buffer)?;
140 ///
141 /// let username = "my-id";
142 /// let secret = "my-secret";
143 /// let formatted_authorization = format!("{}:{}", username, secret);
144 /// let encoded_auth = base64::encode(formatted_authorization);
145 ///
146 /// let pix_client = PixClient::new_with_custom_headers(
147 /// "https://*",
148 /// |headers| {
149 /// headers
150 /// .insert(header::AUTHORIZATION, encoded_auth.parse().unwrap())
151 /// .unwrap();
152 /// },
153 /// cert_buffer,
154 /// );
155 ///
156 /// # Ok(())
157 /// # }
158 /// ```
159 pub fn new_with_custom_headers<F>(endpoint: &str, mut custom_headers: F, certificate: Vec<u8>) -> PixClient
160 where
161 F: FnMut(&mut HeaderMap),
162 {
163 let identity = Identity::from_pkcs12_der(&*certificate, "").expect("Invalid certificate");
164 let mut default_headers = HeaderMap::new();
165
166 custom_headers(&mut default_headers);
167
168 let client = Client::builder().identity(identity).https_only(true).build().unwrap();
169
170 Self {
171 inner_client: client,
172 headers: ArcSwap::from_pointee(default_headers),
173 certificate,
174 base_endpoint: endpoint.to_string(),
175 }
176 }
177
178 /// Call this method in order to change the value of your `Authorization` header.
179 ///
180 /// For Bearer: `format!("Bearer {}", token)`
181 ///
182 /// For Basic: `format!("Basic {}:{}", id, secret)`
183 ///
184 /// This is usually done after you fetch the oauth token.
185 pub fn swap_authorization_token(&self, authorization_header_value: String) {
186 let mut stored_header = HeaderMap::new();
187 stored_header.insert(header::AUTHORIZATION, authorization_header_value.parse().unwrap());
188
189 self.headers.store(Arc::new(stored_header));
190 }
191
192 fn request_with_headers<Payload, Response>(
193 &self,
194 method: Method,
195 endpoint: &str,
196 payload: Payload,
197 ) -> ApiRequest<Response>
198 where
199 Payload: Serialize,
200 Response: DeserializeOwned,
201 {
202 let inner_headers = &**self.headers.load();
203 let request = self
204 .inner_client
205 .request(method, endpoint)
206 .headers(inner_headers.clone())
207 .json(&payload)
208 .build()
209 .unwrap();
210
211 ApiRequest::new(self, request)
212 }
213}
214
215#[derive(Debug)]
216pub struct ApiRequest<'a, Response> {
217 client: &'a PixClient,
218 request: Request,
219 response_type: PhantomData<Response>,
220}
221
222impl<'a, T> ApiRequest<'a, T> {
223 fn new(client: &'a PixClient, request: Request) -> ApiRequest<T> {
224 Self {
225 client,
226 request,
227 response_type: Default::default(),
228 }
229 }
230}
231
232#[async_trait]
233impl<ResponseType> Executor<ResponseType> for ApiRequest<'_, ResponseType>
234where
235 ResponseType: DeserializeOwned + Send,
236{
237 async fn execute(self) -> ApiResult<ResponseType> {
238 let body = self
239 .request
240 .body()
241 .map(|x| x.as_bytes().map(|x| String::from_utf8(Vec::from(x)).unwrap()))
242 .flatten();
243 log::info!("{:?}", body);
244
245 let result = self.client.inner_client.execute(self.request).await?;
246 let status_code = result.status();
247
248 let text = result.text().await?;
249 log::info!("{}", text);
250
251 if !status_code.is_success() {
252 return match status_code {
253 StatusCode::UNAUTHORIZED => Err(PixError::InvalidCredentials),
254 StatusCode::BAD_REQUEST => Err(PixError::PayloadError),
255 _ => Err(PixError::Other(text)),
256 };
257 }
258
259 serde_json::from_str::<ResponseType>(&*text).map_err(|e| e.into())
260 }
261}
262
263#[async_trait]
264pub trait Executor<T> {
265 async fn execute(self) -> ApiResult<T>;
266}