x402_paywall/
processor.rs

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/// The state of a payment processed by the paywall when accessing the resource handler.
13///
14/// This state is attached to the request extensions before running the resource handler,
15/// and can be accessed within the handler to inspect the payment status.
16///
17/// # Example
18///
19/// ```rust
20/// use axum::{extract::Extension, Json};
21/// use serde_json::{json, Value};
22/// use x402_core::facilitator::{VerifyValid, SettleSuccess};
23/// use x402_paywall::processor::PaymentState;
24///
25/// async fn example_handler(Extension(payment_state): Extension<PaymentState>) -> Json<Value> {
26///     Json(json!({
27///         "message": "You have accessed a protected resource!",
28///         "verify_state": serde_json::to_value(&payment_state.verified).unwrap_or(json!(null)),
29///         "settle_state": serde_json::to_value(&payment_state.settled).unwrap_or(json!(null)),
30///     }))
31/// }
32/// ```
33#[derive(Debug, Clone)]
34pub struct PaymentState {
35    /// Verification result, if verification was performed.
36    pub verified: Option<VerifyValid>,
37    /// Settlement result, if settlement was performed.
38    pub settled: Option<SettleSuccess>,
39    /// All extensions info provided by the paywall.
40    pub required_extensions: Record<Extension>,
41    /// All extensions info provided by the signer.
42    pub payload_extensions: Record<Extension>,
43}
44
45/// Payment processing state before running the resource handler.
46///
47/// See [`PayWall`] for usage in the full payment processing flow.
48pub 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    /// Verify the payment with the facilitator.
58    ///
59    /// `self.payment_state.verified` will be populated on success.
60    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    /// Settle the payment with the facilitator.
90    ///
91    /// `self.payment_state.settled` will be populated on success.
92    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    /// Run the resource handler with the payment state attached to the request extensions.
127    ///
128    /// After running the handler, returns a [`ResponseProcessor`] for further processing.
129    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
152/// Payment processing state after running the resource handler.
153pub 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    /// Settle the payment with the facilitator after running the resource handler.
163    ///
164    /// After settlement, `self.payment_state.settled` will be populated on success.
165    pub async fn settle(mut self) -> Result<Self, ErrorResponse> {
166        // Settle payment with facilitator
167        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    /// Conditionally settle the payment based on the provided prediction function.
200    ///
201    /// After settlement, `self.payment_state.settled` will be populated on success.
202    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    /// Settle the payment if the response status is a success (2xx).
214    ///
215    /// After settlement, `self.payment_state.settled` will be populated on success.
216    pub async fn settle_on_success(self) -> Result<Self, ErrorResponse> {
217        self.settle_on(|resp| resp.status().is_success()).await
218    }
219
220    /// Generate the final response, including the `PAYMENT-RESPONSE` header if settled.
221    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}