use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::pin::Pin;
use crate::error::FacilitatorError;
use crate::facilitator::BoxFuture;
use crate::facilitator::FailureRecovery;
use crate::wire::{PaymentPayload, PaymentRequirements, SettleResponse, VerifyResponse};
pub type WirePaymentPayload = PaymentPayload<PaymentRequirements, serde_json::Value>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CancelReason {
HandlerThrew,
HandlerFailed,
AfterVerifyAborted,
}
impl CancelReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::HandlerThrew => "handler_threw",
Self::HandlerFailed => "handler_failed",
Self::AfterVerifyAborted => "after_verify_aborted",
}
}
}
impl fmt::Display for CancelReason {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum BeforeOpDecision<T> {
Continue,
Abort {
reason: String,
message: String,
},
Skip {
result: T,
},
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct SkipHandlerDirective {
pub content_type: Option<String>,
pub body: Option<serde_json::Value>,
}
impl SkipHandlerDirective {
#[must_use]
pub const fn empty() -> Self {
Self {
content_type: None,
body: None,
}
}
#[must_use]
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
#[must_use]
pub fn with_body(mut self, body: serde_json::Value) -> Self {
self.body = Some(body);
self
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub enum AfterVerifyDecision {
#[default]
Continue,
Abort {
reason: String,
message: String,
},
SkipHandler {
response: SkipHandlerDirective,
},
}
#[derive(Clone)]
#[non_exhaustive]
pub struct PaymentHookContext {
pub payload: WirePaymentPayload,
pub requirements: PaymentRequirements,
}
impl Debug for PaymentHookContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentHookContext")
.field("requirements", &self.requirements)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct VerifyResultContext {
pub payment: PaymentHookContext,
pub result: VerifyResponse,
}
impl Debug for VerifyResultContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("VerifyResultContext")
.field("payment", &self.payment)
.field("result_valid", &self.result.is_valid())
.finish_non_exhaustive()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct SettleResultContext {
pub payment: PaymentHookContext,
pub result: SettleResponse,
}
impl Debug for SettleResultContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SettleResultContext")
.field("payment", &self.payment)
.field("result_success", &self.result.is_success())
.finish_non_exhaustive()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct VerifiedPaymentCanceledContext {
pub payment: PaymentHookContext,
pub reason: CancelReason,
pub error: Option<String>,
pub response_status: Option<u16>,
}
impl Debug for VerifiedPaymentCanceledContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("VerifiedPaymentCanceledContext")
.field("reason", &self.reason)
.field("response_status", &self.response_status)
.finish_non_exhaustive()
}
}
pub trait ResourceServerHooks: Send + Sync {
fn before_verify<'a>(
&'a self,
_ctx: &'a PaymentHookContext,
) -> impl Future<Output = BeforeOpDecision<VerifyResponse>> + Send + 'a {
async { BeforeOpDecision::Continue }
}
fn after_verify<'a>(
&'a self,
_ctx: &'a VerifyResultContext,
) -> impl Future<Output = AfterVerifyDecision> + Send + 'a {
async { AfterVerifyDecision::Continue }
}
fn on_verify_failure<'a>(
&'a self,
_ctx: &'a PaymentHookContext,
_error: &'a FacilitatorError,
) -> impl Future<Output = FailureRecovery<VerifyResponse>> + Send + 'a {
async { FailureRecovery::Propagate }
}
fn before_settle<'a>(
&'a self,
_ctx: &'a PaymentHookContext,
) -> impl Future<Output = BeforeOpDecision<SettleResponse>> + Send + 'a {
async { BeforeOpDecision::Continue }
}
fn after_settle<'a>(
&'a self,
_ctx: &'a SettleResultContext,
) -> impl Future<Output = ()> + Send + 'a {
async {}
}
fn on_settle_failure<'a>(
&'a self,
_ctx: &'a PaymentHookContext,
_error: &'a FacilitatorError,
) -> impl Future<Output = FailureRecovery<SettleResponse>> + Send + 'a {
async { FailureRecovery::Propagate }
}
fn on_verified_payment_canceled<'a>(
&'a self,
_ctx: &'a VerifiedPaymentCanceledContext,
) -> impl Future<Output = ()> + Send + 'a {
async {}
}
}
pub trait DynResourceServerHooks: Send + Sync {
fn before_verify<'a>(
&'a self,
ctx: &'a PaymentHookContext,
) -> Pin<Box<dyn Future<Output = BeforeOpDecision<VerifyResponse>> + Send + 'a>>;
fn after_verify<'a>(
&'a self,
ctx: &'a VerifyResultContext,
) -> Pin<Box<dyn Future<Output = AfterVerifyDecision> + Send + 'a>>;
fn on_verify_failure<'a>(
&'a self,
ctx: &'a PaymentHookContext,
error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<VerifyResponse>>;
fn before_settle<'a>(
&'a self,
ctx: &'a PaymentHookContext,
) -> Pin<Box<dyn Future<Output = BeforeOpDecision<SettleResponse>> + Send + 'a>>;
fn after_settle<'a>(&'a self, ctx: &'a SettleResultContext) -> BoxFuture<'a, ()>;
fn on_settle_failure<'a>(
&'a self,
ctx: &'a PaymentHookContext,
error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<SettleResponse>>;
fn on_verified_payment_canceled<'a>(
&'a self,
ctx: &'a VerifiedPaymentCanceledContext,
) -> BoxFuture<'a, ()>;
}
impl<T: ResourceServerHooks + ?Sized> DynResourceServerHooks for T {
fn before_verify<'a>(
&'a self,
ctx: &'a PaymentHookContext,
) -> Pin<Box<dyn Future<Output = BeforeOpDecision<VerifyResponse>> + Send + 'a>> {
Box::pin(<Self as ResourceServerHooks>::before_verify(self, ctx))
}
fn after_verify<'a>(
&'a self,
ctx: &'a VerifyResultContext,
) -> Pin<Box<dyn Future<Output = AfterVerifyDecision> + Send + 'a>> {
Box::pin(<Self as ResourceServerHooks>::after_verify(self, ctx))
}
fn on_verify_failure<'a>(
&'a self,
ctx: &'a PaymentHookContext,
error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<VerifyResponse>> {
Box::pin(<Self as ResourceServerHooks>::on_verify_failure(
self, ctx, error,
))
}
fn before_settle<'a>(
&'a self,
ctx: &'a PaymentHookContext,
) -> Pin<Box<dyn Future<Output = BeforeOpDecision<SettleResponse>> + Send + 'a>> {
Box::pin(<Self as ResourceServerHooks>::before_settle(self, ctx))
}
fn after_settle<'a>(&'a self, ctx: &'a SettleResultContext) -> BoxFuture<'a, ()> {
Box::pin(<Self as ResourceServerHooks>::after_settle(self, ctx))
}
fn on_settle_failure<'a>(
&'a self,
ctx: &'a PaymentHookContext,
error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<SettleResponse>> {
Box::pin(<Self as ResourceServerHooks>::on_settle_failure(
self, ctx, error,
))
}
fn on_verified_payment_canceled<'a>(
&'a self,
ctx: &'a VerifiedPaymentCanceledContext,
) -> BoxFuture<'a, ()> {
Box::pin(<Self as ResourceServerHooks>::on_verified_payment_canceled(
self, ctx,
))
}
}