x402_kit/
facilitator_client.rs

1use http::{HeaderMap, HeaderName, HeaderValue};
2use serde::{Deserialize, Serialize};
3use url::Url;
4
5use crate::{
6    facilitator::{
7        Facilitator, PaymentRequest, SettleFailed, SettleResult, SettleSuccess, SupportedResponse,
8        VerifyInvalid, VerifyResult, VerifyValid,
9    },
10    transport::{PaymentPayload, PaymentRequirements},
11};
12
13/// A remote facilitator client that communicates over HTTP.
14///
15/// You can customize the request and response types for verification and settlement
16///
17/// # Type Parameters
18///
19/// - `VReq`: The request type for verification, must be convertible from `FacilitatorPaymentRequest` and serializable.
20/// - `VRes`: The response type for verification, must be convertible into `FacilitatorVerifyResponse` and deserializable.
21/// - `SReq`: The request type for settlement, must be convertible from `FacilitatorPaymentRequest` and serializable.
22/// - `SRes`: The response type for settlement, must be convertible into `FacilitatorSettleResponse` and deserializable.
23#[derive(Debug, Clone)]
24pub struct RemoteFacilitatorClient<VReq, VRes, SReq, SRes>
25where
26    VReq: From<PaymentRequest> + Serialize,
27    VRes: IntoVerifyResponse + for<'de> Deserialize<'de>,
28    SReq: From<PaymentRequest> + Serialize,
29    SRes: IntoSettleResponse + for<'de> Deserialize<'de>,
30{
31    pub base_url: Url,
32    pub client: reqwest::Client,
33    pub supported_headers: HeaderMap,
34    pub verify_headers: HeaderMap,
35    pub settle_headers: HeaderMap,
36    pub _phantom: std::marker::PhantomData<(VReq, VRes, SReq, SRes)>,
37}
38
39pub trait IntoVerifyResponse {
40    fn into_verify_response(self) -> VerifyResult;
41}
42
43pub trait IntoSettleResponse {
44    fn into_settle_response(self) -> SettleResult;
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct DefaultPaymentRequest {
50    pub payment_payload: PaymentPayload,
51    pub payment_requirements: PaymentRequirements,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct DefaultVerifyResponse {
57    pub is_valid: bool,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub invalid_reason: Option<String>,
60    pub payer: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct DefaultSettleResponse {
66    pub success: bool,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub error_reason: Option<String>,
69    pub payer: Option<String>,
70    pub transaction: Option<String>,
71    pub network: Option<String>,
72}
73
74impl From<PaymentRequest> for DefaultPaymentRequest {
75    fn from(request: PaymentRequest) -> Self {
76        DefaultPaymentRequest {
77            payment_payload: request.payment_payload,
78            payment_requirements: request.payment_requirements,
79        }
80    }
81}
82
83impl IntoVerifyResponse for DefaultVerifyResponse {
84    fn into_verify_response(self) -> VerifyResult {
85        if self.is_valid {
86            VerifyResult::valid(VerifyValid {
87                payer: self.payer.unwrap_or_default(),
88            })
89        } else {
90            VerifyResult::invalid(VerifyInvalid {
91                invalid_reason: self.invalid_reason.unwrap_or_default(),
92                payer: self.payer,
93            })
94        }
95    }
96}
97
98impl IntoSettleResponse for DefaultSettleResponse {
99    fn into_settle_response(self) -> SettleResult {
100        if self.success {
101            SettleResult::success(SettleSuccess {
102                payer: self.payer.unwrap_or_default(),
103                transaction: self.transaction.unwrap_or_default(),
104                network: self.network.unwrap_or_default(),
105            })
106        } else {
107            SettleResult::failed(SettleFailed {
108                error_reason: self.error_reason.unwrap_or_default(),
109                payer: self.payer,
110            })
111        }
112    }
113}
114
115/// A type alias for a RemoteFacilitatorClient using the default request and response types.
116pub type DefaultRemoteFacilitatorClient = RemoteFacilitatorClient<
117    DefaultPaymentRequest,
118    DefaultVerifyResponse,
119    DefaultPaymentRequest,
120    DefaultSettleResponse,
121>;
122
123impl<VReq, VRes, SReq, SRes> RemoteFacilitatorClient<VReq, VRes, SReq, SRes>
124where
125    VReq: From<PaymentRequest> + Serialize,
126    VRes: IntoVerifyResponse + for<'de> Deserialize<'de>,
127    SReq: From<PaymentRequest> + Serialize,
128    SRes: IntoSettleResponse + for<'de> Deserialize<'de>,
129{
130    pub fn new_from_url(base_url: Url) -> Self {
131        RemoteFacilitatorClient {
132            base_url,
133            client: reqwest::Client::new(),
134            supported_headers: HeaderMap::new(),
135            verify_headers: HeaderMap::new(),
136            settle_headers: HeaderMap::new(),
137            _phantom: std::marker::PhantomData,
138        }
139    }
140
141    pub fn with_verify_request_type<NewVReq>(
142        self,
143    ) -> RemoteFacilitatorClient<NewVReq, VRes, SReq, SRes>
144    where
145        NewVReq: From<PaymentRequest> + Serialize,
146    {
147        RemoteFacilitatorClient {
148            base_url: self.base_url,
149            client: self.client,
150            supported_headers: self.supported_headers,
151            verify_headers: self.verify_headers,
152            settle_headers: self.settle_headers,
153            _phantom: std::marker::PhantomData,
154        }
155    }
156
157    pub fn with_verify_response_type<NewVRes>(
158        self,
159    ) -> RemoteFacilitatorClient<VReq, NewVRes, SReq, SRes>
160    where
161        NewVRes: IntoVerifyResponse + for<'de> Deserialize<'de>,
162    {
163        RemoteFacilitatorClient {
164            supported_headers: self.supported_headers,
165            base_url: self.base_url,
166            verify_headers: self.verify_headers,
167            settle_headers: self.settle_headers,
168            client: self.client,
169            _phantom: std::marker::PhantomData,
170        }
171    }
172
173    pub fn with_settle_request_type<NewSReq>(
174        self,
175    ) -> RemoteFacilitatorClient<VReq, VRes, NewSReq, SRes>
176    where
177        NewSReq: From<PaymentRequest> + Serialize,
178    {
179        RemoteFacilitatorClient {
180            supported_headers: self.supported_headers,
181            base_url: self.base_url,
182            verify_headers: self.verify_headers,
183            settle_headers: self.settle_headers,
184            client: self.client,
185            _phantom: std::marker::PhantomData,
186        }
187    }
188
189    pub fn with_settle_response_type<NewSRes>(
190        self,
191    ) -> RemoteFacilitatorClient<VReq, VRes, SReq, NewSRes>
192    where
193        NewSRes: IntoSettleResponse + for<'de> Deserialize<'de>,
194    {
195        RemoteFacilitatorClient {
196            supported_headers: self.supported_headers,
197            base_url: self.base_url,
198            verify_headers: self.verify_headers,
199            settle_headers: self.settle_headers,
200            client: self.client,
201            _phantom: std::marker::PhantomData,
202        }
203    }
204
205    pub fn header(mut self, key: &HeaderName, value: &HeaderValue) -> Self {
206        self.supported_headers.insert(key, value.to_owned());
207        self.verify_headers.insert(key, value.to_owned());
208        self.settle_headers.insert(key, value.to_owned());
209        self
210    }
211
212    pub fn supported_header(mut self, key: &HeaderName, value: &HeaderValue) -> Self {
213        self.supported_headers.insert(key, value.to_owned());
214        self
215    }
216
217    pub fn verify_header(mut self, key: &HeaderName, value: &HeaderValue) -> Self {
218        self.verify_headers.insert(key, value.to_owned());
219        self
220    }
221
222    pub fn settle_header(mut self, key: &HeaderName, value: &HeaderValue) -> Self {
223        self.settle_headers.insert(key, value.to_owned());
224        self
225    }
226}
227
228impl
229    RemoteFacilitatorClient<
230        DefaultPaymentRequest,
231        DefaultVerifyResponse,
232        DefaultPaymentRequest,
233        DefaultSettleResponse,
234    >
235{
236    pub fn from_url(base_url: Url) -> Self {
237        RemoteFacilitatorClient::new_from_url(base_url)
238    }
239}
240
241#[derive(Debug, thiserror::Error)]
242pub enum RemoteFacilitatorClientError {
243    #[error("URL parse error: {0}")]
244    UrlParseError(#[from] url::ParseError),
245    #[error("HTTP request error: {0}")]
246    HttpRequestError(#[from] reqwest::Error),
247    #[error("Serialization/Deserialization error: {0}")]
248    SerdeError(#[from] serde_json::Error),
249}
250
251impl<VReq, VRes, SReq, SRes> Facilitator for RemoteFacilitatorClient<VReq, VRes, SReq, SRes>
252where
253    VReq: From<PaymentRequest> + Serialize,
254    VRes: IntoVerifyResponse + for<'de> Deserialize<'de>,
255    SReq: From<PaymentRequest> + Serialize,
256    SRes: IntoSettleResponse + for<'de> Deserialize<'de>,
257{
258    type Error = RemoteFacilitatorClientError;
259
260    async fn supported(&self) -> Result<SupportedResponse, Self::Error> {
261        let supported = self
262            .client
263            .get(self.base_url.join("supported")?)
264            .headers(self.supported_headers.clone())
265            .send()
266            .await?
267            .json()
268            .await?;
269
270        Ok(supported)
271    }
272
273    async fn verify(&self, request: PaymentRequest) -> Result<VerifyResult, Self::Error> {
274        let result = self
275            .client
276            .post(self.base_url.join("verify")?)
277            .headers(self.verify_headers.clone())
278            .json(&VReq::from(request))
279            .send()
280            .await?
281            .json::<VRes>()
282            .await?;
283
284        Ok(result.into_verify_response())
285    }
286
287    async fn settle(&self, request: PaymentRequest) -> Result<SettleResult, Self::Error> {
288        let result = self
289            .client
290            .post(self.base_url.join("settle")?)
291            .headers(self.settle_headers.clone())
292            .json(&SReq::from(request))
293            .send()
294            .await?
295            .json::<SRes>()
296            .await?;
297
298        Ok(result.into_settle_response())
299    }
300}