Skip to main content

x402_paywall/
lib.rs

1//! # X402 Paywall
2//!
3//! A framework-agnostic HTTP paywall implementation for the X402 payment protocol.
4//!
5//! This crate provides [`paywall::PayWall`], a composable middleware that protects
6//! HTTP resources with X402 payments. It handles the complete payment lifecycle including
7//! verification and settlement through a configured facilitator.
8//!
9//! ## Modules
10//!
11//! - [`paywall`]: The main [`PayWall`](paywall::PayWall) struct and payment flow logic.
12//! - [`processor`]: Payment processing types including [`RequestProcessor`](processor::RequestProcessor)
13//!   and [`PaymentState`](processor::PaymentState).
14//! - [`errors`]: Error types for payment failures and HTTP error responses.
15//!
16//! ## Payment Flow
17//!
18//! The standard payment flow using [`PayWall::handle_payment`](paywall::PayWall::handle_payment):
19//!
20//! 1. **Update Accepts**: Filter payment requirements based on facilitator support.
21//! 2. **Process Request**: Extract and validate the `PAYMENT-SIGNATURE` header.
22//! 3. **Verify**: Verify the payment signature with the facilitator.
23//! 4. **Run Handler**: Execute the resource handler.
24//! 5. **Settle**: Settle the payment on successful response.
25//!
26//! For custom flows, use the step-by-step API directly. See [`PayWall`](paywall::PayWall) for details.
27//!
28//! ## Framework Integration
29//!
30//! While framework-agnostic, `x402-paywall` works seamlessly with any HTTP framework.
31//! See the [`x402-kit` documentation](https://docs.rs/x402-kit) for complete usage examples
32//! with Axum and other frameworks.
33//!
34//! ## Error Handling
35//!
36//! [`ErrorResponse`](errors::ErrorResponse) implements `IntoResponse` for Axum and can be
37//! easily adapted to other frameworks. It returns appropriate HTTP status codes:
38//!
39//! - `402 Payment Required`: No payment signature provided.
40//! - `400 Bad Request`: Invalid payment payload or unsupported requirements.
41//! - `500 Internal Server Error`: Facilitator communication failures.
42
43use std::fmt::Display;
44
45pub mod errors;
46pub mod paywall;
47pub mod processor;
48
49pub trait HttpRequest {
50    fn get_header(&self, name: &str) -> Option<&[u8]>;
51    fn insert_extension<T: Clone + Send + Sync + 'static>(&mut self, ext: T) -> Option<T>;
52}
53
54pub trait HttpResponse {
55    fn is_success(&self) -> bool;
56    fn insert_header(&mut self, name: &'static str, value: &[u8])
57    -> Result<(), InvalidHeaderValue>;
58}
59
60impl<R> HttpRequest for http::Request<R> {
61    fn get_header(&self, name: &str) -> Option<&[u8]> {
62        self.headers().get(name).map(|v| v.as_bytes())
63    }
64
65    fn insert_extension<T: Clone + Send + Sync + 'static>(&mut self, ext: T) -> Option<T> {
66        self.extensions_mut().insert(ext)
67    }
68}
69
70#[derive(Debug)]
71pub struct InvalidHeaderValue;
72
73impl Display for InvalidHeaderValue {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.write_str("invalid header value")
76    }
77}
78
79impl<R> HttpResponse for http::Response<R> {
80    fn is_success(&self) -> bool {
81        self.status().is_success()
82    }
83
84    fn insert_header(
85        &mut self,
86        name: &'static str,
87        value: &[u8],
88    ) -> Result<(), InvalidHeaderValue> {
89        let value = http::HeaderValue::from_bytes(value).map_err(|_| InvalidHeaderValue)?;
90        self.headers_mut().insert(name, value);
91        Ok(())
92    }
93}
94
95#[cfg(feature = "actix-web")]
96mod actix_impl {
97    use actix_web::HttpMessage;
98
99    use super::*;
100    impl HttpRequest for actix_web::HttpRequest {
101        fn get_header(&self, name: &str) -> Option<&[u8]> {
102            self.headers().get(name).map(|v| v.as_bytes())
103        }
104
105        fn insert_extension<T: Clone + Send + Sync + 'static>(&mut self, ext: T) -> Option<T> {
106            self.extensions_mut().insert(ext)
107        }
108    }
109
110    impl<B> HttpResponse for actix_web::HttpResponse<B> {
111        fn is_success(&self) -> bool {
112            self.status().is_success()
113        }
114
115        fn insert_header(
116            &mut self,
117            name: &'static str,
118            value: &[u8],
119        ) -> Result<(), InvalidHeaderValue> {
120            let value = actix_web::http::header::HeaderValue::from_bytes(value)
121                .map_err(|_| InvalidHeaderValue)?;
122            let name = actix_web::http::header::HeaderName::from_static(name);
123            self.headers_mut().insert(name, value);
124            Ok(())
125        }
126    }
127}