mercadopago_sdk_rust/
lib.rs

1//! An open source, strongly-typed SDK for the MercadoPago API.
2//!
3//! It will try to hold your hand and reduce the possibility of errors, providing the correct API
4//! surface.
5//!
6//! ### Note
7//!
8//! The library is still under development and its public API is subject to change.
9//!
10//! # Installation
11//!
12//! Added the following into your Cargo.toml:
13//!
14//! ```toml
15//! mercadopago_sdk_rust = "0.1"
16//! ```
17//!
18//! # Usage
19//!
20//! The client is built using the
21//! [`MercadoPagoSDKBuilder::with_token`](crate::MercadoPagoSDKBuilder) `with_token`
22//! method.
23//!
24//! ```rust
25//! # fn main() {
26//! use mercadopago_sdk_rust::{MercadoPagoSDK, MercadoPagoSDKBuilder};
27//!
28//! let mp_sdk: MercadoPagoSDK = MercadoPagoSDKBuilder::with_token("MP_ACCESS_TOKEN");
29//!
30//! # }
31//! ```
32//!
33//! Once the token is inserted, you can call methods on [`crate::MercadoPagoSDK`]
34//!
35//!
36//!
37//! # Creating a CheckoutPro Preference
38//! ```no_run
39//! use mercadopago_sdk_rust::common_types::{CheckoutProPayer, Item};
40//! use mercadopago_sdk_rust::payments::requests::DocumentType;
41//! use mercadopago_sdk_rust::preferences::requests::CheckoutProPreferences;
42//! use mercadopago_sdk_rust::MercadoPagoSDKBuilder;
43//!
44//! #[tokio::main]
45//! async fn async_main() {
46//!     let mp_sdk = MercadoPagoSDKBuilder::with_token("MP_ACCESS_TOKEN");
47//!
48//!     let sample_item =
49//!         Item::minimal_item("Sample item".to_string(), "".to_string(), 15.00, 1).unwrap();
50//!
51//!     let preferences = CheckoutProPreferences::new()
52//!         .set_items(vec![sample_item])
53//!         .set_payer(CheckoutProPayer::minimal_payer(
54//!             "fulano@beltrano.com.br".to_string(),
55//!             DocumentType::CPF,
56//!             41810524485,
57//!         ));
58//!
59//!     mp_sdk
60//!         .create_preferences_checkout_pro(preferences)
61//!         .expect("Failed to validate checkout preference. Something is wrong.")
62//!         .execute()
63//!         .await
64//!         .unwrap();
65//! }
66//! ```
67//!
68//! # Other Examples
69//!
70//! Check out the `tests` folder inside our repository to check for more examples.
71//!
72//! # License
73//! Project is licensed under the permissive MIT license.
74
75pub mod card_tokens;
76pub mod common_types;
77pub mod errors;
78pub mod helpers;
79pub mod payments;
80pub mod preferences;
81pub mod webhooks;
82
83use std::marker::PhantomData;
84
85use futures::future::err;
86use futures::TryFutureExt;
87use oauth2::basic::BasicClient;
88use oauth2::reqwest::async_http_client;
89use oauth2::{
90    AccessToken, AuthType, AuthUrl, ClientId, ClientSecret, Scope, TokenResponse, TokenUrl,
91};
92use reqwest::{Client, Method, RequestBuilder};
93use serde::de::DeserializeOwned;
94
95use crate::card_tokens::requests::CardTokenOptions;
96use crate::card_tokens::responses::CardTokenResponse;
97use crate::errors::{ApiError, SDKError};
98use crate::payments::requests::CreatePaymentPayload;
99use crate::preferences::requests::CheckoutProPreferences;
100use crate::preferences::responses::CheckoutProPreferencesResponse;
101
102const API_BASE_URL: &str = "https://api.mercadopago.com";
103
104///
105#[derive(Debug)]
106pub struct MercadoPagoSDKBuilder {}
107
108impl MercadoPagoSDKBuilder {
109    async fn authorize<T: ToString>(
110        client_id: T,
111        client_secret: T,
112    ) -> Result<MercadoPagoSDK, SDKError> {
113        let client = BasicClient::new(
114            ClientId::new(client_id.to_string()),
115            Some(ClientSecret::new(client_secret.to_string())),
116            AuthUrl::new("https://auth.mercadopago.com/authorization".to_string()).unwrap(),
117            Some(TokenUrl::new("https://api.mercadopago.com/oauth/token".to_string()).unwrap()),
118        )
119        .set_auth_type(AuthType::BasicAuth);
120
121        let token_response = client
122            .exchange_client_credentials()
123            .add_scope(Scope::new("offline_access".to_string()))
124            .request_async(async_http_client)
125            .await
126            .unwrap();
127
128        Ok(MercadoPagoSDK {
129            http_client: Default::default(),
130            access_token: token_response.access_token().clone(),
131        })
132    }
133
134    /// Creates an [`MercadoPagoSDK`] ready to request the API.
135    pub fn with_token<T: ToString>(client_access_token: T) -> MercadoPagoSDK {
136        MercadoPagoSDK {
137            http_client: Default::default(),
138            access_token: AccessToken::new(client_access_token.to_string()),
139        }
140    }
141}
142
143#[derive(Debug)]
144pub struct MercadoPagoSDK {
145    pub(crate) http_client: Client,
146    pub(crate) access_token: AccessToken,
147}
148
149#[derive(Debug)]
150pub struct SDKRequest<'a, RP> {
151    http_client: &'a Client,
152    access_token: &'a AccessToken,
153    request: RequestBuilder,
154    response_type: PhantomData<RP>,
155}
156
157impl<'a, RP> SDKRequest<'a, RP> {
158    /// Injects bearer token, and return response
159    pub async fn execute(self) -> Result<RP, SDKError>
160    where
161        RP: DeserializeOwned,
162    {
163        let request = self
164            .request
165            .bearer_auth(self.access_token.secret())
166            .build()
167            .unwrap();
168        let response = self
169            .http_client
170            .execute(request)
171            .and_then(|c| c.text())
172            .await?;
173        eprintln!("response = {}", response);
174
175        // matches errors due to wrong payloads etc
176        let error_jd = serde_json::from_str::<ApiError>(&*response);
177        if let Ok(err) = error_jd {
178            eprintln!("err = {:#?}", err);
179            return Err(SDKError::GenericError);
180        }
181
182        let jd = &mut serde_json::Deserializer::from_str(&*response);
183        let res: Result<RP, _> = serde_path_to_error::deserialize(jd);
184
185        match res {
186            Ok(deserialized_resp) => Ok(deserialized_resp),
187            Err(wow) => {
188                println!("{:?}", wow.path());
189                eprintln!("Error = {:#?}", wow);
190                Err(SDKError::GenericError)
191            }
192        }
193    }
194}
195
196impl MercadoPagoSDK {
197    pub fn create_preferences_checkout_pro(
198        &self,
199        opts: CheckoutProPreferences,
200    ) -> Result<SDKRequest<CheckoutProPreferencesResponse>, SDKError> {
201        if opts.validate() {}
202
203        let request = self
204            .http_client
205            .request(
206                Method::POST,
207                API_BASE_URL.to_string() + "/checkout/preferences",
208            )
209            .json(&opts);
210
211        Ok(SDKRequest {
212            http_client: &self.http_client,
213            access_token: &self.access_token,
214            request,
215            response_type: PhantomData::<_>,
216        })
217    }
218
219    /// Used to create and save a credit/debit card token, instead of transacting raw sensitive
220    /// data, such as card number.
221    ///
222    /// Create a token before issuing payments with cards.
223    pub fn create_card_token(
224        &self,
225        opts: CardTokenOptions,
226    ) -> Result<SDKRequest<CardTokenResponse>, SDKError> {
227        let url = format!(
228            "{}/v1/card_tokens?public_key={}",
229            API_BASE_URL,
230            opts.public_key.as_deref().unwrap_or("")
231        );
232
233        let request = self.http_client.request(Method::POST, url).json(&opts);
234
235        Ok(SDKRequest {
236            http_client: &self.http_client,
237            access_token: &self.access_token,
238            request,
239            response_type: PhantomData::<_>,
240        })
241    }
242
243    pub fn create_payment(
244        &self,
245        opts: CreatePaymentPayload,
246    ) -> Result<SDKRequest<CheckoutProPreferencesResponse>, SDKError> {
247        let request = self
248            .http_client
249            .request(Method::POST, "/payments")
250            .json(&opts);
251
252        Ok(SDKRequest {
253            http_client: &self.http_client,
254            access_token: &self.access_token,
255            request,
256            response_type: PhantomData::<CheckoutProPreferencesResponse>,
257        })
258    }
259}