rust_x402/middleware/
payment.rs

1//! Payment middleware implementation
2
3use super::config::PaymentMiddlewareConfig;
4use crate::types::{
5    PaymentPayload, PaymentRequirements, PaymentRequirementsResponse, SettleResponse,
6};
7use crate::{Result, X402Error};
8use axum::{
9    extract::{Request, State},
10    http::{HeaderValue, StatusCode},
11    middleware::Next,
12    response::{IntoResponse, Response},
13    Json,
14};
15use std::sync::Arc;
16
17/// Axum middleware for x402 payments
18#[derive(Debug, Clone)]
19pub struct PaymentMiddleware {
20    pub config: Arc<PaymentMiddlewareConfig>,
21    pub facilitator: Option<crate::facilitator::FacilitatorClient>,
22    pub template_config: Option<crate::template::PaywallConfig>,
23}
24
25/// Payment processing result
26#[derive(Debug)]
27pub enum PaymentResult {
28    /// Payment verified and settled successfully
29    Success {
30        response: axum::response::Response,
31        settlement: SettleResponse,
32    },
33    /// Payment required (402 response)
34    PaymentRequired { response: axum::response::Response },
35    /// Payment verification failed
36    VerificationFailed { response: axum::response::Response },
37    /// Payment settlement failed
38    SettlementFailed { response: axum::response::Response },
39}
40
41impl PaymentMiddleware {
42    /// Create a new payment middleware
43    pub fn new(amount: rust_decimal::Decimal, pay_to: impl Into<String>) -> Self {
44        Self {
45            config: Arc::new(PaymentMiddlewareConfig::new(amount, pay_to)),
46            facilitator: None,
47            template_config: None,
48        }
49    }
50
51    /// Set the payment description
52    pub fn with_description(mut self, description: impl Into<String>) -> Self {
53        Arc::make_mut(&mut self.config).description = Some(description.into());
54        self
55    }
56
57    /// Set the MIME type
58    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
59        Arc::make_mut(&mut self.config).mime_type = Some(mime_type.into());
60        self
61    }
62
63    /// Set the maximum timeout
64    pub fn with_max_timeout_seconds(mut self, max_timeout_seconds: u32) -> Self {
65        Arc::make_mut(&mut self.config).max_timeout_seconds = max_timeout_seconds;
66        self
67    }
68
69    /// Set the output schema
70    pub fn with_output_schema(mut self, output_schema: serde_json::Value) -> Self {
71        Arc::make_mut(&mut self.config).output_schema = Some(output_schema);
72        self
73    }
74
75    /// Set the facilitator configuration
76    pub fn with_facilitator_config(
77        mut self,
78        facilitator_config: crate::types::FacilitatorConfig,
79    ) -> Self {
80        Arc::make_mut(&mut self.config).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        Arc::make_mut(&mut self.config).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        Arc::make_mut(&mut self.config).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        Arc::make_mut(&mut self.config).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        Arc::make_mut(&mut self.config).resource_root_url = Some(url.into());
105        self
106    }
107
108    /// Get the middleware configuration
109    pub fn config(&self) -> &PaymentMiddlewareConfig {
110        &self.config
111    }
112
113    /// Set the facilitator client
114    pub fn with_facilitator(mut self, facilitator: crate::facilitator::FacilitatorClient) -> Self {
115        self.facilitator = Some(facilitator);
116        self
117    }
118
119    /// Set the template configuration
120    pub fn with_template_config(mut self, template_config: crate::template::PaywallConfig) -> Self {
121        self.template_config = Some(template_config);
122        self
123    }
124
125    /// Verify a payment payload
126    pub async fn verify(&self, payment_payload: &PaymentPayload) -> bool {
127        // Create facilitator if not already configured
128        let facilitator = if let Some(facilitator) = &self.facilitator {
129            facilitator.clone()
130        } else {
131            match crate::facilitator::FacilitatorClient::new(self.config.facilitator_config.clone())
132            {
133                Ok(facilitator) => facilitator,
134                Err(_) => return false,
135            }
136        };
137
138        if let Ok(requirements) = self.config.create_payment_requirements("/") {
139            if let Ok(response) = facilitator.verify(payment_payload, &requirements).await {
140                return response.is_valid;
141            }
142        }
143        false
144    }
145
146    /// Settle a payment
147    pub async fn settle(&self, payment_payload: &PaymentPayload) -> Result<SettleResponse> {
148        // Create facilitator if not already configured
149        let facilitator = if let Some(facilitator) = &self.facilitator {
150            facilitator.clone()
151        } else {
152            crate::facilitator::FacilitatorClient::new(self.config.facilitator_config.clone())?
153        };
154
155        let requirements = self.config.create_payment_requirements("/")?;
156        facilitator.settle(payment_payload, &requirements).await
157    }
158
159    /// Verify payment with specific requirements
160    pub async fn verify_with_requirements(
161        &self,
162        payment_payload: &PaymentPayload,
163        requirements: &PaymentRequirements,
164    ) -> Result<bool> {
165        let facilitator = if let Some(facilitator) = &self.facilitator {
166            facilitator.clone()
167        } else {
168            crate::facilitator::FacilitatorClient::new(self.config.facilitator_config.clone())?
169        };
170
171        let response = facilitator.verify(payment_payload, requirements).await?;
172        Ok(response.is_valid)
173    }
174
175    /// Settle payment with specific requirements
176    pub async fn settle_with_requirements(
177        &self,
178        payment_payload: &PaymentPayload,
179        requirements: &PaymentRequirements,
180    ) -> Result<SettleResponse> {
181        let facilitator = if let Some(facilitator) = &self.facilitator {
182            facilitator.clone()
183        } else {
184            crate::facilitator::FacilitatorClient::new(self.config.facilitator_config.clone())?
185        };
186
187        facilitator.settle(payment_payload, requirements).await
188    }
189
190    /// Process payment with unified flow
191    pub async fn process_payment(&self, request: Request, next: Next) -> Result<PaymentResult> {
192        let headers = request.headers();
193        let uri = request.uri().to_string();
194
195        // Check if this is a web browser request
196        let user_agent = headers
197            .get("User-Agent")
198            .and_then(|v| v.to_str().ok())
199            .unwrap_or("");
200        let accept = headers
201            .get("Accept")
202            .and_then(|v| v.to_str().ok())
203            .unwrap_or("");
204
205        let is_web_browser = accept.contains("text/html") && user_agent.contains("Mozilla");
206
207        // Create payment requirements
208        let payment_requirements = self.config.create_payment_requirements(&uri)?;
209
210        // Check for payment header
211        let payment_header = headers.get("X-PAYMENT").and_then(|v| v.to_str().ok());
212
213        match payment_header {
214            Some(payment_b64) => {
215                // Decode payment payload
216                let payment_payload = PaymentPayload::from_base64(payment_b64).map_err(|e| {
217                    X402Error::invalid_payment_payload(format!("Failed to decode payment: {}", e))
218                })?;
219
220                // Get facilitator client
221                let facilitator = if let Some(facilitator) = &self.facilitator {
222                    facilitator.clone()
223                } else {
224                    crate::facilitator::FacilitatorClient::new(
225                        self.config.facilitator_config.clone(),
226                    )?
227                };
228
229                // Verify payment
230                let verify_response = facilitator
231                    .verify(&payment_payload, &payment_requirements)
232                    .await
233                    .map_err(|e| {
234                        X402Error::facilitator_error(format!("Payment verification failed: {}", e))
235                    })?;
236
237                if !verify_response.is_valid {
238                    let error_response = self.create_payment_required_response(
239                        "Payment verification failed",
240                        &payment_requirements,
241                        is_web_browser,
242                    )?;
243                    return Ok(PaymentResult::VerificationFailed {
244                        response: error_response,
245                    });
246                }
247
248                // Execute the handler
249                let mut response = next.run(request).await;
250
251                // Settle the payment
252                let settle_response = facilitator
253                    .settle(&payment_payload, &payment_requirements)
254                    .await
255                    .map_err(|e| {
256                        X402Error::facilitator_error(format!("Payment settlement failed: {}", e))
257                    })?;
258
259                // Add settlement header
260                let settlement_header = settle_response.to_base64().map_err(|e| {
261                    X402Error::config(format!("Failed to encode settlement response: {}", e))
262                })?;
263
264                if let Ok(header_value) = HeaderValue::from_str(&settlement_header) {
265                    response
266                        .headers_mut()
267                        .insert("X-PAYMENT-RESPONSE", header_value);
268                }
269
270                Ok(PaymentResult::Success {
271                    response,
272                    settlement: settle_response,
273                })
274            }
275            None => {
276                // No payment provided, return 402 with requirements
277                let response = self.create_payment_required_response(
278                    "X-PAYMENT header is required",
279                    &payment_requirements,
280                    is_web_browser,
281                )?;
282                Ok(PaymentResult::PaymentRequired { response })
283            }
284        }
285    }
286
287    /// Create payment required response
288    fn create_payment_required_response(
289        &self,
290        error: &str,
291        payment_requirements: &PaymentRequirements,
292        is_web_browser: bool,
293    ) -> Result<axum::response::Response> {
294        if is_web_browser {
295            let html = if let Some(custom_html) = &self.config.custom_paywall_html {
296                custom_html.clone()
297            } else {
298                // Use the template system
299                let paywall_config = self.template_config.clone().unwrap_or_else(|| {
300                    crate::template::PaywallConfig::new()
301                        .with_app_name("x402 Service")
302                        .with_app_logo("💰")
303                });
304
305                crate::template::generate_paywall_html(
306                    error,
307                    std::slice::from_ref(payment_requirements),
308                    Some(&paywall_config),
309                )
310            };
311
312            let response = Response::builder()
313                .status(StatusCode::PAYMENT_REQUIRED)
314                .header("Content-Type", "text/html")
315                .body(html.into())
316                .map_err(|e| X402Error::config(format!("Failed to create HTML response: {}", e)))?;
317
318            Ok(response)
319        } else {
320            let payment_response =
321                PaymentRequirementsResponse::new(error, vec![payment_requirements.clone()]);
322
323            Ok(Json(payment_response).into_response())
324        }
325    }
326}
327
328/// Axum middleware function for handling x402 payments
329pub async fn payment_middleware(
330    State(middleware): State<PaymentMiddleware>,
331    request: Request,
332    next: Next,
333) -> Result<impl IntoResponse> {
334    match middleware.process_payment(request, next).await? {
335        PaymentResult::Success { response, .. } => Ok(response),
336        PaymentResult::PaymentRequired { response } => Ok(response),
337        PaymentResult::VerificationFailed { response } => Ok(response),
338        PaymentResult::SettlementFailed { response } => Ok(response),
339    }
340}