x402_core/
transport.rs

1//! X402 transport types and serialization.
2
3use std::fmt::Debug;
4
5use base64::{Engine, prelude::BASE64_STANDARD};
6use serde::{Deserialize, Serialize};
7use url::Url;
8
9use crate::{
10    core::{Address, NetworkFamily, Payment, Resource, Scheme},
11    types::{AmountValue, AnyJson, Base64EncodedHeader, Extension, Record, X402V2},
12};
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15#[serde(rename_all = "camelCase")]
16pub struct PaymentRequirements {
17    pub scheme: String,
18    pub network: String,
19    pub amount: AmountValue,
20    pub asset: String,
21    pub pay_to: String,
22    pub max_timeout_seconds: u64,
23    pub extra: Option<AnyJson>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct PaymentResource {
29    pub url: Url,
30    pub description: String,
31    pub mime_type: String,
32}
33
34impl From<Resource> for PaymentResource {
35    fn from(resource: Resource) -> Self {
36        PaymentResource {
37            url: resource.url,
38            description: resource.description,
39            mime_type: resource.mime_type,
40        }
41    }
42}
43
44#[derive(Clone, Default)]
45pub struct Accepts(Vec<PaymentRequirements>);
46
47impl AsRef<[PaymentRequirements]> for Accepts {
48    fn as_ref(&self) -> &[PaymentRequirements] {
49        &self.0
50    }
51}
52
53impl<T> From<T> for Accepts
54where
55    T: Into<PaymentRequirements>,
56{
57    fn from(value: T) -> Self {
58        Accepts(vec![value.into()])
59    }
60}
61
62impl From<Vec<PaymentRequirements>> for Accepts {
63    fn from(value: Vec<PaymentRequirements>) -> Self {
64        Accepts(value)
65    }
66}
67
68impl IntoIterator for Accepts {
69    type Item = PaymentRequirements;
70    type IntoIter = std::vec::IntoIter<PaymentRequirements>;
71
72    fn into_iter(self) -> Self::IntoIter {
73        self.0.into_iter()
74    }
75}
76
77impl<'a> IntoIterator for &'a Accepts {
78    type Item = &'a PaymentRequirements;
79    type IntoIter = std::slice::Iter<'a, PaymentRequirements>;
80
81    fn into_iter(self) -> Self::IntoIter {
82        self.0.iter()
83    }
84}
85
86impl FromIterator<PaymentRequirements> for Accepts {
87    fn from_iter<T: IntoIterator<Item = PaymentRequirements>>(iter: T) -> Self {
88        let vec: Vec<PaymentRequirements> = iter.into_iter().collect();
89        Accepts(vec)
90    }
91}
92
93impl Serialize for Accepts {
94    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95    where
96        S: serde::Serializer,
97    {
98        self.0.serialize(serializer)
99    }
100}
101
102impl<'de> Deserialize<'de> for Accepts {
103    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104    where
105        D: serde::Deserializer<'de>,
106    {
107        let vec = Vec::<PaymentRequirements>::deserialize(deserializer)?;
108        Ok(Accepts(vec))
109    }
110}
111
112impl Debug for Accepts {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        format!("{:?}", self.0).fmt(f)
115    }
116}
117
118impl Accepts {
119    pub fn push(mut self, payment: impl Into<PaymentRequirements>) -> Self {
120        self.0.push(payment.into());
121        self
122    }
123
124    pub fn new() -> Self {
125        Accepts(Vec::new())
126    }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct PaymentRequired {
132    pub x402_version: X402V2,
133    pub error: String,
134    pub resource: PaymentResource,
135    pub accepts: Accepts,
136    pub extensions: Record<Extension>,
137}
138
139impl TryFrom<PaymentRequired> for Base64EncodedHeader {
140    type Error = crate::errors::Error;
141
142    /// Serialize PaymentRequired into `PAYMENT-REQUIRED` header format
143    fn try_from(value: PaymentRequired) -> Result<Self, Self::Error> {
144        let json = serde_json::to_string(&value)?;
145        let encoded = BASE64_STANDARD.encode(json);
146        Ok(Base64EncodedHeader(encoded))
147    }
148}
149
150impl TryFrom<Base64EncodedHeader> for PaymentRequired {
151    type Error = crate::errors::Error;
152
153    /// Deserialize `PAYMENT-REQUIRED` header into PaymentRequired
154    fn try_from(value: Base64EncodedHeader) -> Result<Self, Self::Error> {
155        let decoded = BASE64_STANDARD.decode(&value.0)?;
156        let json_str = String::from_utf8(decoded)?;
157        let payment_required: PaymentRequired = serde_json::from_str(&json_str)?;
158        Ok(payment_required)
159    }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct PaymentPayload {
165    pub x402_version: X402V2,
166    pub resource: PaymentResource,
167    pub accepted: PaymentRequirements,
168    pub payload: AnyJson,
169    pub extensions: Record<Extension>,
170}
171
172impl TryFrom<PaymentPayload> for Base64EncodedHeader {
173    type Error = crate::errors::Error;
174
175    /// Serialize PaymentPayload into `PAYMENT-SIGNATURE` header format
176    fn try_from(value: PaymentPayload) -> Result<Self, Self::Error> {
177        let json = serde_json::to_string(&value)?;
178        let encoded = BASE64_STANDARD.encode(json);
179        Ok(Base64EncodedHeader(encoded))
180    }
181}
182
183impl TryFrom<Base64EncodedHeader> for PaymentPayload {
184    type Error = crate::errors::Error;
185
186    /// Deserialize `PAYMENT-SIGNATURE` header into PaymentPayload
187    fn try_from(value: Base64EncodedHeader) -> Result<Self, Self::Error> {
188        let decoded_bytes = BASE64_STANDARD.decode(&value.0)?;
189        let json_str = String::from_utf8(decoded_bytes)?;
190        let payload = serde_json::from_str(&json_str)?;
191        Ok(payload)
192    }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct SettlementResponse {
197    pub success: bool,
198    pub transaction: String,
199    pub network: String,
200    pub payer: String,
201}
202
203impl TryFrom<SettlementResponse> for Base64EncodedHeader {
204    type Error = crate::errors::Error;
205
206    /// Serialize SettlementResponse into `PAYMENT-RESPONSE` header format
207    fn try_from(value: SettlementResponse) -> Result<Self, Self::Error> {
208        let json = serde_json::to_string(&value)?;
209        let encoded = BASE64_STANDARD.encode(json);
210        Ok(Base64EncodedHeader(encoded))
211    }
212}
213
214impl TryFrom<Base64EncodedHeader> for SettlementResponse {
215    type Error = crate::errors::Error;
216
217    /// Deserialize `PAYMENT-RESPONSE` header into SettlementResponse
218    fn try_from(value: Base64EncodedHeader) -> Result<Self, Self::Error> {
219        let decoded_bytes = BASE64_STANDARD.decode(&value.0)?;
220        let json_str = String::from_utf8(decoded_bytes)?;
221        let response = serde_json::from_str(&json_str)?;
222        Ok(response)
223    }
224}
225
226impl<S, A> From<Payment<S, A>> for PaymentRequirements
227where
228    S: Scheme,
229    A: Address<Network = S::Network>,
230{
231    fn from(payment: Payment<S, A>) -> Self {
232        PaymentRequirements {
233            scheme: S::SCHEME_NAME.to_string(),
234            network: payment.scheme.network().network_id().to_string(),
235            amount: payment.amount,
236            asset: payment.asset.address.to_string(),
237            pay_to: payment.pay_to.to_string(),
238            max_timeout_seconds: payment.max_timeout_seconds,
239            extra: payment.extra,
240        }
241    }
242}