use std::fmt::{self, Display, Formatter};
use compact_str::CompactString;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorReason {
InsufficientFunds,
InvalidExactEvmPayloadAuthorizationValidAfter,
InvalidExactEvmPayloadAuthorizationValidBefore,
InvalidExactEvmPayloadAuthorizationValueMismatch,
InvalidExactEvmPayloadSignature,
InvalidExactEvmPayloadRecipientMismatch,
InvalidNetwork,
InvalidPayload,
InvalidPaymentRequirements,
InvalidScheme,
UnsupportedScheme,
InvalidX402Version,
InvalidTransactionState,
UnexpectedVerifyError,
UnexpectedSettleError,
DuplicateSettlement,
NonceAlreadyUsed,
Permit2AllowanceRequired,
InvalidUptoEvmPayloadSettlementExceedsAmount,
UptoFacilitatorMismatch,
UptoUnauthorizedFacilitator,
UptoAmountExceedsPermitted,
InvalidExactSolanaPayloadMemoMismatch,
InvalidExactSolanaPayloadMemoCount,
Custom(CompactString),
}
impl ErrorReason {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::InsufficientFunds => "insufficient_funds",
Self::InvalidExactEvmPayloadAuthorizationValidAfter => {
"invalid_exact_evm_payload_authorization_valid_after"
}
Self::InvalidExactEvmPayloadAuthorizationValidBefore => {
"invalid_exact_evm_payload_authorization_valid_before"
}
Self::InvalidExactEvmPayloadAuthorizationValueMismatch => {
"invalid_exact_evm_payload_authorization_value_mismatch"
}
Self::InvalidExactEvmPayloadSignature => "invalid_exact_evm_payload_signature",
Self::InvalidExactEvmPayloadRecipientMismatch => {
"invalid_exact_evm_payload_recipient_mismatch"
}
Self::InvalidNetwork => "invalid_network",
Self::InvalidPayload => "invalid_payload",
Self::InvalidPaymentRequirements => "invalid_payment_requirements",
Self::InvalidScheme => "invalid_scheme",
Self::UnsupportedScheme => "unsupported_scheme",
Self::InvalidX402Version => "invalid_x402_version",
Self::InvalidTransactionState => "invalid_transaction_state",
Self::UnexpectedVerifyError => "unexpected_verify_error",
Self::UnexpectedSettleError => "unexpected_settle_error",
Self::DuplicateSettlement => "duplicate_settlement",
Self::NonceAlreadyUsed => "nonce_already_used",
Self::Permit2AllowanceRequired => "permit2_allowance_required",
Self::InvalidUptoEvmPayloadSettlementExceedsAmount => {
"invalid_upto_evm_payload_settlement_exceeds_amount"
}
Self::UptoFacilitatorMismatch => "upto_facilitator_mismatch",
Self::UptoUnauthorizedFacilitator => "upto_unauthorized_facilitator",
Self::UptoAmountExceedsPermitted => "upto_amount_exceeds_permitted",
Self::InvalidExactSolanaPayloadMemoMismatch => {
"invalid_exact_solana_payload_memo_mismatch"
}
Self::InvalidExactSolanaPayloadMemoCount => "invalid_exact_solana_payload_memo_count",
Self::Custom(s) => s.as_str(),
}
}
#[must_use]
pub fn from_wire(code: &str) -> Self {
match code {
"insufficient_funds" => Self::InsufficientFunds,
"invalid_exact_evm_payload_authorization_valid_after" => {
Self::InvalidExactEvmPayloadAuthorizationValidAfter
}
"invalid_exact_evm_payload_authorization_valid_before" => {
Self::InvalidExactEvmPayloadAuthorizationValidBefore
}
"invalid_exact_evm_payload_authorization_value_mismatch" => {
Self::InvalidExactEvmPayloadAuthorizationValueMismatch
}
"invalid_exact_evm_payload_signature" => Self::InvalidExactEvmPayloadSignature,
"invalid_exact_evm_payload_recipient_mismatch" => {
Self::InvalidExactEvmPayloadRecipientMismatch
}
"invalid_network" => Self::InvalidNetwork,
"invalid_payload" => Self::InvalidPayload,
"invalid_payment_requirements" => Self::InvalidPaymentRequirements,
"invalid_scheme" => Self::InvalidScheme,
"unsupported_scheme" => Self::UnsupportedScheme,
"invalid_x402_version" => Self::InvalidX402Version,
"invalid_transaction_state" => Self::InvalidTransactionState,
"unexpected_verify_error" => Self::UnexpectedVerifyError,
"unexpected_settle_error" => Self::UnexpectedSettleError,
"duplicate_settlement" => Self::DuplicateSettlement,
"nonce_already_used" => Self::NonceAlreadyUsed,
"permit2_allowance_required" => Self::Permit2AllowanceRequired,
"invalid_upto_evm_payload_settlement_exceeds_amount" => {
Self::InvalidUptoEvmPayloadSettlementExceedsAmount
}
"upto_facilitator_mismatch" => Self::UptoFacilitatorMismatch,
"upto_unauthorized_facilitator" => Self::UptoUnauthorizedFacilitator,
"upto_amount_exceeds_permitted" => Self::UptoAmountExceedsPermitted,
"invalid_exact_solana_payload_memo_mismatch" => {
Self::InvalidExactSolanaPayloadMemoMismatch
}
"invalid_exact_solana_payload_memo_count" => Self::InvalidExactSolanaPayloadMemoCount,
other => Self::Custom(CompactString::from(other)),
}
}
}
impl Display for ErrorReason {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for ErrorReason {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ErrorReason {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = CompactString::deserialize(deserializer)?;
Ok(Self::from_wire(&s))
}
}
impl From<&str> for ErrorReason {
fn from(value: &str) -> Self {
Self::from_wire(value)
}
}
impl From<CompactString> for ErrorReason {
fn from(value: CompactString) -> Self {
Self::from_wire(&value)
}
}
impl From<String> for ErrorReason {
fn from(value: String) -> Self {
Self::from_wire(&value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PaymentProblem {
reason: ErrorReason,
details: String,
}
impl PaymentProblem {
#[must_use]
pub const fn new(reason: ErrorReason, details: String) -> Self {
Self { reason, details }
}
#[must_use]
pub fn reason(&self) -> ErrorReason {
self.reason.clone()
}
#[must_use]
pub const fn reason_ref(&self) -> &ErrorReason {
&self.reason
}
#[must_use]
pub fn details(&self) -> &str {
&self.details
}
}
pub trait AsPaymentProblem {
fn as_payment_problem(&self) -> PaymentProblem;
}