use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use compact_str::CompactString;
use r402_facilitator::{BoxFuture, FailureRecovery};
use r402_protocol::error::FacilitatorError;
use r402_protocol::payment::{
Extensions, PaymentPayload, PaymentRequirements, SettleResponse, VerifyResponse,
};
use crate::payment_flow::SettlePhase;
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()
}
}
impl PaymentHookContext {
#[must_use]
pub const fn new(payload: WirePaymentPayload, requirements: PaymentRequirements) -> Self {
Self {
payload,
requirements,
}
}
}
#[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 SettleContext {
pub payment: PaymentHookContext,
pub declared_extensions: Extensions,
pub phase: SettlePhase,
pub resource_url: Option<CompactString>,
}
impl Debug for SettleContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SettleContext")
.field("phase", &self.phase)
.field("payment", &self.payment)
.finish_non_exhaustive()
}
}
impl SettleContext {
#[must_use]
pub fn new(payment: PaymentHookContext, phase: SettlePhase) -> Self {
Self {
payment,
declared_extensions: Extensions::new(),
phase,
resource_url: None,
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct SettleResultContext {
pub settle: SettleContext,
pub result: SettleResponse,
}
impl Debug for SettleResultContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SettleResultContext")
.field("settle", &self.settle)
.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>,
pub settled_phases: Vec<SettlePhase>,
}
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)
.field("settled_phases", &self.settled_phases)
.finish_non_exhaustive()
}
}
impl VerifiedPaymentCanceledContext {
#[must_use]
pub const fn new(
payment: PaymentHookContext,
reason: CancelReason,
settled_phases: Vec<SettlePhase>,
) -> Self {
Self {
payment,
reason,
error: None,
response_status: None,
settled_phases,
}
}
}
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 SettleContext,
) -> 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 SettleContext,
_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,
) -> BoxFuture<'a, BeforeOpDecision<VerifyResponse>>;
fn after_verify<'a>(
&'a self,
ctx: &'a VerifyResultContext,
) -> BoxFuture<'a, AfterVerifyDecision>;
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 SettleContext,
) -> BoxFuture<'a, BeforeOpDecision<SettleResponse>>;
fn after_settle<'a>(&'a self, ctx: &'a SettleResultContext) -> BoxFuture<'a, ()>;
fn on_settle_failure<'a>(
&'a self,
ctx: &'a SettleContext,
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,
) -> BoxFuture<'a, BeforeOpDecision<VerifyResponse>> {
Box::pin(<Self as ResourceServerHooks>::before_verify(self, ctx))
}
fn after_verify<'a>(
&'a self,
ctx: &'a VerifyResultContext,
) -> BoxFuture<'a, AfterVerifyDecision> {
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 SettleContext,
) -> BoxFuture<'a, BeforeOpDecision<SettleResponse>> {
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 SettleContext,
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,
))
}
}