rust_x402/middleware/
config.rs

1//! Middleware configuration
2
3use crate::types::{FacilitatorConfig, Network, PaymentRequirements};
4use crate::{Result, X402Error};
5use rust_decimal::Decimal;
6
7/// Configuration for payment middleware
8#[derive(Debug, Clone)]
9pub struct PaymentMiddlewareConfig {
10    /// Payment amount in decimal units (e.g., 0.0001 for 1/10th of a cent)
11    pub amount: Decimal,
12    /// Recipient wallet address
13    pub pay_to: String,
14    /// Payment description
15    pub description: Option<String>,
16    /// MIME type of the expected response
17    pub mime_type: Option<String>,
18    /// Maximum timeout in seconds
19    pub max_timeout_seconds: u32,
20    /// JSON schema for response format
21    pub output_schema: Option<serde_json::Value>,
22    /// Facilitator configuration
23    pub facilitator_config: FacilitatorConfig,
24    /// Whether this is a testnet
25    pub testnet: bool,
26    /// Custom paywall HTML for web browsers
27    pub custom_paywall_html: Option<String>,
28    /// Resource URL (if different from request URL)
29    pub resource: Option<String>,
30    /// Resource root URL for constructing full resource URLs
31    pub resource_root_url: Option<String>,
32}
33
34impl PaymentMiddlewareConfig {
35    /// Create a new payment middleware config
36    pub fn new(amount: Decimal, pay_to: impl Into<String>) -> Self {
37        // Normalize pay_to to lowercase to avoid EIP-55 checksum mismatches
38        let pay_to_normalized = pay_to.into().to_lowercase();
39        Self {
40            amount,
41            pay_to: pay_to_normalized,
42            description: None,
43            mime_type: None,
44            max_timeout_seconds: 60,
45            output_schema: None,
46            facilitator_config: FacilitatorConfig::default(),
47            testnet: true,
48            custom_paywall_html: None,
49            resource: None,
50            resource_root_url: None,
51        }
52    }
53
54    /// Set the payment description
55    pub fn with_description(mut self, description: impl Into<String>) -> Self {
56        self.description = Some(description.into());
57        self
58    }
59
60    /// Set the MIME type
61    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
62        self.mime_type = Some(mime_type.into());
63        self
64    }
65
66    /// Set the maximum timeout
67    pub fn with_max_timeout_seconds(mut self, max_timeout_seconds: u32) -> Self {
68        self.max_timeout_seconds = max_timeout_seconds;
69        self
70    }
71
72    /// Set the output schema
73    pub fn with_output_schema(mut self, output_schema: serde_json::Value) -> Self {
74        self.output_schema = Some(output_schema);
75        self
76    }
77
78    /// Set the facilitator configuration
79    pub fn with_facilitator_config(mut self, facilitator_config: FacilitatorConfig) -> Self {
80        self.facilitator_config = facilitator_config;
81        self
82    }
83
84    /// Set whether this is a testnet
85    pub fn with_testnet(mut self, testnet: bool) -> Self {
86        self.testnet = testnet;
87        self
88    }
89
90    /// Set custom paywall HTML
91    pub fn with_custom_paywall_html(mut self, html: impl Into<String>) -> Self {
92        self.custom_paywall_html = Some(html.into());
93        self
94    }
95
96    /// Set the resource URL
97    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
98        self.resource = Some(resource.into());
99        self
100    }
101
102    /// Set the resource root URL
103    pub fn with_resource_root_url(mut self, url: impl Into<String>) -> Self {
104        self.resource_root_url = Some(url.into());
105        self
106    }
107
108    /// Create payment requirements from this config
109    pub fn create_payment_requirements(&self, request_uri: &str) -> Result<PaymentRequirements> {
110        let network = if self.testnet {
111            crate::types::networks::BASE_SEPOLIA
112        } else {
113            crate::types::networks::BASE_MAINNET
114        };
115
116        let usdc_address = crate::types::networks::get_usdc_address(network).ok_or_else(|| {
117            X402Error::NetworkNotSupported {
118                network: network.to_string(),
119            }
120        })?;
121
122        let resource = if let Some(ref resource_url) = self.resource {
123            resource_url.clone()
124        } else if let Some(ref root_url) = self.resource_root_url {
125            format!("{}{}", root_url, request_uri)
126        } else {
127            request_uri.to_string()
128        };
129
130        let max_amount_required = (self.amount * Decimal::from(1_000_000u64))
131            .normalize()
132            .to_string();
133
134        // Normalize pay_to to lowercase to avoid EIP-55 checksum mismatches
135        let pay_to_normalized = self.pay_to.to_lowercase();
136
137        let mut requirements = PaymentRequirements::new(
138            crate::types::schemes::EXACT,
139            network,
140            max_amount_required,
141            usdc_address,
142            &pay_to_normalized,
143            resource,
144            self.description.as_deref().unwrap_or("Payment required"),
145        );
146
147        requirements.mime_type = self.mime_type.clone();
148        requirements.output_schema = self.output_schema.clone();
149        requirements.max_timeout_seconds = self.max_timeout_seconds;
150
151        let network = if self.testnet {
152            Network::Testnet
153        } else {
154            Network::Mainnet
155        };
156        requirements.set_usdc_info(network)?;
157
158        Ok(requirements)
159    }
160}