use crate::middleware::PaymentMiddleware;
use crate::types::{PaymentPayload, PaymentRequirements, PaymentRequirementsResponse};
use warp::{
http::StatusCode,
reject::{Reject, Rejection},
reply::{json, with_status},
Filter, Reply,
};
#[derive(Debug)]
pub struct PaymentRequired {
pub requirements: Vec<PaymentRequirements>,
pub error: String,
}
impl Reject for PaymentRequired {}
impl Reply for PaymentRequired {
fn into_response(self) -> warp::reply::Response {
let response = PaymentRequirementsResponse::new(&self.error, self.requirements);
with_status(json(&response), StatusCode::PAYMENT_REQUIRED).into_response()
}
}
pub fn x402_payment_filter(
payment_middleware: PaymentMiddleware,
) -> impl Filter<Extract = (), Error = Rejection> + Clone {
let middleware = std::sync::Arc::new(payment_middleware);
warp::any()
.and(warp::header::optional::<String>("X-PAYMENT"))
.and_then(move |payment_header: Option<String>| {
let _middleware = middleware.clone();
async move {
match payment_header {
Some(payment_b64) => {
match PaymentPayload::from_base64(&payment_b64) {
Ok(payload) => {
let requirements = match create_payment_requirements_for_warp() {
Ok(req) => req,
Err(e) => {
return Err(warp::reject::custom(PaymentRequired {
requirements: vec![],
error: format!(
"Failed to create payment requirements: {}",
e
),
}));
}
};
match verify_payment_with_facilitator_warp(&payload, &requirements)
.await
{
Ok(true) => {
Ok(())
}
Ok(false) => Err(warp::reject::custom(PaymentRequired {
requirements: vec![requirements],
error: "Payment verification failed".to_string(),
})),
Err(e) => Err(warp::reject::custom(PaymentRequired {
requirements: vec![requirements],
error: format!("Payment verification error: {}", e),
})),
}
}
Err(e) => Err(warp::reject::custom(PaymentRequired {
requirements: vec![],
error: format!("Failed to decode payment payload: {}", e),
})),
}
}
None => {
let requirements = match create_payment_requirements_for_warp() {
Ok(req) => vec![req],
Err(_) => vec![],
};
Err(warp::reject::custom(PaymentRequired {
requirements,
error: "Payment required".to_string(),
}))
}
}
}
})
.untuple_one()
}
fn create_payment_requirements_for_warp() -> crate::Result<PaymentRequirements> {
let scheme = std::env::var("X402_SCHEME").unwrap_or_else(|_| "exact".to_string());
let network = std::env::var("X402_NETWORK").unwrap_or_else(|_| "base-sepolia".to_string());
let amount = std::env::var("X402_AMOUNT").unwrap_or_else(|_| "1000000".to_string());
let pay_to = std::env::var("X402_PAY_TO")
.unwrap_or_else(|_| "0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string());
let asset = match network.as_str() {
"base-sepolia" => "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"base" => "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
_ => "0x036CbD53842c5426634e7929541eC2318f3dCF7e", };
let mut requirements = PaymentRequirements::new(
&scheme,
&network,
&amount,
asset,
&pay_to,
"/",
"Payment required for this resource",
);
let network_type = match network.as_str() {
"base" => crate::types::Network::Mainnet,
"base-sepolia" => crate::types::Network::Testnet,
_ => crate::types::Network::Testnet, };
requirements.set_usdc_info(network_type)?;
Ok(requirements)
}
async fn verify_payment_with_facilitator_warp(
payment_payload: &PaymentPayload,
requirements: &PaymentRequirements,
) -> crate::Result<bool> {
let facilitator =
crate::facilitator::FacilitatorClient::new(crate::types::FacilitatorConfig::default())?;
let response = facilitator.verify(payment_payload, requirements).await?;
Ok(response.is_valid)
}
pub fn require_payment(
requirements: Vec<PaymentRequirements>,
) -> impl Filter<Extract = (), Error = Rejection> + Clone {
let requirements = std::sync::Arc::new(requirements);
warp::any()
.and_then(move || {
let requirements = requirements.clone();
async move {
Err::<(), Rejection>(warp::reject::custom(PaymentRequired {
requirements: (*requirements).clone(),
error: "Payment required".to_string(),
}))
}
})
.untuple_one()
}
pub fn verify_payment_with_error(
requirements: Vec<PaymentRequirements>,
error_message: String,
) -> impl Filter<Extract = (), Error = Rejection> + Clone {
let requirements = std::sync::Arc::new(requirements);
let error_message = std::sync::Arc::new(error_message);
warp::any()
.and_then(move || {
let requirements = requirements.clone();
let error_message = error_message.clone();
async move {
Err::<(), Rejection>(warp::reject::custom(PaymentRequired {
requirements: (*requirements).clone(),
error: (*error_message).clone(),
}))
}
})
.untuple_one()
}
pub fn payment_handler() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
warp::path::end().and(warp::get()).map(|| {
let response =
PaymentRequirementsResponse::new("Payment required for this resource", vec![]);
with_status(json(&response), StatusCode::PAYMENT_REQUIRED)
})
}
pub fn create_x402_middleware(
payment_middleware: PaymentMiddleware,
) -> impl Filter<Extract = (), Error = Rejection> + Clone {
x402_payment_filter(payment_middleware)
}
pub async fn handle_payment_verification(
_requirements: &[PaymentRequirements],
) -> std::result::Result<Option<warp::reply::Response>, Box<dyn std::error::Error>> {
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PaymentRequirements;
#[test]
fn test_payment_required_rejection() {
let requirements = vec![PaymentRequirements {
scheme: "exact".to_string(),
network: "base-sepolia".to_string(),
max_amount_required: "1000000".to_string(),
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913".to_string(),
pay_to: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string(),
resource: "/test".to_string(),
description: "Test payment".to_string(),
mime_type: Some("application/json".to_string()),
max_timeout_seconds: 300,
output_schema: None,
extra: None,
}];
let rejection = PaymentRequired {
requirements: requirements.clone(),
error: "Test error".to_string(),
};
assert_eq!(rejection.requirements.len(), 1);
assert_eq!(rejection.error, "Test error");
}
#[test]
fn test_payment_handler() {
let _handler = payment_handler();
}
}