use crate::gateway::Gateway;
use crate::webhook::{WebhookEvent, WebhookLog};
use rustlavel_core::{Json, Result};
use rustlavel_http::{BoxFuture, Request, Response, Status};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Handled {
Processed,
Duplicate,
Refused,
HandlerFailed(String),
}
pub type Handler =
Arc<dyn Fn(WebhookEvent) -> BoxFuture<Result<()>> + Send + Sync + 'static>;
pub struct Receiver {
gateway: Arc<dyn Gateway>,
log: Arc<dyn WebhookLog>,
handler: Handler,
}
impl Receiver {
pub fn new(
gateway: Arc<dyn Gateway>,
log: Arc<dyn WebhookLog>,
handler: impl Fn(WebhookEvent) -> BoxFuture<Result<()>> + Send + Sync + 'static,
) -> Receiver {
Receiver { gateway, log, handler: Arc::new(handler) }
}
pub async fn deliver(&self, headers: &rustlavel_http::Headers, body: &[u8]) -> Handled {
let event = match self.gateway.verify_webhook(headers, body) {
Ok(event) => event,
Err(error) => {
rustlavel_core::warn!(
"payment: refused a webhook for {}: {error}",
self.gateway.name()
);
return Handled::Refused;
}
};
match self.log.record(&event).await {
Ok(true) => {}
Ok(false) => {
rustlavel_core::debug!("payment: duplicate {} ignored", event.dedup_key());
return Handled::Duplicate;
}
Err(error) => {
return Handled::HandlerFailed(format!("the webhook log failed: {error}"));
}
}
let key = event.dedup_key();
match (self.handler)(event).await {
Ok(()) => Handled::Processed,
Err(error) => {
rustlavel_core::error!("payment: the handler for {key} failed: {error}");
if let Err(inner) = self.log.withdraw(&key).await {
rustlavel_core::error!(
"payment: could not withdraw {key} after a failed handler; the gateway's \
retry will be treated as a duplicate: {inner}"
);
}
Handled::HandlerFailed(error.to_string())
}
}
}
pub async fn handle(&self, request: Request) -> Response {
match self.deliver(request.headers(), request.body()).await {
Handled::Processed | Handled::Duplicate => Response::ok(),
Handled::Refused => Response::new(Status::UNAUTHORIZED)
.with_json(Json::object([("message", Json::from("the signature did not verify"))])),
Handled::HandlerFailed(_) => Response::new(Status::INTERNAL_ERROR),
}
}
}