1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt::Display;
4use std::num::NonZeroU8;
5
6use lazy_static::lazy_static;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use strum::{EnumIter, IntoEnumIterator};
10use time::macros::format_description;
11use time::{Date, OffsetDateTime};
12use validator::Validate;
13
14use crate::errors::InvalidError;
15use crate::helpers::format_available_payment_method;
16use crate::{CanValidate, SDKError};
17
18lazy_static! {
19 static ref REGEX_BIRTH_DATE: Regex = Regex::new(r"\d{2}/\d{2}/\d{4}$").unwrap();
20 pub static ref CLEAN_WEBHOOK_REGEX: Regex =
21 Regex::new(r"\&transaction\[products\]\[\].*?\&").unwrap();
22}
23
24#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Debug, strum::Display)]
28pub enum YapayTransactionStatus {
29 #[serde(rename = "4")]
30 AguardandoPagamento,
31 #[serde(rename = "6")]
32 Aprovada,
33 #[serde(rename = "7")]
34 Cancelada,
35 #[serde(rename = "89")]
36 Reprovada,
37
38 #[serde(rename = "24")]
41 Contestacao,
42 #[serde(rename = "89")]
43 Monitoring,
44 }
47
48#[derive(Validate, Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct YapayCustomer {
50 #[validate]
51 pub contacts: Vec<CustomerPhoneContact>,
52 #[validate]
53 pub addresses: Vec<CustomerAddress>,
54 #[validate(length(min = 1))]
55 pub name: String,
56 #[validate(regex = "REGEX_BIRTH_DATE")]
58 pub birth_date: String,
59 #[validate(length(equal = 11), custom = "crate::helpers::validate_cpf")]
61 pub cpf: String,
62 pub cnpj: Option<String>,
63 #[validate(email)]
64 pub email: String,
65}
66
67impl YapayCustomer {
68 pub fn new(
69 name: String,
70 cpf: String,
71 email: String,
72 birth_date: String,
73 phones: Vec<CustomerPhoneContact>,
74 address: Vec<CustomerAddress>,
75 ) -> Result<Self, InvalidError> {
76 let customer = Self {
77 contacts: phones,
78 addresses: address,
79 name,
80 birth_date,
81 cpf,
82 cnpj: None,
83 email,
84 };
85
86 match customer.validate() {
87 Ok(_) => Ok(customer),
88 Err(e) => Err(InvalidError::ValidatorLibError(e)),
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Deserialize)]
94pub struct CustomerResponse {
95 pub name: String,
96 pub company_name: String,
97 pub trade_name: String,
98 pub cnpj: String,
99}
100
101#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct CustomerPhoneContact {
103 pub type_contact: PhoneContactType,
104 #[validate(length(min = 8, max = 15))]
105 pub number_contact: String,
106}
107
108#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct CustomerAddress {
110 pub type_address: AddressType,
111 #[validate(length(max = 8))]
113 pub postal_code: String,
114 pub street: String,
115 pub number: String,
116 pub completion: String,
117 pub neighborhood: String,
118 pub city: String,
119 #[validate(length(equal = 2))]
121 pub state: String,
122}
123
124#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct YapayProduct {
126 pub code: String,
127 #[validate(length(max = 50))]
128 pub sku_code: String,
129 pub description: String,
130 pub quantity: String,
131 pub price_unit: String,
132 pub extra: Option<String>,
133}
134
135impl YapayProduct {
136 #[must_use]
137 pub fn new(
138 sku_or_code: String,
139 description: String,
140 quantity: NonZeroU8,
141 price_unit: f64,
142 ) -> Self {
143 Self {
144 code: sku_or_code.clone(),
145 sku_code: sku_or_code,
146 description,
147 quantity: quantity.get().to_string(),
148 price_unit: price_unit.to_string(),
149 extra: None,
150 }
151 }
152}
153
154#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub struct TransactionResponseCommon {}
156
157#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct YapayTransaction {
166 pub available_payment_methods: String,
167 #[validate(length(max = 20))]
170 pub order_number: Option<String>,
171 pub customer_ip: String,
172 pub shipping_type: Option<String>,
173 pub shipping_price: Option<String>,
174 pub price_discount: String,
175 pub url_notification: String,
177 pub free: String,
178}
179
180impl YapayTransaction {
181 pub fn online_goods(
186 order_number: String,
187 customer_ip: String,
188 available_payment_methods: Option<String>,
189 notification_url: Option<&str>,
190 ) -> Result<Self, SDKError> {
191 let methods = available_payment_methods
192 .unwrap_or_else(|| "2,3,4,5,6,7,14,15,16,18,19,21,22,23".to_string());
193
194 let transaction = Self {
195 available_payment_methods: methods,
196 order_number: Some(order_number),
197 customer_ip,
198 shipping_type: None,
199 shipping_price: None,
200 price_discount: "".to_string(),
201 url_notification: notification_url.unwrap_or("").to_string(),
202 free: "".to_string(),
203 };
204
205 if let Err(err) = transaction.validate() {
206 return Err(InvalidError::ValidatorLibError(err).into());
207 }
208 Ok(transaction)
209 }
210
211 pub fn physical_goods() {}
213}
214
215#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
216pub struct TransactionTrace {
217 pub estimated_date: String,
218}
219
220#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
222pub struct YaypaySavedCardData {
223 pub finger_print: String,
228
229 pub card_token: String,
233
234 #[validate(length(max = 4))]
235 pub card_cvv: String,
236 #[validate(length(min = 1, max = 2))]
237 pub split: String,
238}
239
240#[derive(Validate, Debug, Clone, PartialEq, Serialize, Deserialize)]
241#[validate(schema(function = "validate_card_exp"))]
242pub struct YapayCardData {
243 pub finger_print: String,
248 pub payment_method_id: PaymentCreditCard,
249 pub card_name: String,
250 pub card_number: String,
251
252 #[validate(length(equal = 2))]
254 pub card_expdate_month: String,
255
256 #[validate(length(equal = 4))]
258 pub card_expdate_year: String,
259
260 #[validate(length(max = 4))]
261 pub card_cvv: String,
262 #[validate(length(min = 1, max = 2))]
263 pub split: String,
264}
265
266impl YapayCardData {
267 pub fn new(
268 cc: PaymentCreditCard,
269 cc_owner_name: String,
270 cc_number: String,
271 cc_exp_mm: String,
272 cc_exp_yyyy: String,
273 cc_cvv: String,
274 installments: i8,
275 ) -> Result<Self, SDKError> {
276 let payment = Self {
277 finger_print: "".to_string(),
278 payment_method_id: cc,
279 card_name: cc_owner_name,
280 card_number: cc_number,
281 card_expdate_month: cc_exp_mm,
282 card_expdate_year: cc_exp_yyyy,
283 card_cvv: cc_cvv,
284 split: installments.to_string(),
285 };
286
287 if let Err(err) = payment.validate() {
288 Err(InvalidError::ValidatorLibError(err).into())
289 } else {
290 Ok(payment)
291 }
292 }
293}
294
295impl CanValidate for YapayCardData {}
296
297pub fn validate_card_exp(card_data: &YapayCardData) -> Result<(), validator::ValidationError> {
298 let now = OffsetDateTime::now_utc().date();
299 let res = validate_card_expiration(
300 now,
301 &*card_data.card_expdate_month,
302 &*card_data.card_expdate_year,
303 );
304
305 match res {
306 Ok(_) => Ok(()),
307 Err(err) => Err(validator::ValidationError {
308 code: Cow::from("exp_month and exp_year"),
309 message: Some(Cow::from(err.to_string())),
310 params: HashMap::default(),
311 }),
312 }
313}
314
315pub fn validate_card_expiration(
318 time_cmp: Date,
319 exp_month: &str,
320 exp_year: &str,
321) -> Result<(), SDKError> {
322 let card_expiration = Date::parse(
323 &*format!("01-{}-{}", exp_month, exp_year),
324 format_description!("[day]-[month]-[year]"),
325 )
326 .unwrap();
327
328 if card_expiration >= time_cmp {
329 Ok(())
330 } else {
331 Err(InvalidError::CreditCardExpired.into())
332 }
333}
334
335#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Debug)]
336pub enum AddressType {
337 #[serde(rename = "B")]
338 Cobranca,
339 #[serde(rename = "D")]
340 Entrega,
341}
342
343#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Debug, strum::Display)]
345pub enum PhoneContactType {
346 #[serde(rename = "H")]
347 Residencial,
348 #[serde(rename = "M")]
349 Celular,
350 #[serde(rename = "W")]
351 Comercial,
352}
353
354#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Debug)]
356pub enum PaymentType {
357 Card(PaymentCreditCard),
358 BankTransfer(PaymentOtherMethods),
359}
360
361#[derive(strum::Display, EnumIter, Copy, Clone, Deserialize, Serialize, PartialEq, Debug)]
362pub enum PaymentOtherMethods {
363 #[serde(rename = "6")]
364 #[strum(serialize = "6")]
365 Boleto,
366 #[serde(rename = "27")]
367 #[strum(serialize = "27")]
368 PIX,
369 #[serde(rename = "7")]
371 #[strum(serialize = "7")]
372 BankTransferItauShopline,
373 #[serde(rename = "23")]
375 #[strum(serialize = "23")]
376 BankTransferBB,
377}
378
379#[derive(strum::Display, EnumIter, Copy, Clone, Deserialize, Serialize, PartialEq, Debug)]
380pub enum PaymentCreditCard {
381 #[serde(rename = "3")]
382 #[strum(serialize = "3")]
383 Visa,
384 #[serde(rename = "4")]
385 #[strum(serialize = "4")]
386 MasterCard,
387 #[serde(rename = "5")]
388 #[strum(serialize = "5")]
389 Amex,
390 #[serde(rename = "16")]
391 #[strum(serialize = "16")]
392 Elo,
393 #[serde(rename = "20")]
394 #[strum(serialize = "20")]
395 HiperCard,
396 #[serde(rename = "25")]
397 #[strum(serialize = "25")]
398 HiperItau,
399}
400
401pub trait AsPaymentMethod: Display + IntoEnumIterator {
403 fn payment_methods_all() -> String {
404 format_available_payment_method(&<Self as IntoEnumIterator>::iter().collect::<Vec<_>>())
405 }
406}
407impl AsPaymentMethod for PaymentOtherMethods {}
408impl AsPaymentMethod for PaymentCreditCard {}
409
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411pub struct Card {
412 pub first_six_digits: String,
413 pub last_four_digits: String,
414 pub expiration_month: i64,
415 pub expiration_year: i64,
416
417 pub card_number_length: i64,
418 pub security_code_length: i64,
419
420 #[serde(with = "time::serde::rfc3339")]
421 pub date_created: OffsetDateTime,
422 #[serde(with = "time::serde::rfc3339")]
423 pub date_last_updated: OffsetDateTime,
424 #[serde(with = "time::serde::rfc3339")]
425 pub date_due: OffsetDateTime,
426}
427
428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
429pub struct ResponseRoot<T> {
430 pub message_response: ResponseMessage,
431 pub data_response: T,
432}
433
434#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
435pub struct ResponseMessage {
436 pub message: String,
437}
438
439#[cfg(test)]
440mod tests {
441 use time::macros::format_description;
442 use time::Date;
443
444 use crate::common_types::{
445 validate_card_expiration, AsPaymentMethod, PaymentCreditCard, PaymentOtherMethods,
446 };
447 use crate::helpers::format_available_payment_method;
448
449 #[test]
450 fn cc_valid_date() {
451 let fmt = format_description!("[year]/[month padding:zero]/[day]");
452 let datetime = Date::parse("2022/05/01", &fmt).unwrap();
453
454 let res = validate_card_expiration(datetime, "05", "2022");
455 assert!(res.is_ok());
456 }
457
458 #[test]
459 fn cc_invalid_date() {
460 let fmt = format_description!("[year]/[month padding:zero]/[day]");
461 let datetime = Date::parse("2022/05/01", &fmt).unwrap();
462
463 let res = validate_card_expiration(datetime, "06", "2021");
464 assert!(res.is_err());
465 }
466
467 #[test]
468 fn t_cc_methods() {
469 let res = PaymentCreditCard::payment_methods_all();
470 assert_eq!(res, "3,4,5,16,20,25".to_string());
471 }
472
473 #[test]
474 fn t_specific_methods() {
475 let res = format_available_payment_method(&[
476 PaymentOtherMethods::Boleto,
477 PaymentOtherMethods::BankTransferBB,
478 ]);
479 assert_eq!(res, "6,23".to_string());
480 }
481}