1use serde::de::DeserializeOwned;
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use std::fmt;
26use std::fmt::{Display, Formatter};
27use std::sync::Arc;
28
29use crate::chain::ChainId;
30use crate::proto;
31use crate::proto::v1;
32use crate::proto::{OriginalJson, SupportedResponse};
33
34#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
39pub struct X402Version2;
40
41impl X402Version2 {
42 pub const VALUE: u8 = 2;
43}
44
45impl PartialEq<u8> for X402Version2 {
46 fn eq(&self, other: &u8) -> bool {
47 *other == Self::VALUE
48 }
49}
50
51impl From<X402Version2> for u8 {
52 fn from(_: X402Version2) -> Self {
53 X402Version2::VALUE
54 }
55}
56
57impl Serialize for X402Version2 {
58 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
59 serializer.serialize_u8(Self::VALUE)
60 }
61}
62
63impl<'de> Deserialize<'de> for X402Version2 {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: Deserializer<'de>,
67 {
68 let num = u8::deserialize(deserializer)?;
69 if num == Self::VALUE {
70 Ok(X402Version2)
71 } else {
72 Err(serde::de::Error::custom(format!(
73 "expected version {}, got {}",
74 Self::VALUE,
75 num
76 )))
77 }
78 }
79}
80
81impl Display for X402Version2 {
82 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
83 write!(f, "{}", Self::VALUE)
84 }
85}
86
87pub type VerifyResponse = v1::VerifyResponse;
91
92pub type SettleResponse = v1::SettleResponse;
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct ResourceInfo {
103 pub url: String,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub description: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub mime_type: Option<String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct VerifyRequest<TPayload, TRequirements> {
119 pub x402_version: X402Version2,
121 pub payment_payload: TPayload,
123 pub payment_requirements: TRequirements,
125}
126
127impl<TPayload, TRequirements> TryFrom<&VerifyRequest<TPayload, TRequirements>>
128 for proto::VerifyRequest
129where
130 TPayload: Serialize,
131 TRequirements: Serialize,
132{
133 type Error = serde_json::Error;
134
135 fn try_from(value: &VerifyRequest<TPayload, TRequirements>) -> Result<Self, Self::Error> {
136 let json = serde_json::to_string(value)?;
137 let raw = serde_json::value::RawValue::from_string(json)?;
138 Ok(Self(raw))
139 }
140}
141
142impl<TPayload, TRequirements> TryFrom<&proto::VerifyRequest>
143 for VerifyRequest<TPayload, TRequirements>
144where
145 TPayload: DeserializeOwned,
146 TRequirements: DeserializeOwned,
147{
148 type Error = proto::PaymentVerificationError;
149
150 fn try_from(value: &proto::VerifyRequest) -> Result<Self, Self::Error> {
151 let deserialized = serde_json::from_str(value.as_str())?;
152 Ok(deserialized)
153 }
154}
155
156impl<TPayload, TRequirements> VerifyRequest<TPayload, TRequirements>
157where
158 Self: DeserializeOwned,
159{
160 pub fn from_proto(
161 request: proto::VerifyRequest,
163 ) -> Result<Self, proto::PaymentVerificationError> {
164 let value = serde_json::from_str(request.as_str())?;
165 Ok(value)
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct PaymentPayload<TPaymentRequirements, TPayload> {
181 pub accepted: TPaymentRequirements,
183 pub payload: TPayload,
185 pub resource: Option<ResourceInfo>,
187 pub x402_version: X402Version2,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
203#[serde(rename_all = "camelCase")]
204pub struct PaymentRequirements<
205 TScheme = String,
206 TAmount = String,
207 TAddress = String,
208 TExtra = Option<serde_json::Value>,
209> {
210 pub scheme: TScheme,
212 pub network: ChainId,
214 pub amount: TAmount,
216 pub pay_to: TAddress,
218 pub max_timeout_seconds: u64,
220 pub asset: TAddress,
222 pub extra: TExtra,
224}
225
226impl<TScheme, TAmount, TAddress, TExtra> TryFrom<&OriginalJson>
227 for PaymentRequirements<TScheme, TAmount, TAddress, TExtra>
228where
229 TScheme: for<'a> serde::Deserialize<'a>,
230 TAmount: for<'a> serde::Deserialize<'a>,
231 TAddress: for<'a> serde::Deserialize<'a>,
232 TExtra: for<'a> serde::Deserialize<'a>,
233{
234 type Error = serde_json::Error;
235
236 fn try_from(value: &OriginalJson) -> Result<Self, Self::Error> {
237 let payment_requirements = serde_json::from_str(value.0.get())?;
238 Ok(payment_requirements)
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct PaymentRequired<TAccepts = PaymentRequirements> {
249 pub x402_version: X402Version2,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub error: Option<String>,
254 pub resource: ResourceInfo,
256 #[serde(default = "Vec::default")]
258 pub accepts: Vec<TAccepts>,
259}
260
261#[derive(Clone)]
288#[allow(dead_code)] pub struct PriceTag {
290 pub requirements: PaymentRequirements,
292 #[doc(hidden)]
294 pub enricher: Option<Enricher>,
295}
296
297impl fmt::Debug for PriceTag {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 f.debug_struct("PriceTag")
300 .field("requirements", &self.requirements)
301 .finish()
302 }
303}
304
305pub type Enricher = Arc<dyn Fn(&mut PriceTag, &SupportedResponse) + Send + Sync>;
310
311impl PriceTag {
312 #[allow(dead_code)]
317 pub fn enrich(&mut self, capabilities: &SupportedResponse) {
318 if let Some(enricher) = self.enricher.clone() {
319 enricher(self, capabilities);
320 }
321 }
322
323 #[allow(dead_code)]
325 pub fn with_timeout(mut self, seconds: u64) -> Self {
326 self.requirements.max_timeout_seconds = seconds;
327 self
328 }
329}
330
331impl PartialEq<PaymentRequirements> for PriceTag {
335 fn eq(&self, b: &PaymentRequirements) -> bool {
336 let a = &self.requirements;
337 a == b
338 }
339}