//! `YapayWebhook` is the one you'll want to deserialize the POST requests yapay send to your
//! server.
//!
//! Don't forget to return http 200.
//!
//!
//!
//! `YapayWebhook` implements `From<Vec<u8>>`. You can use it on raw payloads sent to your
//! `actix_web` server, for example.
use serde::{Deserialize, Serialize};
use serde_qs::Config;
use crate::common_types::{YapayTransactionStatus, CLEAN_WEBHOOK_REGEX};
/// The notification Yapay POSTs into your server when a transaction is created.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct YapayWebhook {
pub token_transaction: String,
pub transaction: WebhookTransaction,
}
impl TryFrom<Vec<u8>> for YapayWebhook {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let value_as_string = String::from_utf8(value).map_err(|_| ())?;
let cfg = Config::new(10, false);
cfg.deserialize_bytes::<Self>(clean_non_indexed(&*value_as_string).as_ref())
.map_err(|_| ())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WebhookTransaction {
pub order_number: String,
pub free: String,
pub transaction_id: String,
pub status_name: String,
pub status_id: YapayTransactionStatus,
pub date_transaction: String,
pub split: String,
pub price_payment: String,
pub date_payment: String,
pub seller_token: String,
pub transaction_token: String,
pub token_transaction: String,
pub price_seller: String,
pub price_original: String,
pub price_additional: String,
pub price_discount: String,
pub shipping_price: String,
pub shipping_type: String,
pub payment_method_id: String,
pub payment_method_name: String,
pub customer: Customer,
pub company: Company,
pub payment: Payment,
// Broken due to non-indexed
// pub products: Vec<Product>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Product {
pub code: Option<String>,
pub description: Option<String>,
pub extra: Option<String>,
pub price_unit: Option<String>,
pub quantity: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Customer {
pub name: String,
pub cpf: String,
pub cnpj: String,
pub email: String,
pub token: String,
pub address: Address,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Address {
pub street: String,
pub number: String,
pub neighborhood: String,
pub postal_code: String,
pub completion: String,
pub city: String,
pub state: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Company {
pub name: String,
pub cnpj: String,
pub cpf: String,
pub contact: String,
pub url: String,
pub token: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Payment {
pub price_payment: String,
pub payment_response: String,
pub payment_response_code: String,
pub url_payment: String,
pub date_approval: String,
pub selling_message: String,
pub number_of_voucher_sales: String,
pub split: String,
pub date_payment: String,
pub payment_method_id: String,
pub payment_method_name: String,
pub card_id: String,
pub number_proccess: String,
}
pub fn clean_non_indexed(raw: &str) -> String {
// let l_idx = raw.find("&transaction[products][]").unwrap();
// let r_idx = raw.rfind("&transaction[products][]").unwrap();
let mut locs = vec![];
for needle in CLEAN_WEBHOOK_REGEX.find_iter(raw) {
let start = needle.start();
let finish = needle.end();
locs.push((start, finish));
}
let first_idx = locs[0].0 + 1;
let last_idx = locs.last().map(|&(_, l)| l).unwrap();
let first_chunk = &raw[..first_idx];
let last_chunk = &raw[last_idx..];
first_chunk.to_string() + last_chunk
}
#[cfg(test)]
mod tests {
use serde_qs::Config;
use super::*;
// Just a heads-up.
// Yapay posts the webhook with broken indexing on `products` field.
// So we just clean it up... don't forget it!
// Raw body of a POST received from Yapay after cleaning up non-indexed array
const fn post_cleaned() -> &'static str {
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"#
}
// Raw body of a POST received from Yapay
const fn post_not_cleaned() -> &'static str {
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"#
}
// Raw body of a POST received from Yapay as bytes
const fn post_not_cleaned_bytes() -> &'static [u8] {
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()
}
#[test]
fn t_clean_post() {
assert_eq!(post_cleaned(), clean_non_indexed(post_not_cleaned()));
}
#[test]
fn t_notification() {
let cfg = Config::new(10, false);
let result = cfg
.deserialize_str::<YapayWebhook>(&*clean_non_indexed(post_not_cleaned()))
.unwrap();
eprintln!("result = {:#?}", result);
}
#[test]
fn t_notification_bytes() {
let de_res = YapayWebhook::try_from(Vec::from(post_not_cleaned_bytes()));
assert!(de_res.is_ok());
}
}