1use std::future::Future;
11use std::sync::Arc;
12
13use r402::client::X402Client;
14use r402::proto::{PaymentPayload, PaymentPayloadV1, PaymentRequired, PaymentRequiredV1};
15use reqwest::{Request, Response};
16use reqwest_middleware::{Middleware, Next};
17
18use crate::constants::{PAYMENT_REQUIRED_HEADER, PAYMENT_SIGNATURE_HEADER, X_PAYMENT_HEADER};
19use crate::error::HttpError;
20use crate::headers::{decode_payment_required, encode_payment_signature, encode_x_payment};
21
22#[derive(Debug, Clone)]
47pub struct X402HttpClient {
48 client: Arc<X402Client>,
49}
50
51impl X402HttpClient {
52 #[must_use]
54 pub fn new(client: Arc<X402Client>) -> Self {
55 Self { client }
56 }
57
58 #[must_use]
62 pub fn from_client(client: X402Client) -> Self {
63 Self {
64 client: Arc::new(client),
65 }
66 }
67
68 #[must_use]
84 pub fn build_reqwest(client: X402Client) -> reqwest_middleware::ClientWithMiddleware {
85 reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
86 .with(Self::from_client(client))
87 .build()
88 }
89
90 async fn extract_payment_required(response: &Response) -> Option<PaymentRequiredVersion> {
94 if let Some(header_value) = response.headers().get(PAYMENT_REQUIRED_HEADER) {
96 if let Ok(s) = header_value.to_str() {
97 if let Ok(parsed) = decode_payment_required(s) {
98 return match parsed {
99 r402::proto::helpers::PaymentRequiredEnum::V2(pr) => {
100 Some(PaymentRequiredVersion::V2(*pr))
101 }
102 r402::proto::helpers::PaymentRequiredEnum::V1(pr) => {
103 Some(PaymentRequiredVersion::V1(*pr))
104 }
105 };
106 }
107 }
108 }
109
110 None
111 }
112
113 fn encode_payment_header(
115 payload: &PaymentPayloadVersion,
116 ) -> Result<(String, String), HttpError> {
117 match payload {
118 PaymentPayloadVersion::V2(p) => {
119 let encoded = encode_payment_signature(p)?;
120 Ok((PAYMENT_SIGNATURE_HEADER.to_owned(), encoded))
121 }
122 PaymentPayloadVersion::V1(p) => {
123 let encoded = encode_x_payment(p)?;
124 Ok((X_PAYMENT_HEADER.to_owned(), encoded))
125 }
126 }
127 }
128}
129
130enum PaymentRequiredVersion {
132 V2(PaymentRequired),
133 V1(PaymentRequiredV1),
134}
135
136enum PaymentPayloadVersion {
138 V2(PaymentPayload),
139 V1(PaymentPayloadV1),
140}
141
142impl Middleware for X402HttpClient {
143 fn handle<'life0, 'life1, 'life2, 'async_trait>(
144 &'life0 self,
145 req: Request,
146 extensions: &'life1 mut http::Extensions,
147 next: Next<'life2>,
148 ) -> core::pin::Pin<
149 Box<dyn Future<Output = Result<Response, reqwest_middleware::Error>> + Send + 'async_trait>,
150 >
151 where
152 'life0: 'async_trait,
153 'life1: 'async_trait,
154 'life2: 'async_trait,
155 Self: 'async_trait,
156 {
157 Box::pin(async move {
158 let method = req.method().clone();
160 let url = req.url().clone();
161 let original_headers = req.headers().clone();
162
163 let response = next.clone().run(req, extensions).await?;
165
166 if response.status().as_u16() != 402 {
168 return Ok(response);
169 }
170
171 let payment_required = match Self::extract_payment_required(&response).await {
173 Some(pr) => pr,
174 None => return Ok(response),
175 };
176
177 let payment_payload = match &payment_required {
179 PaymentRequiredVersion::V2(pr) => {
180 match self.client.create_payment_payload(pr).await {
181 Ok(p) => PaymentPayloadVersion::V2(p),
182 Err(_) => return Ok(response),
183 }
184 }
185 PaymentRequiredVersion::V1(pr) => {
186 match self.client.create_payment_payload_v1(pr).await {
187 Ok(p) => PaymentPayloadVersion::V1(p),
188 Err(_) => return Ok(response),
189 }
190 }
191 };
192
193 let (header_name, header_value) = match Self::encode_payment_header(&payment_payload) {
195 Ok(h) => h,
196 Err(_) => return Ok(response),
197 };
198
199 let mut retry_req = Request::new(method, url);
201 *retry_req.headers_mut() = original_headers;
202 retry_req.headers_mut().insert(
203 reqwest::header::HeaderName::from_bytes(header_name.as_bytes())
204 .expect("valid header name"),
205 reqwest::header::HeaderValue::from_str(&header_value).expect("valid header value"),
206 );
207
208 next.run(retry_req, extensions).await
210 })
211 }
212}