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}