Skip to main content

everruns_core/
payment.rs

1//! Machine payment execution contract.
2//!
3//! Design decision: payment is an internal authority consumed by capabilities,
4//! not a generic model-facing paid HTTP tool. Domain tools such as
5//! `parallel_search` build typed requests and let the platform resolve wallets,
6//! enforce policy, sign, settle, and record receipts.
7//!
8//! EVE-838: the durable accounting **records** (`PaymentAccount`,
9//! `PaymentPolicy`, `PaymentAttempt`) and their value enums (`PaymentOwnerType`,
10//! `PaymentStatus`) moved to the `everruns-platform` crate. The
11//! capability-internal execution contract below stays in core because it is
12//! bound to the [`PaymentAuthority`](crate::PaymentAuthority) trait and
13//! `ToolContext`; `PaymentRail` and `PaymentMethod` are the value types those
14//! DTOs embed.
15
16use serde::{Deserialize, Serialize};
17
18use crate::typed_id::PaymentAttemptId;
19
20#[cfg(feature = "openapi")]
21use utoipa::ToSchema;
22
23/// Payment rail used to settle a machine payment.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25#[cfg_attr(feature = "openapi", derive(ToSchema))]
26#[serde(rename_all = "snake_case")]
27pub enum PaymentRail {
28    MppTempo,
29    X402Base,
30}
31
32impl PaymentRail {
33    pub fn as_wire(&self) -> &'static str {
34        match self {
35            PaymentRail::MppTempo => "mpp_tempo",
36            PaymentRail::X402Base => "x402_base",
37        }
38    }
39}
40
41impl std::fmt::Display for PaymentRail {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.write_str(self.as_wire())
44    }
45}
46
47impl std::str::FromStr for PaymentRail {
48    type Err = String;
49
50    fn from_str(value: &str) -> Result<Self, Self::Err> {
51        match value {
52            "mpp_tempo" => Ok(PaymentRail::MppTempo),
53            "x402_base" => Ok(PaymentRail::X402Base),
54            _ => Err(format!("Invalid payment rail: {value}")),
55        }
56    }
57}
58
59impl From<&str> for PaymentRail {
60    fn from(value: &str) -> Self {
61        match value {
62            "x402_base" => PaymentRail::X402Base,
63            _ => PaymentRail::MppTempo,
64        }
65    }
66}
67
68/// HTTP method for an internal paid request.
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70#[serde(rename_all = "UPPERCASE")]
71pub enum PaymentMethod {
72    Get,
73    Post,
74}
75
76impl PaymentMethod {
77    pub fn as_wire(&self) -> &'static str {
78        match self {
79            PaymentMethod::Get => "GET",
80            PaymentMethod::Post => "POST",
81        }
82    }
83}
84
85impl std::fmt::Display for PaymentMethod {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        f.write_str(self.as_wire())
88    }
89}
90
91impl std::str::FromStr for PaymentMethod {
92    type Err = String;
93
94    fn from_str(value: &str) -> Result<Self, Self::Err> {
95        match value {
96            "GET" => Ok(PaymentMethod::Get),
97            "POST" => Ok(PaymentMethod::Post),
98            _ => Err(format!("Invalid payment method: {value}")),
99        }
100    }
101}
102
103/// Internal request from a capability to the payment authority.
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct MachinePaymentRequest {
106    pub capability: String,
107    pub operation: String,
108    pub method: PaymentMethod,
109    pub url: String,
110    pub body: Option<serde_json::Value>,
111    pub max_amount_usd: f64,
112    pub rail_preference: Vec<PaymentRail>,
113    pub metadata: serde_json::Value,
114}
115
116/// Response returned to the calling capability after payment and execution.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct MachinePaymentResponse {
119    pub attempt_id: Option<PaymentAttemptId>,
120    pub amount_usd: f64,
121    pub rail: Option<PaymentRail>,
122    pub response: serde_json::Value,
123    pub receipt: serde_json::Value,
124}