1use super::network::Network;
4use chrono::Utc;
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::time::Duration;
10
11pub const X402_VERSION: u32 = 1;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct PaymentRequirements {
17 pub scheme: String,
19 pub network: String,
21 #[serde(rename = "maxAmountRequired")]
23 pub max_amount_required: String,
24 pub asset: String,
26 #[serde(rename = "payTo")]
28 pub pay_to: String,
29 pub resource: String,
31 pub description: String,
33 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
35 pub mime_type: Option<String>,
36 #[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
38 pub output_schema: Option<Value>,
39 #[serde(rename = "maxTimeoutSeconds")]
41 pub max_timeout_seconds: u32,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub extra: Option<Value>,
45}
46
47impl PaymentRequirements {
48 pub fn new(
50 scheme: impl Into<String>,
51 network: impl Into<String>,
52 max_amount_required: impl Into<String>,
53 asset: impl Into<String>,
54 pay_to: impl Into<String>,
55 resource: impl Into<String>,
56 description: impl Into<String>,
57 ) -> Self {
58 Self {
59 scheme: scheme.into(),
60 network: network.into(),
61 max_amount_required: max_amount_required.into(),
62 asset: asset.into(),
63 pay_to: pay_to.into(),
64 resource: resource.into(),
65 description: description.into(),
66 mime_type: None,
67 output_schema: None,
68 max_timeout_seconds: 60,
69 extra: None,
70 }
71 }
72
73 pub fn set_usdc_info(&mut self, network: Network) -> crate::Result<()> {
75 let mut usdc_info = HashMap::new();
76 usdc_info.insert("name".to_string(), network.usdc_name().to_string());
77 usdc_info.insert("version".to_string(), "2".to_string());
78
79 self.extra = Some(serde_json::to_value(usdc_info)?);
80 Ok(())
81 }
82
83 pub fn amount_as_decimal(&self) -> crate::Result<Decimal> {
85 self.max_amount_required
86 .parse()
87 .map_err(|_| crate::X402Error::invalid_payment_requirements("Invalid amount format"))
88 }
89
90 pub fn amount_in_decimal_units(&self, decimals: u8) -> crate::Result<Decimal> {
92 let amount = self.amount_as_decimal()?;
93 let divisor = Decimal::from(10u64.pow(decimals as u32));
94 Ok(amount / divisor)
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct PaymentPayload {
101 #[serde(rename = "x402Version")]
103 pub x402_version: u32,
104 pub scheme: String,
106 pub network: String,
108 pub payload: ExactEvmPayload,
110}
111
112impl PaymentPayload {
113 pub fn new(
115 scheme: impl Into<String>,
116 network: impl Into<String>,
117 payload: ExactEvmPayload,
118 ) -> Self {
119 Self {
120 x402_version: X402_VERSION,
121 scheme: scheme.into(),
122 network: network.into(),
123 payload,
124 }
125 }
126
127 pub fn from_base64(encoded: &str) -> crate::Result<Self> {
129 use base64::{engine::general_purpose, Engine as _};
130 let decoded = general_purpose::STANDARD.decode(encoded)?;
131 let payload: PaymentPayload = serde_json::from_slice(&decoded)?;
132 Ok(payload)
133 }
134
135 pub fn to_base64(&self) -> crate::Result<String> {
137 use base64::{engine::general_purpose, Engine as _};
138 let json = serde_json::to_string(self)?;
139 Ok(general_purpose::STANDARD.encode(json))
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ExactEvmPayload {
146 pub signature: String,
148 pub authorization: ExactEvmPayloadAuthorization,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ExactEvmPayloadAuthorization {
155 pub from: String,
157 pub to: String,
159 pub value: String,
161 #[serde(rename = "validAfter")]
163 pub valid_after: String,
164 #[serde(rename = "validBefore")]
166 pub valid_before: String,
167 pub nonce: String,
169}
170
171impl ExactEvmPayloadAuthorization {
172 pub fn new(
174 from: impl Into<String>,
175 to: impl Into<String>,
176 value: impl Into<String>,
177 valid_after: impl Into<String>,
178 valid_before: impl Into<String>,
179 nonce: impl Into<String>,
180 ) -> Self {
181 Self {
182 from: from.into(),
183 to: to.into(),
184 value: value.into(),
185 valid_after: valid_after.into(),
186 valid_before: valid_before.into(),
187 nonce: nonce.into(),
188 }
189 }
190
191 pub fn is_valid_now(&self) -> crate::Result<bool> {
193 let now = Utc::now().timestamp();
194 let valid_after: i64 = self.valid_after.parse().map_err(|_| {
195 crate::X402Error::invalid_authorization("Invalid valid_after timestamp")
196 })?;
197 let valid_before: i64 = self.valid_before.parse().map_err(|_| {
198 crate::X402Error::invalid_authorization("Invalid valid_before timestamp")
199 })?;
200
201 Ok(now >= valid_after && now <= valid_before)
202 }
203
204 pub fn validity_duration(&self) -> crate::Result<Duration> {
206 let valid_after: i64 = self.valid_after.parse().map_err(|_| {
207 crate::X402Error::invalid_authorization("Invalid valid_after timestamp")
208 })?;
209 let valid_before: i64 = self.valid_before.parse().map_err(|_| {
210 crate::X402Error::invalid_authorization("Invalid valid_before timestamp")
211 })?;
212
213 Ok(Duration::from_secs((valid_before - valid_after) as u64))
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct PaymentRequirementsResponse {
220 #[serde(rename = "x402Version")]
222 pub x402_version: u32,
223 pub error: String,
225 pub accepts: Vec<PaymentRequirements>,
227}
228
229impl PaymentRequirementsResponse {
230 pub fn new(error: impl Into<String>, accepts: Vec<PaymentRequirements>) -> Self {
232 Self {
233 x402_version: X402_VERSION,
234 error: error.into(),
235 accepts,
236 }
237 }
238}