1#![allow(
84 clippy::missing_const_for_fn,
85 clippy::missing_errors_doc,
86 clippy::missing_panics_doc,
87 clippy::must_use_candidate,
88 clippy::non_ascii_literal,
89 clippy::redundant_closure,
90 clippy::use_self,
91 clippy::used_underscore_binding
92)]
93#![warn(missing_debug_implementations, missing_copy_implementations)]
94#![deny(
95 trivial_casts,
96 trivial_numeric_casts,
97 unused_import_braces,
98 unused_qualifications
99)]
100
101mod checkout;
102mod common_types;
103pub mod errors;
104mod helpers;
105mod simulation;
106mod transaction;
107mod webhooks;
108
109use std::marker::PhantomData;
110
111pub use checkout::CheckoutPreferences;
112use common_types::ResponseRoot;
113pub use common_types::{
114 AddressType, AsPaymentMethod, CustomerAddress, CustomerPhoneContact, PaymentCreditCard,
115 PaymentOtherMethods, PhoneContactType, YapayCardData, YapayCustomer, YapayProduct,
116 YapayTransaction, YapayTransactionStatus,
117};
118use futures::TryFutureExt;
119use reqwest::header::{CONTENT_TYPE, LOCATION};
120use reqwest::redirect::Policy;
121use reqwest::{Client, Method};
122use serde::de::DeserializeOwned;
123use serde::Serialize;
124use validator::Validate;
125pub use webhooks::YapayWebhook;
126
127use crate::errors::{ApiError, InvalidError, SDKError};
128use crate::simulation::{PaymentTaxResponse, SimulatePayload, SimulationResponseWrapper};
129use crate::transaction::creditcard::TransactionResponse;
130use crate::transaction::{PaymentRequestRoot, TransactionResponseWrapper};
131
132const API_PROD_BASE: &str = "https://api.intermediador.yapay.com.br/api";
133const API_TEST_BASE: &str = "https://api.intermediador.sandbox.yapay.com.br/api";
134
135const CHECKOUT_PROD_BASE: &str = "https://tc.intermediador.yapay.com.br/payment/transaction";
136const CHECKOUT_TEST_BASE: &str =
137 "https://tc-intermediador-sandbox.yapay.com.br/payment/transaction";
138
139pub trait CanValidate: Serialize + Validate {}
140
141#[derive(Debug, Copy, Clone, Eq, PartialEq)]
142pub enum YapayEnv {
143 PRODUCTION,
144 SANDBOX,
145}
146
147impl YapayEnv {
148 pub const fn checkout_link(self) -> &'static str {
149 match self {
150 Self::PRODUCTION => CHECKOUT_PROD_BASE,
151 Self::SANDBOX => CHECKOUT_TEST_BASE,
152 }
153 }
154
155 pub const fn api_link(self) -> &'static str {
156 match self {
157 Self::PRODUCTION => API_PROD_BASE,
158 Self::SANDBOX => API_TEST_BASE,
159 }
160 }
161}
162
163#[derive(Copy, Clone, Debug)]
165pub struct YapaySDKBuilder {}
166
167impl YapaySDKBuilder {
168 pub fn with_token<T>(account_token: &T) -> YapaySDK
170 where
171 T: ToString,
172 {
173 let http_client = Client::builder()
174 .cookie_store(true)
175 .redirect(Policy::none())
176 .build()
177 .expect("Failed to create client.");
178
179 YapaySDK {
180 http_client,
181 account_token: account_token.to_string(),
182 }
183 }
184}
185
186#[derive(Debug)]
187pub struct YapaySDK {
188 pub(crate) http_client: Client,
189 pub(crate) account_token: String,
190}
191
192#[derive(Debug)]
193pub struct SDKJsonRequest<'a, RP> {
194 http_client: &'a Client,
195 method: Method,
196 endpoint: &'a str,
197 payload: String,
198 response_type: PhantomData<RP>,
199}
200
201impl<'a, RP> SDKJsonRequest<'a, RP> {
202 #[must_use]
203 pub fn from_sdk(sdk: &'a YapaySDK, method: Method, endpoint: &'a str, payload: String) -> Self {
204 Self {
205 http_client: &sdk.http_client,
206 method,
207 endpoint,
208 response_type: Default::default(),
209 payload,
210 }
211 }
212}
213
214impl<'a, RP> SDKJsonRequest<'a, RP> {
215 pub async fn execute(self, yapay_env: YapayEnv) -> Result<RP, SDKError>
217 where
218 RP: DeserializeOwned + Send,
219 {
220 let api_endpoint = format!("{}{}", yapay_env.api_link(), self.endpoint);
221 tracing::trace!("api endpoint: {:?}", api_endpoint);
222
223 let request = self
224 .http_client
225 .request(self.method, api_endpoint)
226 .body(self.payload)
227 .header(CONTENT_TYPE, "application/json")
228 .build()
229 .unwrap();
230 tracing::trace!("request = {:#?}", request);
231
232 let response = self
233 .http_client
234 .execute(request)
235 .and_then(reqwest::Response::text)
236 .await?;
237 tracing::trace!("response = {}", response);
238
239 let error_jd = serde_json::from_str::<ApiError>(&*response);
241 if let Ok(err) = error_jd {
242 tracing::error!("err = {:#?}", err);
243 return Err(SDKError::PayloadError(err));
244 }
245
246 let jd = &mut serde_json::Deserializer::from_str(&*response);
247 let res: Result<RP, _> = serde_path_to_error::deserialize(jd);
248
249 match res {
250 Ok(deserialized_resp) => Ok(deserialized_resp),
251 Err(err) => {
252 tracing::error!("{:?}", err.path());
253 tracing::error!("Error = {:#?}", err);
254 Err(SDKError::GenericError)
255 }
256 }
257 }
258}
259
260pub type CardTransactionResponse = ResponseRoot<TransactionResponseWrapper<TransactionResponse>>;
261pub type SimulationResponse = ResponseRoot<SimulationResponseWrapper<PaymentTaxResponse>>;
262
263impl YapaySDK {
264 pub async fn create_checkout_page(
265 &self,
266 yapay_env: YapayEnv,
267 checkout_preferences: CheckoutPreferences,
268 ) -> Result<String, SDKError> {
269 let querystring = checkout_preferences.to_form(&*self.account_token);
270 let request = self
271 .http_client
272 .request(Method::POST, yapay_env.checkout_link())
273 .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
274 .body(querystring)
275 .build()
276 .unwrap();
277
278 let response = self.http_client.execute(request).await.unwrap();
279
280 response
281 .headers()
282 .get(LOCATION)
283 .and_then(|hdr| hdr.to_str().ok())
284 .map(ToString::to_string)
285 .ok_or(SDKError::GenericError)
286 }
287
288 pub fn create_credit_card_payment(
290 &self,
291 customer: YapayCustomer,
292 transaction: YapayTransaction,
293 products: Vec<YapayProduct>,
294 cc_payment_data: YapayCardData,
295 ) -> Result<SDKJsonRequest<CardTransactionResponse>, SDKError> {
296 let request_payload = PaymentRequestRoot::new(
297 self.account_token.clone(),
298 customer,
299 products,
300 transaction,
301 cc_payment_data,
302 );
303
304 if let Err(errs) = request_payload.validate() {
305 return Err(InvalidError::ValidatorLibError(errs).into());
306 }
307
308 let payload = serde_json::to_string(&request_payload).expect("Safe to unwrap.");
309
310 Ok(SDKJsonRequest::from_sdk(
311 self,
312 Method::POST,
313 "/v3/transactions/payment",
314 payload,
315 ))
316 }
317
318 #[must_use]
319 pub fn simulate_payment(&self, total_amount: f64) -> SDKJsonRequest<SimulationResponse> {
320 let request_payload = SimulatePayload::new(self.account_token.clone(), total_amount);
321 let payload = serde_json::to_string(&request_payload).unwrap();
322
323 SDKJsonRequest::from_sdk(
324 self,
325 Method::POST,
326 "/v1/transactions/simulate_splitting",
327 payload,
328 )
329 }
330}