1use 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#[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#[derive(Debug)]
27pub enum PaymentResult {
28 Success {
30 response: axum::response::Response,
31 settlement: SettleResponse,
32 },
33 PaymentRequired { response: axum::response::Response },
35 VerificationFailed { response: axum::response::Response },
37 SettlementFailed { response: axum::response::Response },
39}
40
41impl PaymentMiddleware {
42 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 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 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 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 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 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 pub fn with_testnet(mut self, testnet: bool) -> Self {
86 Arc::make_mut(&mut self.config).testnet = testnet;
87 self
88 }
89
90 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 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 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 pub fn config(&self) -> &PaymentMiddlewareConfig {
110 &self.config
111 }
112
113 pub fn with_facilitator(mut self, facilitator: crate::facilitator::FacilitatorClient) -> Self {
115 self.facilitator = Some(facilitator);
116 self
117 }
118
119 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 pub async fn verify(&self, payment_payload: &PaymentPayload) -> bool {
127 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 pub async fn settle(&self, payment_payload: &PaymentPayload) -> Result<SettleResponse> {
148 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 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 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 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 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 let payment_requirements = self.config.create_payment_requirements(&uri)?;
209
210 let payment_header = headers.get("X-PAYMENT").and_then(|v| v.to_str().ok());
212
213 match payment_header {
214 Some(payment_b64) => {
215 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 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 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 let mut response = next.run(request).await;
250
251 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 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 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 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 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
328pub 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}