use std::marker::PhantomData;
use crate::error::FacilitatorError;
use crate::facilitator::Facilitator;
use crate::wire::{SettleRequest, SettleResponse, VerifyRequest, VerifyResponse};
#[derive(Debug, Clone, Copy, Default)]
pub struct Unverified;
#[derive(Debug, Clone, Copy, Default)]
pub struct Verified;
#[derive(Debug, Clone, Copy, Default)]
pub struct Settled;
#[derive(Debug, Clone)]
pub struct Payment<S> {
request: VerifyRequest,
state: PhantomData<fn() -> S>,
}
impl<S> Payment<S> {
#[must_use]
pub const fn request(&self) -> &VerifyRequest {
&self.request
}
}
impl Payment<Unverified> {
#[must_use]
pub const fn new(request: VerifyRequest) -> Self {
Self {
request,
state: PhantomData,
}
}
pub async fn verify<F: Facilitator>(
self,
facilitator: &F,
request: VerifyRequest,
) -> Result<(Payment<Verified>, VerifyResponse), FacilitatorError> {
let response = facilitator.verify(request).await?;
match &response {
VerifyResponse::Valid { .. } => Ok((
Payment {
request: self.request,
state: PhantomData,
},
response,
)),
VerifyResponse::Invalid {
reason, message, ..
} => Err(FacilitatorError::Verification(
crate::error::VerificationError::InvalidFormat(format!(
"{reason}: {}",
message.as_deref().unwrap_or(""),
)),
)),
}
}
}
impl Payment<Verified> {
pub async fn settle<F: Facilitator>(
self,
facilitator: &F,
request: SettleRequest,
) -> Result<(Payment<Settled>, SettleResponse), FacilitatorError> {
let response = facilitator.settle(request).await?;
match &response {
SettleResponse::Success { .. } => Ok((
Payment {
request: self.request,
state: PhantomData,
},
response,
)),
SettleResponse::Failure { message, .. } => Err(FacilitatorError::Onchain(
message.as_deref().unwrap_or("").to_owned(),
)),
}
}
}
impl Payment<Settled> {
#[must_use]
pub fn into_request(self) -> VerifyRequest {
self.request
}
}
#[cfg(test)]
mod tests {
use std::future::Future;
use super::*;
use crate::error_reason::ErrorReason;
use crate::wire::{Extensions, SettleResponse, SupportedResponse, VerifyResponse};
struct AlwaysValid;
impl Facilitator for AlwaysValid {
fn verify(
&self,
_request: VerifyRequest,
) -> impl Future<Output = Result<VerifyResponse, FacilitatorError>> + Send {
std::future::ready(Ok(VerifyResponse::valid("0xPAYER")))
}
fn settle(
&self,
_request: SettleRequest,
) -> impl Future<Output = Result<SettleResponse, FacilitatorError>> + Send {
std::future::ready(Ok(SettleResponse::Success {
payer: "0xPAYER".into(),
transaction: "0xTX".into(),
network: "eip155:1".into(),
amount: Some("1000000".into()),
extensions: Extensions::new(),
}))
}
fn supported(
&self,
) -> impl Future<Output = Result<SupportedResponse, FacilitatorError>> + Send {
std::future::ready(Ok(SupportedResponse::default()))
}
}
struct AlwaysInvalid;
impl Facilitator for AlwaysInvalid {
fn verify(
&self,
_request: VerifyRequest,
) -> impl Future<Output = Result<VerifyResponse, FacilitatorError>> + Send {
std::future::ready(Ok(VerifyResponse::invalid(
None,
ErrorReason::InvalidPayload,
)))
}
fn settle(
&self,
_request: SettleRequest,
) -> impl Future<Output = Result<SettleResponse, FacilitatorError>> + Send {
std::future::ready(Err(FacilitatorError::Onchain("unreachable".into())))
}
fn supported(
&self,
) -> impl Future<Output = Result<SupportedResponse, FacilitatorError>> + Send {
std::future::ready(Ok(SupportedResponse::default()))
}
}
fn dummy_request() -> VerifyRequest {
serde_json::json!({}).into()
}
#[tokio::test]
async fn verify_then_settle_succeeds() {
let facilitator = AlwaysValid;
let payment = Payment::new(dummy_request());
let (verified, verify_resp) = payment.verify(&facilitator, dummy_request()).await.unwrap();
assert!(verify_resp.is_valid());
let (_settled, settle_resp) = verified
.settle(&facilitator, dummy_request().into())
.await
.unwrap();
assert!(settle_resp.is_success());
}
#[tokio::test]
async fn verify_invalid_propagates() {
let facilitator = AlwaysInvalid;
let payment = Payment::new(dummy_request());
let err = payment
.verify(&facilitator, dummy_request())
.await
.unwrap_err();
assert!(matches!(err, FacilitatorError::Verification(_)));
}
}