use thiserror::Error;
#[cfg(feature = "actix-web")]
use actix_web::{HttpResponse, ResponseError};
pub type Result<T> = std::result::Result<T, X402Error>;
#[derive(Error, Debug)]
pub enum X402Error {
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("Base64 error: {0}")]
Base64(#[from] base64::DecodeError),
#[error("Invalid payment payload: {message}")]
InvalidPaymentPayload { message: String },
#[error("Invalid payment requirements: {message}")]
InvalidPaymentRequirements { message: String },
#[error("Payment verification failed: {reason}")]
PaymentVerificationFailed { reason: String },
#[error("Payment settlement failed: {reason}")]
PaymentSettlementFailed { reason: String },
#[error("Facilitator error: {message}")]
FacilitatorError { message: String },
#[error("Cryptographic error: {0}")]
Crypto(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error("Invalid signature: {message}")]
InvalidSignature { message: String },
#[error("Invalid authorization: {message}")]
InvalidAuthorization { message: String },
#[error("Network not supported: {network}")]
NetworkNotSupported { network: String },
#[error("Network error: {message}")]
NetworkError { message: String },
#[error("Invalid network: {message}")]
InvalidNetwork { message: String },
#[error("Scheme not supported: {scheme}")]
SchemeNotSupported { scheme: String },
#[error("Insufficient funds")]
InsufficientFunds,
#[error("Authorization expired")]
AuthorizationExpired,
#[error("Authorization not yet valid")]
AuthorizationNotYetValid,
#[error("Invalid amount: expected {expected}, got {got}")]
InvalidAmount { expected: String, got: String },
#[error("Recipient mismatch: expected {expected}, got {got}")]
RecipientMismatch { expected: String, got: String },
#[error("Unexpected error: {message}")]
Unexpected { message: String },
#[error("Configuration error: {message}")]
Config { message: String },
#[error("Request timeout")]
Timeout,
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse integer error: {0}")]
ParseInt(#[from] std::num::ParseIntError),
#[error("From hex error: {0}")]
FromHex(#[from] rustc_hex::FromHexError),
#[error("From str radix error: {0}")]
FromStrRadix(#[from] ethereum_types::FromStrRadixErr),
#[cfg(feature = "http3")]
#[error("Rustls error: {0}")]
Rustls(#[from] rustls::Error),
#[cfg(feature = "http3")]
#[error("Certificate generation error: {0}")]
CertGen(#[from] rcgen::Error),
#[error("Address parse error: {0}")]
AddrParse(#[from] std::net::AddrParseError),
#[cfg(feature = "http3")]
#[error("Quinn connection error: {0}")]
Quinn(#[from] quinn::ConnectionError),
#[cfg(feature = "http3")]
#[error("H3 connection error: {0}")]
H3(#[from] h3::error::ConnectionError),
}
impl X402Error {
pub fn invalid_payment_payload(message: impl Into<String>) -> Self {
Self::InvalidPaymentPayload {
message: message.into(),
}
}
pub fn invalid_payment_requirements(message: impl Into<String>) -> Self {
Self::InvalidPaymentRequirements {
message: message.into(),
}
}
pub fn payment_verification_failed(reason: impl Into<String>) -> Self {
Self::PaymentVerificationFailed {
reason: reason.into(),
}
}
pub fn payment_settlement_failed(reason: impl Into<String>) -> Self {
Self::PaymentSettlementFailed {
reason: reason.into(),
}
}
pub fn facilitator_error(message: impl Into<String>) -> Self {
Self::FacilitatorError {
message: message.into(),
}
}
pub fn invalid_signature(message: impl Into<String>) -> Self {
Self::InvalidSignature {
message: message.into(),
}
}
pub fn invalid_authorization(message: impl Into<String>) -> Self {
Self::InvalidAuthorization {
message: message.into(),
}
}
pub fn network_error(message: impl Into<String>) -> Self {
Self::NetworkError {
message: message.into(),
}
}
pub fn invalid_network(message: impl Into<String>) -> Self {
Self::InvalidNetwork {
message: message.into(),
}
}
pub fn unexpected(message: impl Into<String>) -> Self {
Self::Unexpected {
message: message.into(),
}
}
pub fn config(message: impl Into<String>) -> Self {
Self::Config {
message: message.into(),
}
}
pub fn status_code(&self) -> u16 {
match self {
Self::InvalidPaymentPayload { .. } => 400,
Self::InvalidPaymentRequirements { .. } => 400,
Self::PaymentVerificationFailed { .. } => 402,
Self::PaymentSettlementFailed { .. } => 402,
Self::FacilitatorError { .. } => 502,
Self::InvalidSignature { .. } => 400,
Self::InvalidAuthorization { .. } => 401,
Self::NetworkNotSupported { .. } => 400,
Self::NetworkError { .. } => 502,
Self::InvalidNetwork { .. } => 400,
Self::SchemeNotSupported { .. } => 400,
Self::InsufficientFunds => 402,
Self::AuthorizationExpired => 401,
Self::AuthorizationNotYetValid => 401,
Self::InvalidAmount { .. } => 400,
Self::RecipientMismatch { .. } => 400,
Self::Unexpected { .. } => 500,
Self::Config { .. } => 500,
Self::Timeout => 408,
Self::Json(_) => 400,
Self::Http(_) => 502,
Self::Base64(_) => 400,
Self::Crypto(_) => 500,
Self::Io(_) => 500,
Self::ParseInt(_) => 400,
Self::FromHex(_) => 400,
Self::FromStrRadix(_) => 400,
#[cfg(feature = "http3")]
Self::Rustls(_) => 500,
#[cfg(feature = "http3")]
Self::CertGen(_) => 500,
Self::AddrParse(_) => 400,
#[cfg(feature = "http3")]
Self::Quinn(_) => 502,
#[cfg(feature = "http3")]
Self::H3(_) => 502,
}
}
pub fn error_type(&self) -> &'static str {
match self {
Self::InvalidPaymentPayload { .. } => "invalid_payment_payload",
Self::InvalidPaymentRequirements { .. } => "invalid_payment_requirements",
Self::PaymentVerificationFailed { .. } => "payment_verification_failed",
Self::PaymentSettlementFailed { .. } => "payment_settlement_failed",
Self::FacilitatorError { .. } => "facilitator_error",
Self::InvalidSignature { .. } => "invalid_signature",
Self::InvalidAuthorization { .. } => "invalid_authorization",
Self::NetworkNotSupported { .. } => "network_not_supported",
Self::NetworkError { .. } => "network_error",
Self::InvalidNetwork { .. } => "invalid_network",
Self::SchemeNotSupported { .. } => "scheme_not_supported",
Self::InsufficientFunds => "insufficient_funds",
Self::AuthorizationExpired => "authorization_expired",
Self::AuthorizationNotYetValid => "authorization_not_yet_valid",
Self::InvalidAmount { .. } => "invalid_amount",
Self::RecipientMismatch { .. } => "recipient_mismatch",
Self::Unexpected { .. } => "unexpected_error",
Self::Config { .. } => "configuration_error",
Self::Timeout => "timeout",
Self::Json(_) => "json_error",
Self::Http(_) => "http_error",
Self::Base64(_) => "base64_error",
Self::Crypto(_) => "crypto_error",
Self::Io(_) => "io_error",
Self::ParseInt(_) => "parse_int_error",
Self::FromHex(_) => "from_hex_error",
Self::FromStrRadix(_) => "from_str_radix_error",
#[cfg(feature = "http3")]
Self::Rustls(_) => "rustls_error",
#[cfg(feature = "http3")]
Self::CertGen(_) => "cert_gen_error",
Self::AddrParse(_) => "addr_parse_error",
#[cfg(feature = "http3")]
Self::Quinn(_) => "quinn_error",
#[cfg(feature = "http3")]
Self::H3(_) => "h3_error",
}
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ErrorResponse {
pub error: String,
#[serde(rename = "type")]
pub error_type: String,
pub status_code: u16,
#[serde(rename = "x402Version")]
pub x402_version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl ErrorResponse {
pub fn from_x402_error(error: &X402Error) -> Self {
Self {
error: error.to_string(),
error_type: error.error_type().to_string(),
status_code: error.status_code(),
x402_version: 1,
details: None,
}
}
pub fn new(error: impl Into<String>, error_type: impl Into<String>, status_code: u16) -> Self {
Self {
error: error.into(),
error_type: error_type.into(),
status_code,
x402_version: 1,
details: None,
}
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.details = Some(details);
self
}
}
impl From<&X402Error> for ErrorResponse {
fn from(error: &X402Error) -> Self {
Self::from_x402_error(error)
}
}
#[cfg(feature = "actix-web")]
impl ResponseError for X402Error {
fn error_response(&self) -> HttpResponse {
let error_response = ErrorResponse::from_x402_error(self);
let status_code = actix_web::http::StatusCode::from_u16(self.status_code())
.unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR);
HttpResponse::build(status_code).json(error_response)
}
}