r402_core/
resource_server.rs1use std::sync::Arc;
10
11use serde::Serialize;
12use serde_json::Value;
13
14use crate::error::FacilitatorError;
15use crate::facilitator::{DynFacilitator, Facilitator};
16use crate::wire::{
17 PaymentPayload, PaymentRequirements, SettleRequest, SettleResponse, TypedVerifyRequest, V2,
18 VerifyRequest, VerifyResponse, find_matching_requirements,
19};
20
21pub type WirePaymentPayload = PaymentPayload<PaymentRequirements, Value>;
23
24pub struct ResourceServer {
26 facilitator: Arc<dyn DynFacilitator>,
27}
28
29impl Clone for ResourceServer {
30 fn clone(&self) -> Self {
31 Self {
32 facilitator: Arc::clone(&self.facilitator),
33 }
34 }
35}
36
37impl std::fmt::Debug for ResourceServer {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("ResourceServer").finish_non_exhaustive()
40 }
41}
42
43impl ResourceServer {
44 #[must_use]
46 pub fn new<F>(facilitator: Arc<F>) -> Self
47 where
48 F: Facilitator + 'static,
49 {
50 let erased: Arc<dyn DynFacilitator> = facilitator;
51 Self {
52 facilitator: erased,
53 }
54 }
55
56 #[must_use]
58 pub fn from_dyn(facilitator: Arc<dyn DynFacilitator>) -> Self {
59 Self { facilitator }
60 }
61
62 #[must_use]
64 pub fn facilitator(&self) -> Arc<dyn DynFacilitator> {
65 Arc::clone(&self.facilitator)
66 }
67
68 #[must_use]
73 #[allow(
74 clippy::unused_self,
75 reason = "API parity with Go X402ResourceServer.FindMatchingRequirements"
76 )]
77 pub fn find_matching_requirements<'a>(
78 &self,
79 available: &'a [PaymentRequirements],
80 payload: &WirePaymentPayload,
81 ) -> Option<&'a PaymentRequirements> {
82 find_matching_requirements(available, &payload.accepted)
83 }
84
85 pub async fn verify_payment(
91 &self,
92 payload: &WirePaymentPayload,
93 requirements: &PaymentRequirements,
94 ) -> Result<VerifyResponse, FacilitatorError> {
95 let request = build_verify_request(payload, requirements)?;
96 DynFacilitator::verify(self.facilitator.as_ref(), request).await
97 }
98
99 pub async fn settle_payment(
105 &self,
106 payload: &WirePaymentPayload,
107 requirements: &PaymentRequirements,
108 ) -> Result<SettleResponse, FacilitatorError> {
109 let request = build_settle_request(payload, requirements)?;
110 DynFacilitator::settle(self.facilitator.as_ref(), request).await
111 }
112}
113
114fn build_verify_request(
115 payload: &WirePaymentPayload,
116 requirements: &PaymentRequirements,
117) -> Result<VerifyRequest, FacilitatorError> {
118 let typed = TypedVerifyRequest {
119 x402_version: V2,
120 payment_payload: payload.clone(),
121 payment_requirements: requirements.clone(),
122 };
123 to_verify_request(&typed)
124}
125
126fn build_settle_request(
127 payload: &WirePaymentPayload,
128 requirements: &PaymentRequirements,
129) -> Result<SettleRequest, FacilitatorError> {
130 let typed = TypedVerifyRequest {
131 x402_version: V2,
132 payment_payload: payload.clone(),
133 payment_requirements: requirements.clone(),
134 };
135 let verify = to_verify_request(&typed)?;
136 Ok(SettleRequest::from(verify.into_json()))
137}
138
139fn to_verify_request<T: Serialize>(typed: &T) -> Result<VerifyRequest, FacilitatorError> {
140 let json = serde_json::to_value(typed).map_err(FacilitatorError::internal)?;
141 Ok(VerifyRequest::from(json))
142}
143
144#[cfg(test)]
145mod tests {
146 use std::future::Future;
147 use std::sync::atomic::{AtomicUsize, Ordering};
148
149 use super::*;
150 use crate::wire::SupportedResponse;
151
152 struct MockFacilitator {
153 verifies: AtomicUsize,
154 settles: AtomicUsize,
155 }
156
157 impl Facilitator for MockFacilitator {
158 fn verify(
159 &self,
160 _request: VerifyRequest,
161 ) -> impl Future<Output = Result<VerifyResponse, FacilitatorError>> + Send {
162 self.verifies.fetch_add(1, Ordering::SeqCst);
163 std::future::ready(Ok(VerifyResponse::valid("0xpayer")))
164 }
165
166 fn settle(
167 &self,
168 _request: SettleRequest,
169 ) -> impl Future<Output = Result<SettleResponse, FacilitatorError>> + Send {
170 self.settles.fetch_add(1, Ordering::SeqCst);
171 std::future::ready(Ok(SettleResponse::Success {
172 payer: "0xpayer".into(),
173 transaction: "0xtx".into(),
174 network: "eip155:1".into(),
175 amount: Some("1".into()),
176 extensions: crate::wire::Extensions::new(),
177 }))
178 }
179
180 fn supported(
181 &self,
182 ) -> impl Future<Output = Result<SupportedResponse, FacilitatorError>> + Send {
183 std::future::ready(Ok(SupportedResponse::default()))
184 }
185 }
186
187 fn sample_payload() -> WirePaymentPayload {
188 let req = PaymentRequirements::new(
189 "exact".into(),
190 "eip155:1".parse().unwrap(),
191 "1".into(),
192 "0xa".into(),
193 "0xb".into(),
194 60,
195 );
196 WirePaymentPayload::new(req, serde_json::json!({"k": 1}))
197 }
198
199 #[tokio::test]
200 async fn verify_and_settle_invoke_facilitator() {
201 let mock = Arc::new(MockFacilitator {
202 verifies: AtomicUsize::new(0),
203 settles: AtomicUsize::new(0),
204 });
205 let rs = ResourceServer::new(Arc::clone(&mock));
206 let payload = sample_payload();
207 let req = payload.accepted.clone();
208 assert!(rs.verify_payment(&payload, &req).await.unwrap().is_valid());
209 assert!(
210 rs.settle_payment(&payload, &req)
211 .await
212 .unwrap()
213 .is_success()
214 );
215 assert_eq!(mock.verifies.load(Ordering::SeqCst), 1);
216 assert_eq!(mock.settles.load(Ordering::SeqCst), 1);
217 }
218
219 #[test]
220 fn find_matching_delegates_to_core() {
221 let mock = Arc::new(MockFacilitator {
222 verifies: AtomicUsize::new(0),
223 settles: AtomicUsize::new(0),
224 });
225 let rs = ResourceServer::new(mock);
226 let payload = sample_payload();
227 let available = [payload.accepted.clone()];
228 assert!(
229 rs.find_matching_requirements(&available, &payload)
230 .is_some()
231 );
232 }
233}