use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::pin::Pin;
use r402_protocol::{PaymentRequired, SettleResponse};
use crate::register::PaymentClient;
use crate::select::PaymentSelector;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum HookDecision {
Continue,
Abort {
reason: String,
message: String,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FailureRecovery<T> {
Propagate,
Recovered(T),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PaymentCreationContext {
pub payment_required: PaymentRequired,
}
impl PaymentCreationContext {
#[must_use]
pub const fn new(payment_required: PaymentRequired) -> Self {
Self { payment_required }
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct CreatedPayment {
pub signed_payload: String,
pub payment_required: PaymentRequired,
}
impl CreatedPayment {
#[must_use]
pub fn new(signed_payload: impl Into<String>, payment_required: PaymentRequired) -> Self {
Self {
signed_payload: signed_payload.into(),
payment_required,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PaymentResponseContext {
pub payment_required: PaymentRequired,
pub signed_payload: String,
pub settle_response: Option<SettleResponse>,
pub corrective_payment_required: Option<PaymentRequired>,
}
impl PaymentResponseContext {
#[must_use]
pub fn new(payment_required: PaymentRequired, signed_payload: impl Into<String>) -> Self {
Self {
payment_required,
signed_payload: signed_payload.into(),
settle_response: None,
corrective_payment_required: None,
}
}
#[must_use]
pub fn with_settle_response(mut self, settle: SettleResponse) -> Self {
self.settle_response = Some(settle);
self
}
#[must_use]
pub fn with_corrective_payment_required(mut self, required: PaymentRequired) -> Self {
self.corrective_payment_required = Some(required);
self
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct PaymentResponseResult {
pub recovered: bool,
}
impl PaymentResponseResult {
#[must_use]
pub const fn continue_() -> Self {
Self { recovered: false }
}
#[must_use]
pub const fn recovered() -> Self {
Self { recovered: true }
}
}
pub trait ClientHooks: Send + Sync {
fn before_payment_creation<'a>(
&'a self,
_ctx: &'a PaymentCreationContext,
) -> impl Future<Output = HookDecision> + Send + 'a {
async { HookDecision::Continue }
}
fn after_payment_creation<'a>(
&'a self,
_ctx: &'a PaymentCreationContext,
_created: &'a CreatedPayment,
) -> impl Future<Output = ()> + Send + 'a {
async {}
}
fn on_payment_creation_failure<'a>(
&'a self,
_ctx: &'a PaymentCreationContext,
_error: &'a str,
) -> impl Future<Output = FailureRecovery<CreatedPayment>> + Send + 'a {
async { FailureRecovery::Propagate }
}
fn on_payment_response<'a>(
&'a self,
_ctx: &'a PaymentResponseContext,
) -> impl Future<Output = PaymentResponseResult> + Send + 'a {
async { PaymentResponseResult::continue_() }
}
}
pub trait DynClientHooks: Send + Sync {
fn before_payment_creation<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
) -> Pin<Box<dyn Future<Output = HookDecision> + Send + 'a>>;
fn after_payment_creation<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
created: &'a CreatedPayment,
) -> BoxFuture<'a, ()>;
fn on_payment_creation_failure<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
error: &'a str,
) -> BoxFuture<'a, FailureRecovery<CreatedPayment>>;
fn on_payment_response<'a>(
&'a self,
ctx: &'a PaymentResponseContext,
) -> Pin<Box<dyn Future<Output = PaymentResponseResult> + Send + 'a>>;
}
impl<T: ClientHooks + ?Sized> DynClientHooks for T {
fn before_payment_creation<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
) -> Pin<Box<dyn Future<Output = HookDecision> + Send + 'a>> {
Box::pin(<Self as ClientHooks>::before_payment_creation(self, ctx))
}
fn after_payment_creation<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
created: &'a CreatedPayment,
) -> BoxFuture<'a, ()> {
Box::pin(<Self as ClientHooks>::after_payment_creation(
self, ctx, created,
))
}
fn on_payment_creation_failure<'a>(
&'a self,
ctx: &'a PaymentCreationContext,
error: &'a str,
) -> BoxFuture<'a, FailureRecovery<CreatedPayment>> {
Box::pin(<Self as ClientHooks>::on_payment_creation_failure(
self, ctx, error,
))
}
fn on_payment_response<'a>(
&'a self,
ctx: &'a PaymentResponseContext,
) -> Pin<Box<dyn Future<Output = PaymentResponseResult> + Send + 'a>> {
Box::pin(<Self as ClientHooks>::on_payment_response(self, ctx))
}
}
impl Debug for dyn DynClientHooks {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("DynClientHooks")
}
}
impl<S: PaymentSelector> PaymentClient<S> {
pub async fn handle_payment_response(
&self,
ctx: &PaymentResponseContext,
) -> PaymentResponseResult {
let mut recovered = false;
for hook in &self.hooks {
let result = hook.on_payment_response(ctx).await;
if result.recovered {
recovered = true;
}
}
if recovered {
PaymentResponseResult::recovered()
} else {
PaymentResponseResult::continue_()
}
}
}