use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
pub type AuthHeadersFn =
dyn Fn() -> crate::Result<HashMap<String, HashMap<String, String>>> + Send + Sync;
pub type AuthHeadersFnArc = Arc<AuthHeadersFn>;
pub type AuthHeadersFnBox = Box<AuthHeadersFn>;
#[derive(Clone)]
pub struct FacilitatorConfig {
pub url: String,
pub timeout: Option<Duration>,
pub create_auth_headers: Option<AuthHeadersFnArc>,
}
impl std::fmt::Debug for FacilitatorConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FacilitatorConfig")
.field("url", &self.url)
.field("timeout", &self.timeout)
.field("create_auth_headers", &"<function>")
.finish()
}
}
impl FacilitatorConfig {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
timeout: None,
create_auth_headers: None,
}
}
pub fn validate(&self) -> crate::Result<()> {
if self.url.is_empty() {
return Err(crate::X402Error::config("Facilitator URL cannot be empty"));
}
if !self.url.starts_with("http://") && !self.url.starts_with("https://") {
return Err(crate::X402Error::config(
"Facilitator URL must start with http:// or https://",
));
}
Ok(())
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn with_auth_headers(mut self, creator: AuthHeadersFnBox) -> Self {
self.create_auth_headers = Some(Arc::from(creator));
self
}
}
impl Default for FacilitatorConfig {
fn default() -> Self {
Self::new("https://x402.org/facilitator")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyResponse {
#[serde(rename = "isValid")]
pub is_valid: bool,
#[serde(rename = "invalidReason", skip_serializing_if = "Option::is_none")]
pub invalid_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettleResponse {
pub success: bool,
#[serde(rename = "errorReason", skip_serializing_if = "Option::is_none")]
pub error_reason: Option<String>,
pub transaction: String,
pub network: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer: Option<String>,
}
impl SettleResponse {
pub fn to_base64(&self) -> crate::Result<String> {
use base64::{engine::general_purpose, Engine as _};
let json = serde_json::to_string(self)?;
Ok(general_purpose::STANDARD.encode(json))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupportedKinds {
pub kinds: Vec<SupportedKind>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupportedKind {
#[serde(rename = "x402Version")]
pub x402_version: u32,
pub scheme: String,
pub network: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}