use std::error::Error;
use kasapay::paytr::{Config, Credentials, PayTr};
use kasapay::{Delivery, ErrorKind, Event, EventKind, IdSource, Webhook};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let credentials = Credentials::new("merchant-1", "merchant-key", "merchant-salt");
let paytr = PayTr::new(Config::at("https://www.paytr.com", credentials.clone())?)?;
let handler: &dyn Webhook = &paytr;
let notice = |order: &str, status: &str, total: &str, hash: &str| {
format!(
"merchant_oid={order}&status={status}&total_amount={total}\
&hash={}&payment_type=card¤cy=TL",
urlencoded(hash)
)
};
let signed = |order: &str, status: &str, total: &str| {
notice(
order,
status,
total,
&credentials.callback_hash(order, status, total),
)
};
let deliveries = [
signed("ord-1", "success", "14990"),
signed("ord-2", "pending", "14990"),
notice(
"ord-3",
"success",
"149900",
&credentials.callback_hash("ord-3", "success", "14990"),
),
];
for body in &deliveries {
let delivery = Delivery::new(&[], body.as_bytes());
let reply = answer(handler.verify(&delivery).await);
println!("answered {reply}\n");
}
Ok(())
}
fn answer(verified: Result<Event, kasapay::Error>) -> &'static str {
match verified {
Ok(event) => {
match &event.kind {
EventKind::Captured => println!("ship {}", show(&event)),
EventKind::Failed => println!("do not ship {}", show(&event)),
other => println!("noted {other:?} for {}", show(&event)),
}
if event.amount.is_none()
&& let Some(total) = event.raw.text_at("/total_amount")
{
println!(" the notice says {total}, in a currency it did not sign");
}
"OK"
}
Err(error) if error.kind() == ErrorKind::Untrusted => {
println!("not acted on: {error}");
"OK"
}
Err(error) => {
println!("not read: {error}");
"OK"
}
}
}
fn show(event: &Event) -> String {
let payment = event
.payment
.as_ref()
.map_or_else(|| "an unnamed payment".to_owned(), ToString::to_string);
match event.id.source() {
IdSource::Provider => format!("{payment} (event {}, the provider's own)", event.id),
IdSource::Derived(fields) => {
format!("{payment} (event {}, composed from {fields:?})", event.id)
}
}
}
fn urlencoded(value: &str) -> String {
value
.bytes()
.map(|byte| match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
char::from(byte).to_string()
}
other => format!("%{other:02X}"),
})
.collect()
}