1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::typed_id::{PaymentAccountId, PaymentAttemptId, PaymentPolicyId};
12
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18#[cfg_attr(feature = "openapi", derive(ToSchema))]
19#[serde(rename_all = "snake_case")]
20pub enum PaymentRail {
21 MppTempo,
22 X402Base,
23}
24
25impl PaymentRail {
26 pub fn as_wire(&self) -> &'static str {
27 match self {
28 PaymentRail::MppTempo => "mpp_tempo",
29 PaymentRail::X402Base => "x402_base",
30 }
31 }
32}
33
34impl std::fmt::Display for PaymentRail {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.write_str(self.as_wire())
37 }
38}
39
40impl std::str::FromStr for PaymentRail {
41 type Err = String;
42
43 fn from_str(value: &str) -> Result<Self, Self::Err> {
44 match value {
45 "mpp_tempo" => Ok(PaymentRail::MppTempo),
46 "x402_base" => Ok(PaymentRail::X402Base),
47 _ => Err(format!("Invalid payment rail: {value}")),
48 }
49 }
50}
51
52impl From<&str> for PaymentRail {
53 fn from(value: &str) -> Self {
54 match value {
55 "x402_base" => PaymentRail::X402Base,
56 _ => PaymentRail::MppTempo,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63#[cfg_attr(feature = "openapi", derive(ToSchema))]
64#[serde(rename_all = "snake_case")]
65pub enum PaymentOwnerType {
66 User,
67 AgentIdentity,
68 Organization,
69}
70
71impl PaymentOwnerType {
72 pub fn as_wire(&self) -> &'static str {
73 match self {
74 PaymentOwnerType::User => "user",
75 PaymentOwnerType::AgentIdentity => "agent_identity",
76 PaymentOwnerType::Organization => "organization",
77 }
78 }
79}
80
81impl std::fmt::Display for PaymentOwnerType {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.write_str(self.as_wire())
84 }
85}
86
87impl From<&str> for PaymentOwnerType {
88 fn from(value: &str) -> Self {
89 match value {
90 "agent_identity" => PaymentOwnerType::AgentIdentity,
91 "organization" => PaymentOwnerType::Organization,
92 _ => PaymentOwnerType::User,
93 }
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101#[cfg_attr(feature = "openapi", derive(ToSchema))]
102#[serde(rename_all = "snake_case")]
103pub enum PaymentStatus {
104 Active,
105 Disabled,
106 Pending,
107 Succeeded,
108 Failed,
109 Released,
110}
111
112impl PaymentStatus {
113 pub fn as_wire(&self) -> &'static str {
114 match self {
115 PaymentStatus::Active => "active",
116 PaymentStatus::Disabled => "disabled",
117 PaymentStatus::Pending => "pending",
118 PaymentStatus::Succeeded => "succeeded",
119 PaymentStatus::Failed => "failed",
120 PaymentStatus::Released => "released",
121 }
122 }
123}
124
125impl std::fmt::Display for PaymentStatus {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.write_str(self.as_wire())
128 }
129}
130
131impl From<&str> for PaymentStatus {
132 fn from(value: &str) -> Self {
133 match value {
134 "disabled" => PaymentStatus::Disabled,
135 "pending" => PaymentStatus::Pending,
136 "succeeded" => PaymentStatus::Succeeded,
137 "failed" => PaymentStatus::Failed,
138 "released" => PaymentStatus::Released,
139 _ => PaymentStatus::Active,
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
148#[cfg_attr(feature = "openapi", derive(ToSchema))]
149pub struct PaymentAccount {
150 pub id: PaymentAccountId,
152 #[cfg_attr(
154 feature = "openapi",
155 schema(example = "org_01933b5a000070008000000000000001")
156 )]
157 pub organization_id: String,
158 pub owner_type: PaymentOwnerType,
160 #[cfg_attr(
162 feature = "openapi",
163 schema(example = "agent_01933b5a000070008000000000000001")
164 )]
165 pub owner_id: String,
166 pub rail: PaymentRail,
168 #[cfg_attr(feature = "openapi", schema(example = "Production USDC ops wallet"))]
170 pub label: String,
171 #[cfg_attr(
173 feature = "openapi",
174 schema(example = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8")
175 )]
176 pub public_address: Option<String>,
177 pub status: PaymentStatus,
179 #[cfg_attr(feature = "openapi", schema(example = json!({"team": "finance"})))]
181 pub metadata: serde_json::Value,
182 #[cfg_attr(feature = "openapi", schema(example = "2026-04-01T10:00:00Z"))]
184 pub created_at: DateTime<Utc>,
185 #[cfg_attr(feature = "openapi", schema(example = "2026-05-20T14:00:00Z"))]
187 pub updated_at: DateTime<Utc>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
194#[cfg_attr(feature = "openapi", derive(ToSchema))]
195pub struct PaymentPolicy {
196 pub id: PaymentPolicyId,
198 #[cfg_attr(
200 feature = "openapi",
201 schema(example = "org_01933b5a000070008000000000000001")
202 )]
203 pub organization_id: String,
204 pub payment_account_id: PaymentAccountId,
206 #[cfg_attr(feature = "openapi", schema(example = "agent_identity"))]
208 pub subject_type: String,
209 #[cfg_attr(
211 feature = "openapi",
212 schema(example = "identity_01933b5a000070008000000000000001")
213 )]
214 pub subject_id: String,
215 #[cfg_attr(feature = "openapi", schema(example = json!(["paid_search", "paid_image_gen"])))]
217 pub allowed_capabilities: Vec<String>,
218 #[cfg_attr(feature = "openapi", schema(example = json!(["api.openai.com", "api.anthropic.com"])))]
220 pub allowed_hosts: Vec<String>,
221 pub rail_preference: Vec<PaymentRail>,
223 #[cfg_attr(feature = "openapi", schema(example = 2.5))]
225 pub max_amount_usd_per_request: Option<f64>,
226 #[cfg_attr(feature = "openapi", schema(example = 5.0))]
228 pub max_amount_usd_per_turn: Option<f64>,
229 #[cfg_attr(feature = "openapi", schema(example = 50.0))]
231 pub max_amount_usd_per_day: Option<f64>,
232 #[cfg_attr(feature = "openapi", schema(example = 10.0))]
234 pub require_approval_above_usd: Option<f64>,
235 pub status: PaymentStatus,
237 #[cfg_attr(feature = "openapi", schema(example = json!({"created_by": "alex@acme.example"})))]
239 pub metadata: serde_json::Value,
240 #[cfg_attr(feature = "openapi", schema(example = "2026-04-01T10:00:00Z"))]
242 pub created_at: DateTime<Utc>,
243 #[cfg_attr(feature = "openapi", schema(example = "2026-05-20T14:00:00Z"))]
245 pub updated_at: DateTime<Utc>,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
252#[cfg_attr(feature = "openapi", derive(ToSchema))]
253pub struct PaymentAttempt {
254 pub id: PaymentAttemptId,
256 #[cfg_attr(
258 feature = "openapi",
259 schema(example = "org_01933b5a000070008000000000000001")
260 )]
261 pub organization_id: String,
262 pub payment_account_id: Option<PaymentAccountId>,
264 #[cfg_attr(
266 feature = "openapi",
267 schema(example = "session_01933b5a000070008000000000000001")
268 )]
269 pub session_id: Option<String>,
270 #[cfg_attr(feature = "openapi", schema(example = "paid_search"))]
272 pub capability: String,
273 #[cfg_attr(feature = "openapi", schema(example = "search.query"))]
275 pub operation: String,
276 pub rail: Option<PaymentRail>,
278 #[cfg_attr(feature = "openapi", schema(example = 0.014))]
280 pub amount_usd: f64,
281 #[cfg_attr(feature = "openapi", schema(example = "USD"))]
283 pub currency: String,
284 #[cfg_attr(
286 feature = "openapi",
287 schema(example = "https://api.example.com/v1/search")
288 )]
289 pub target_url: String,
290 #[cfg_attr(
292 feature = "openapi",
293 schema(
294 example = "sha256:9f1e2a4c3d5b6e8a0b2c4d6e8f0a1b3c5d7e9f0a1b2c4d6e8f0a1b2c4d6e8f0a"
295 )
296 )]
297 pub request_hash: Option<String>,
298 pub status: PaymentStatus,
300 #[cfg_attr(
302 feature = "openapi",
303 schema(example = "rail.insufficient_funds: settled balance below minimum")
304 )]
305 pub error_message: Option<String>,
306 #[cfg_attr(feature = "openapi", schema(example = json!({"tx_hash": "0x4a1c2b3d", "block": 18234567})))]
308 pub receipt: serde_json::Value,
309 #[cfg_attr(feature = "openapi", schema(example = "2026-05-25T10:14:00Z"))]
311 pub created_at: DateTime<Utc>,
312 #[cfg_attr(feature = "openapi", schema(example = "2026-05-25T10:14:02Z"))]
314 pub updated_at: DateTime<Utc>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
319#[serde(rename_all = "UPPERCASE")]
320pub enum PaymentMethod {
321 Get,
322 Post,
323}
324
325impl PaymentMethod {
326 pub fn as_wire(&self) -> &'static str {
327 match self {
328 PaymentMethod::Get => "GET",
329 PaymentMethod::Post => "POST",
330 }
331 }
332}
333
334impl std::fmt::Display for PaymentMethod {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.write_str(self.as_wire())
337 }
338}
339
340impl std::str::FromStr for PaymentMethod {
341 type Err = String;
342
343 fn from_str(value: &str) -> Result<Self, Self::Err> {
344 match value {
345 "GET" => Ok(PaymentMethod::Get),
346 "POST" => Ok(PaymentMethod::Post),
347 _ => Err(format!("Invalid payment method: {value}")),
348 }
349 }
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct MachinePaymentRequest {
355 pub capability: String,
356 pub operation: String,
357 pub method: PaymentMethod,
358 pub url: String,
359 pub body: Option<serde_json::Value>,
360 pub max_amount_usd: f64,
361 pub rail_preference: Vec<PaymentRail>,
362 pub metadata: serde_json::Value,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct MachinePaymentResponse {
368 pub attempt_id: Option<PaymentAttemptId>,
369 pub amount_usd: f64,
370 pub rail: Option<PaymentRail>,
371 pub response: serde_json::Value,
372 pub receipt: serde_json::Value,
373}