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}