use crate::error_reason::{AsPaymentProblem, ErrorReason, PaymentProblem};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum VerificationError {
#[error("invalid format: {0}")]
InvalidFormat(String),
#[error("payment amount is below requirements")]
InvalidPaymentAmount,
#[error("payment authorization is not yet valid")]
Early,
#[error("payment authorization has expired")]
Expired,
#[error("chain id mismatch")]
ChainIdMismatch,
#[error("payment recipient mismatch")]
RecipientMismatch,
#[error("payment asset mismatch")]
AssetMismatch,
#[error("insufficient on-chain balance")]
InsufficientFunds,
#[error("permit2 allowance required")]
Permit2AllowanceRequired,
#[error("invalid signature: {0}")]
InvalidSignature(String),
#[error("simulation failed: {0}")]
SimulationFailed(String),
#[error("unsupported chain")]
UnsupportedChain,
#[error("unsupported scheme")]
UnsupportedScheme,
#[error("accepted details do not match requirements")]
AcceptedRequirementsMismatch,
#[error("authorization nonce already used")]
NonceAlreadyUsed,
#[error("duplicate settlement attempt")]
DuplicateSettlement,
#[error("memo data mismatch")]
MemoMismatch,
#[error("memo instruction count invalid (expected 1, got {count})")]
MemoInstructionCountInvalid {
count: usize,
},
#[error("settlement amount {requested} exceeds authorised maximum {authorised}")]
SettlementAmountExceedsPermitted {
requested: String,
authorised: String,
},
#[error("witness.facilitator {witness} is not authorised on this facilitator")]
UptoFacilitatorMismatch {
witness: String,
},
#[error("on-chain proxy rejected settle: msg.sender does not match witness.facilitator")]
UptoUnauthorizedFacilitator,
#[error("on-chain proxy rejected settle: amount exceeds permitted maximum")]
UptoAmountExceedsPermitted,
}
impl From<serde_json::Error> for VerificationError {
fn from(err: serde_json::Error) -> Self {
Self::InvalidFormat(err.to_string())
}
}
impl AsPaymentProblem for VerificationError {
fn as_payment_problem(&self) -> PaymentProblem {
let reason = match self {
Self::InvalidFormat(_) | Self::InvalidSignature(_) | Self::Early | Self::Expired => {
ErrorReason::InvalidPayload
}
Self::InvalidPaymentAmount
| Self::RecipientMismatch
| Self::AssetMismatch
| Self::AcceptedRequirementsMismatch => ErrorReason::InvalidPaymentRequirements,
Self::ChainIdMismatch | Self::UnsupportedChain => ErrorReason::InvalidNetwork,
Self::InsufficientFunds => ErrorReason::InsufficientFunds,
Self::Permit2AllowanceRequired => ErrorReason::Permit2AllowanceRequired,
Self::SimulationFailed(_) => ErrorReason::InvalidTransactionState,
Self::UnsupportedScheme => ErrorReason::UnsupportedScheme,
Self::NonceAlreadyUsed => ErrorReason::NonceAlreadyUsed,
Self::DuplicateSettlement => ErrorReason::DuplicateSettlement,
Self::MemoMismatch => ErrorReason::InvalidExactSolanaPayloadMemoMismatch,
Self::MemoInstructionCountInvalid { .. } => {
ErrorReason::InvalidExactSolanaPayloadMemoCount
}
Self::SettlementAmountExceedsPermitted { .. } => {
ErrorReason::InvalidUptoEvmPayloadSettlementExceedsAmount
}
Self::UptoFacilitatorMismatch { .. } => ErrorReason::UptoFacilitatorMismatch,
Self::UptoUnauthorizedFacilitator => ErrorReason::UptoUnauthorizedFacilitator,
Self::UptoAmountExceedsPermitted => ErrorReason::UptoAmountExceedsPermitted,
};
PaymentProblem::new(reason, self.to_string())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SettlementError {
#[error("on-chain settlement failed: {0}")]
Onchain(String),
#[error("settlement timed out")]
Timeout,
#[error("duplicate settlement attempt")]
Duplicate,
}
impl AsPaymentProblem for SettlementError {
fn as_payment_problem(&self) -> PaymentProblem {
let reason = match self {
Self::Onchain(_) => ErrorReason::InvalidTransactionState,
Self::Timeout => ErrorReason::UnexpectedSettleError,
Self::Duplicate => ErrorReason::DuplicateSettlement,
};
PaymentProblem::new(reason, self.to_string())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FacilitatorError {
#[error(transparent)]
Verification(#[from] VerificationError),
#[error(transparent)]
Settlement(#[from] SettlementError),
#[error("{reason}: {message}")]
Aborted {
reason: String,
message: String,
},
#[error("on-chain error: {0}")]
Onchain(String),
#[error(transparent)]
Internal(Box<dyn std::error::Error + Send + Sync>),
}
impl FacilitatorError {
#[must_use]
pub fn aborted(reason: impl Into<String>, message: impl Into<String>) -> Self {
Self::Aborted {
reason: reason.into(),
message: message.into(),
}
}
#[must_use]
pub fn internal<E>(err: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Self::Internal(err.into())
}
}
impl AsPaymentProblem for FacilitatorError {
fn as_payment_problem(&self) -> PaymentProblem {
match self {
Self::Verification(e) => e.as_payment_problem(),
Self::Settlement(e) => e.as_payment_problem(),
Self::Aborted { reason, message } => PaymentProblem::new(
ErrorReason::from_wire(reason),
format!("{reason}: {message}"),
),
Self::Onchain(message) => {
PaymentProblem::new(ErrorReason::InvalidTransactionState, message.clone())
}
Self::Internal(e) => {
PaymentProblem::new(ErrorReason::UnexpectedVerifyError, e.to_string())
}
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ClientError {
#[error("no matching payment option")]
NoMatchingPaymentOption,
#[error("request is not cloneable (streaming body?)")]
RequestNotCloneable,
#[error("failed to parse 402 response: {0}")]
Parse(String),
#[error("failed to sign payment: {0}")]
Signing(String),
#[error("payment pre-condition not met: {0}")]
PreConditionFailed(String),
#[error(transparent)]
Json(#[from] serde_json::Error),
}