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