1use http::{HeaderValue, Request, Response};
2use x402_core::{
3 facilitator::{
4 Facilitator, PaymentRequest, SettleResult, SettleSuccess, VerifyResult, VerifyValid,
5 },
6 transport::{PaymentPayload, PaymentRequirements, SettlementResponse},
7 types::{Base64EncodedHeader, Extension, Record},
8};
9
10use crate::{errors::ErrorResponse, paywall::PayWall};
11
12#[derive(Debug, Clone)]
34pub struct PaymentState {
35 pub verified: Option<VerifyValid>,
37 pub settled: Option<SettleSuccess>,
39 pub required_extensions: Record<Extension>,
41 pub payload_extensions: Record<Extension>,
43}
44
45pub struct RequestProcessor<'pw, F: Facilitator, Req> {
49 pub paywall: &'pw PayWall<F>,
50 pub request: Request<Req>,
51 pub payload: PaymentPayload,
52 pub selected: PaymentRequirements,
53 pub payment_state: PaymentState,
54}
55
56impl<'pw, F: Facilitator, Req> RequestProcessor<'pw, F, Req> {
57 pub async fn verify(mut self) -> Result<Self, ErrorResponse> {
61 let response = self
62 .paywall
63 .facilitator
64 .verify(PaymentRequest {
65 payment_payload: self.payload.clone(),
66 payment_requirements: self.selected.clone(),
67 })
68 .await
69 .map_err(|err| {
70 self.paywall
71 .server_error(format!("Failed to verify payment: {err}"))
72 })?;
73
74 let valid = match response {
75 VerifyResult::Valid(v) => v,
76 VerifyResult::Invalid(iv) => {
77 return Err(self.paywall.payment_failed(iv.invalid_reason));
78 }
79 };
80
81 #[cfg(feature = "tracing")]
82 tracing::debug!("Payment verified: payer='{}'", valid.payer);
83
84 self.payment_state.verified = Some(valid);
85
86 Ok(self)
87 }
88
89 pub async fn settle(mut self) -> Result<Self, ErrorResponse> {
93 let settlement = self
94 .paywall
95 .facilitator
96 .settle(PaymentRequest {
97 payment_payload: self.payload.clone(),
98 payment_requirements: self.selected.clone(),
99 })
100 .await
101 .map_err(|err| {
102 self.paywall
103 .server_error(format!("Failed to settle payment: {err}"))
104 })?;
105
106 let settled = match settlement {
107 SettleResult::Success(s) => s,
108 SettleResult::Failed(f) => {
109 return Err(self.paywall.payment_failed(f.error_reason));
110 }
111 };
112
113 #[cfg(feature = "tracing")]
114 tracing::debug!(
115 "Payment settled: payer='{}', transaction='{}', network='{}'",
116 settled.payer,
117 settled.transaction,
118 settled.network
119 );
120
121 self.payment_state.settled = Some(settled);
122
123 Ok(self)
124 }
125
126 pub async fn run_handler<Fun, Fut, Res>(
130 mut self,
131 handler: Fun,
132 ) -> Result<ResponseProcessor<'pw, F, Res>, ErrorResponse>
133 where
134 Fun: FnOnce(Request<Req>) -> Fut,
135 Fut: Future<Output = Response<Res>>,
136 {
137 self.request
138 .extensions_mut()
139 .insert(self.payment_state.clone());
140
141 let response = handler(self.request).await;
142 Ok(ResponseProcessor {
143 paywall: self.paywall,
144 response,
145 payload: self.payload,
146 selected: self.selected,
147 payment_state: self.payment_state,
148 })
149 }
150}
151
152pub struct ResponseProcessor<'pw, F: Facilitator, Res> {
154 pub paywall: &'pw PayWall<F>,
155 pub response: Response<Res>,
156 pub payload: PaymentPayload,
157 pub selected: PaymentRequirements,
158 pub payment_state: PaymentState,
159}
160
161impl<'pw, F: Facilitator, Res> ResponseProcessor<'pw, F, Res> {
162 pub async fn settle(mut self) -> Result<Self, ErrorResponse> {
166 let settlement = self
168 .paywall
169 .facilitator
170 .settle(PaymentRequest {
171 payment_payload: self.payload.clone(),
172 payment_requirements: self.selected.clone(),
173 })
174 .await
175 .map_err(|err| {
176 self.paywall
177 .server_error(format!("Failed to settle payment: {err}"))
178 })?;
179
180 let settled = match settlement {
181 SettleResult::Success(s) => s,
182 SettleResult::Failed(f) => {
183 return Err(self.paywall.payment_failed(f.error_reason));
184 }
185 };
186
187 #[cfg(feature = "tracing")]
188 tracing::debug!(
189 "Payment settled: payer='{}', transaction='{}', network='{}'",
190 settled.payer,
191 settled.transaction,
192 settled.network
193 );
194
195 self.payment_state.settled = Some(settled);
196 Ok(self)
197 }
198
199 pub async fn settle_on(
203 self,
204 predicate: impl Fn(&Response<Res>) -> bool,
205 ) -> Result<Self, ErrorResponse> {
206 if predicate(&self.response) {
207 self.settle().await
208 } else {
209 Ok(self)
210 }
211 }
212
213 pub async fn settle_on_success(self) -> Result<Self, ErrorResponse> {
217 self.settle_on(|resp| resp.status().is_success()).await
218 }
219
220 pub fn response(self) -> Response<Res> {
222 let mut response = self.response;
223
224 if let Some(settled) = &self.payment_state.settled {
225 let settlement_response = SettlementResponse {
226 success: true,
227 payer: settled.payer.clone(),
228 transaction: settled.transaction.clone(),
229 network: settled.network.clone(),
230 };
231
232 let header = Base64EncodedHeader::try_from(settlement_response)
233 .inspect_err(|err| {
234 #[cfg(feature = "tracing")]
235 tracing::warn!("Failed to encode PAYMENT-RESPONSE header: {err}; skipping")
236 })
237 .ok()
238 .and_then(|h| {
239 HeaderValue::from_str(&h.0)
240 .inspect_err(|err| {
241 #[cfg(feature = "tracing")]
242 tracing::warn!(
243 "Failed to encode PAYMENT-RESPONSE header: {err}; skipping"
244 )
245 })
246 .ok()
247 });
248
249 if let Some(header) = header {
250 response.headers_mut().insert("PAYMENT-RESPONSE", header);
251 }
252 }
253
254 response
255 }
256}