yapay_sdk_rust/
webhooks.rs

1//! `YapayWebhook` is the one you'll want to deserialize the POST requests yapay send to your
2//! server.
3//!
4//! Don't forget to return http 200.
5//!
6//!
7//!
8//! `YapayWebhook` implements `From<Vec<u8>>`. You can use it on raw payloads sent to your
9//! `actix_web` server, for example.
10
11use serde::{Deserialize, Serialize};
12use serde_qs::Config;
13
14use crate::common_types::{YapayTransactionStatus, CLEAN_WEBHOOK_REGEX};
15
16/// The notification Yapay POSTs into your server when a transaction is created.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct YapayWebhook {
19    pub token_transaction: String,
20    pub transaction: WebhookTransaction,
21}
22
23impl TryFrom<Vec<u8>> for YapayWebhook {
24    type Error = ();
25
26    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
27        let value_as_string = String::from_utf8(value).map_err(|_| ())?;
28
29        let cfg = Config::new(10, false);
30        cfg.deserialize_bytes::<Self>(clean_non_indexed(&*value_as_string).as_ref())
31            .map_err(|_| ())
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct WebhookTransaction {
37    pub order_number: String,
38    pub free: String,
39    pub transaction_id: String,
40    pub status_name: String,
41    pub status_id: YapayTransactionStatus,
42    pub date_transaction: String,
43    pub split: String,
44    pub price_payment: String,
45    pub date_payment: String,
46    pub seller_token: String,
47    pub transaction_token: String,
48    pub token_transaction: String,
49    pub price_seller: String,
50    pub price_original: String,
51    pub price_additional: String,
52    pub price_discount: String,
53    pub shipping_price: String,
54    pub shipping_type: String,
55    pub payment_method_id: String,
56    pub payment_method_name: String,
57    pub customer: Customer,
58    pub company: Company,
59    pub payment: Payment,
60    // Broken due to non-indexed
61    // pub products: Vec<Product>,
62}
63
64#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct Product {
66    pub code: Option<String>,
67    pub description: Option<String>,
68    pub extra: Option<String>,
69    pub price_unit: Option<String>,
70    pub quantity: Option<String>,
71}
72
73#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct Customer {
75    pub name: String,
76    pub cpf: String,
77    pub cnpj: String,
78    pub email: String,
79    pub token: String,
80    pub address: Address,
81}
82
83#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Address {
85    pub street: String,
86    pub number: String,
87    pub neighborhood: String,
88    pub postal_code: String,
89    pub completion: String,
90    pub city: String,
91    pub state: String,
92}
93
94#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct Company {
96    pub name: String,
97    pub cnpj: String,
98    pub cpf: String,
99    pub contact: String,
100    pub url: String,
101    pub token: String,
102}
103
104#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct Payment {
106    pub price_payment: String,
107    pub payment_response: String,
108    pub payment_response_code: String,
109    pub url_payment: String,
110    pub date_approval: String,
111    pub selling_message: String,
112    pub number_of_voucher_sales: String,
113    pub split: String,
114    pub date_payment: String,
115    pub payment_method_id: String,
116    pub payment_method_name: String,
117    pub card_id: String,
118    pub number_proccess: String,
119}
120
121pub fn clean_non_indexed(raw: &str) -> String {
122    // let l_idx = raw.find("&transaction[products][]").unwrap();
123    // let r_idx = raw.rfind("&transaction[products][]").unwrap();
124
125    let mut locs = vec![];
126    for needle in CLEAN_WEBHOOK_REGEX.find_iter(raw) {
127        let start = needle.start();
128        let finish = needle.end();
129        locs.push((start, finish));
130    }
131
132    let first_idx = locs[0].0 + 1;
133    let last_idx = locs.last().map(|&(_, l)| l).unwrap();
134
135    let first_chunk = &raw[..first_idx];
136    let last_chunk = &raw[last_idx..];
137
138    first_chunk.to_string() + last_chunk
139}
140
141#[cfg(test)]
142mod tests {
143    use serde_qs::Config;
144
145    use super::*;
146
147    // Just a heads-up.
148    // Yapay posts the webhook with broken indexing on `products` field.
149    // So we just clean it up... don't forget it!
150
151    // Raw body of a POST received from Yapay after cleaning up non-indexed array
152    const fn post_cleaned() -> &'static str {
153        r#"token_transaction=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[order_number]=c2357843-db24-4a06-b&transaction[free]=&transaction[transaction_id]=670863&transaction[status_name]=Aprovada&transaction[status_id]=6&transaction[date_transaction]=2022-05-24T23%3A09%3A21&transaction[split]=3&transaction[price_payment]=104.01&transaction[date_payment]=2022-05-24T23%3A09%3A21&transaction[seller_token]=73047784b36c659&transaction[transaction_token]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[token_transaction]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[price_seller]=95.51&transaction[price_original]=100.0&transaction[price_additional]=0.0&transaction[price_discount]=0.0&transaction[shipping_price]=0&transaction[shipping_type]=&transaction[payment_method_id]=4&transaction[payment_method_name]=Mastercard&transaction[customer][name]=Rufino%20Beltrano&transaction[customer][cpf]=41810420814&transaction[customer][cnpj]=&transaction[customer][email]=saskenuba%40gmail.com&transaction[customer][token]=63db358c1adbc36&transaction[company][name]=Yapay%20Sandbox&transaction[company][cnpj]=&transaction[company][cpf]=90357966678&transaction[company][contact]=1112312312&transaction[company][url]=&transaction[company][token]=73047784b36c659&transaction[payment][price_payment]=104.01&transaction[payment][payment_response]=&transaction[payment][payment_response_code]=&transaction[payment][url_payment]=&transaction[payment][date_approval]=24%2F05%2F2022%20-%2023%3A09%3A21&transaction[payment][selling_message]=Mensagem%20de%20venda%20fake&transaction[payment][number_of_voucher_sales]=03076032815324372004&transaction[payment][split]=3&transaction[payment][date_payment]=24%2F05%2F2022&transaction[payment][payment_method_id]=4&transaction[payment][payment_method_name]=Mastercard&transaction[payment][card_id]=64296&transaction[payment][number_proccess]=705036&transaction[customer][address][street]=Av%20Bartholomeu%20de%20carlos&transaction[customer][address][number]=901&transaction[customer][address][neighborhood]=Jardim%20Flor%20da%20Montanha&transaction[customer][address][postal_code]=07097420&transaction[customer][address][completion]=&transaction[customer][address][city]=Guarulhos&transaction[customer][address][state]=SP"#
154    }
155
156    // Raw body of a POST received from Yapay
157    const fn post_not_cleaned() -> &'static str {
158        r#"token_transaction=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[order_number]=c2357843-db24-4a06-b&transaction[free]=&transaction[transaction_id]=670863&transaction[status_name]=Aprovada&transaction[status_id]=6&transaction[date_transaction]=2022-05-24T23%3A09%3A21&transaction[split]=3&transaction[price_payment]=104.01&transaction[date_payment]=2022-05-24T23%3A09%3A21&transaction[seller_token]=73047784b36c659&transaction[transaction_token]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[token_transaction]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[price_seller]=95.51&transaction[price_original]=100.0&transaction[price_additional]=0.0&transaction[price_discount]=0.0&transaction[shipping_price]=0&transaction[shipping_type]=&transaction[payment_method_id]=4&transaction[payment_method_name]=Mastercard&transaction[products][][code]=100brl&transaction[products][][description]=100%20reais%20de%20cr%C3%A9dito%20na%20carteira%20MercadoSkin.&transaction[products][][extra]=&transaction[products][][price_unit]=100.0&transaction[products][][quantity]=1.0&transaction[customer][name]=Rufino%20Beltrano&transaction[customer][cpf]=41810420814&transaction[customer][cnpj]=&transaction[customer][email]=saskenuba%40gmail.com&transaction[customer][token]=63db358c1adbc36&transaction[company][name]=Yapay%20Sandbox&transaction[company][cnpj]=&transaction[company][cpf]=90357966678&transaction[company][contact]=1112312312&transaction[company][url]=&transaction[company][token]=73047784b36c659&transaction[payment][price_payment]=104.01&transaction[payment][payment_response]=&transaction[payment][payment_response_code]=&transaction[payment][url_payment]=&transaction[payment][date_approval]=24%2F05%2F2022%20-%2023%3A09%3A21&transaction[payment][selling_message]=Mensagem%20de%20venda%20fake&transaction[payment][number_of_voucher_sales]=03076032815324372004&transaction[payment][split]=3&transaction[payment][date_payment]=24%2F05%2F2022&transaction[payment][payment_method_id]=4&transaction[payment][payment_method_name]=Mastercard&transaction[payment][card_id]=64296&transaction[payment][number_proccess]=705036&transaction[customer][address][street]=Av%20Bartholomeu%20de%20carlos&transaction[customer][address][number]=901&transaction[customer][address][neighborhood]=Jardim%20Flor%20da%20Montanha&transaction[customer][address][postal_code]=07097420&transaction[customer][address][completion]=&transaction[customer][address][city]=Guarulhos&transaction[customer][address][state]=SP"#
159    }
160
161    // Raw body of a POST received from Yapay as bytes
162    const fn post_not_cleaned_bytes() -> &'static [u8] {
163        b"token_transaction=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[order_number]=c2357843-db24-4a06-b&transaction[free]=&transaction[transaction_id]=670863&transaction[status_name]=Aprovada&transaction[status_id]=6&transaction[date_transaction]=2022-05-24T23%3A09%3A21&transaction[split]=3&transaction[price_payment]=104.01&transaction[date_payment]=2022-05-24T23%3A09%3A21&transaction[seller_token]=73047784b36c659&transaction[transaction_token]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[token_transaction]=938ad7ab4b1c6bb227f03b1b0fe08f67&transaction[price_seller]=95.51&transaction[price_original]=100.0&transaction[price_additional]=0.0&transaction[price_discount]=0.0&transaction[shipping_price]=0&transaction[shipping_type]=&transaction[payment_method_id]=4&transaction[payment_method_name]=Mastercard&transaction[products][][code]=100brl&transaction[products][][description]=100%20reais%20de%20cr%C3%A9dito%20na%20carteira%20MercadoSkin.&transaction[products][][extra]=&transaction[products][][price_unit]=100.0&transaction[products][][quantity]=1.0&transaction[customer][name]=Rufino%20Beltrano&transaction[customer][cpf]=41810420814&transaction[customer][cnpj]=&transaction[customer][email]=saskenuba%40gmail.com&transaction[customer][token]=63db358c1adbc36&transaction[company][name]=Yapay%20Sandbox&transaction[company][cnpj]=&transaction[company][cpf]=90357966678&transaction[company][contact]=1112312312&transaction[company][url]=&transaction[company][token]=73047784b36c659&transaction[payment][price_payment]=104.01&transaction[payment][payment_response]=&transaction[payment][payment_response_code]=&transaction[payment][url_payment]=&transaction[payment][date_approval]=24%2F05%2F2022%20-%2023%3A09%3A21&transaction[payment][selling_message]=Mensagem%20de%20venda%20fake&transaction[payment][number_of_voucher_sales]=03076032815324372004&transaction[payment][split]=3&transaction[payment][date_payment]=24%2F05%2F2022&transaction[payment][payment_method_id]=4&transaction[payment][payment_method_name]=Mastercard&transaction[payment][card_id]=64296&transaction[payment][number_proccess]=705036&transaction[customer][address][street]=Av%20Bartholomeu%20de%20carlos&transaction[customer][address][number]=901&transaction[customer][address][neighborhood]=Jardim%20Flor%20da%20Montanha&transaction[customer][address][postal_code]=07097420&transaction[customer][address][completion]=&transaction[customer][address][city]=Guarulhos&transaction[customer][address][state]=SP".as_slice()
164    }
165
166    #[test]
167    fn t_clean_post() {
168        assert_eq!(post_cleaned(), clean_non_indexed(post_not_cleaned()));
169    }
170
171    #[test]
172    fn t_notification() {
173        let cfg = Config::new(10, false);
174        let result = cfg
175            .deserialize_str::<YapayWebhook>(&*clean_non_indexed(post_not_cleaned()))
176            .unwrap();
177
178        eprintln!("result = {:#?}", result);
179    }
180
181    #[test]
182    fn t_notification_bytes() {
183        let de_res = YapayWebhook::try_from(Vec::from(post_not_cleaned_bytes()));
184        assert!(de_res.is_ok());
185    }
186}