use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::typed_id::{PaymentAccountId, PaymentAttemptId, PaymentPolicyId};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentRail {
MppTempo,
X402Base,
}
impl PaymentRail {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentRail::MppTempo => "mpp_tempo",
PaymentRail::X402Base => "x402_base",
}
}
}
impl std::fmt::Display for PaymentRail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl std::str::FromStr for PaymentRail {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"mpp_tempo" => Ok(PaymentRail::MppTempo),
"x402_base" => Ok(PaymentRail::X402Base),
_ => Err(format!("Invalid payment rail: {value}")),
}
}
}
impl From<&str> for PaymentRail {
fn from(value: &str) -> Self {
match value {
"x402_base" => PaymentRail::X402Base,
_ => PaymentRail::MppTempo,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentOwnerType {
User,
AgentIdentity,
Organization,
}
impl PaymentOwnerType {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentOwnerType::User => "user",
PaymentOwnerType::AgentIdentity => "agent_identity",
PaymentOwnerType::Organization => "organization",
}
}
}
impl std::fmt::Display for PaymentOwnerType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl From<&str> for PaymentOwnerType {
fn from(value: &str) -> Self {
match value {
"agent_identity" => PaymentOwnerType::AgentIdentity,
"organization" => PaymentOwnerType::Organization,
_ => PaymentOwnerType::User,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentStatus {
Active,
Disabled,
Pending,
Succeeded,
Failed,
Released,
}
impl PaymentStatus {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentStatus::Active => "active",
PaymentStatus::Disabled => "disabled",
PaymentStatus::Pending => "pending",
PaymentStatus::Succeeded => "succeeded",
PaymentStatus::Failed => "failed",
PaymentStatus::Released => "released",
}
}
}
impl std::fmt::Display for PaymentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl From<&str> for PaymentStatus {
fn from(value: &str) -> Self {
match value {
"disabled" => PaymentStatus::Disabled,
"pending" => PaymentStatus::Pending,
"succeeded" => PaymentStatus::Succeeded,
"failed" => PaymentStatus::Failed,
"released" => PaymentStatus::Released,
_ => PaymentStatus::Active,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentAccount {
pub id: PaymentAccountId,
#[cfg_attr(
feature = "openapi",
schema(example = "org_01933b5a000070008000000000000001")
)]
pub organization_id: String,
pub owner_type: PaymentOwnerType,
#[cfg_attr(
feature = "openapi",
schema(example = "agent_01933b5a000070008000000000000001")
)]
pub owner_id: String,
pub rail: PaymentRail,
#[cfg_attr(feature = "openapi", schema(example = "Production USDC ops wallet"))]
pub label: String,
#[cfg_attr(
feature = "openapi",
schema(example = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8")
)]
pub public_address: Option<String>,
pub status: PaymentStatus,
#[cfg_attr(feature = "openapi", schema(example = json!({"team": "finance"})))]
pub metadata: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = "2026-04-01T10:00:00Z"))]
pub created_at: DateTime<Utc>,
#[cfg_attr(feature = "openapi", schema(example = "2026-05-20T14:00:00Z"))]
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentPolicy {
pub id: PaymentPolicyId,
#[cfg_attr(
feature = "openapi",
schema(example = "org_01933b5a000070008000000000000001")
)]
pub organization_id: String,
pub payment_account_id: PaymentAccountId,
#[cfg_attr(feature = "openapi", schema(example = "agent_identity"))]
pub subject_type: String,
#[cfg_attr(
feature = "openapi",
schema(example = "identity_01933b5a000070008000000000000001")
)]
pub subject_id: String,
#[cfg_attr(feature = "openapi", schema(example = json!(["paid_search", "paid_image_gen"])))]
pub allowed_capabilities: Vec<String>,
#[cfg_attr(feature = "openapi", schema(example = json!(["api.openai.com", "api.anthropic.com"])))]
pub allowed_hosts: Vec<String>,
pub rail_preference: Vec<PaymentRail>,
#[cfg_attr(feature = "openapi", schema(example = 2.5))]
pub max_amount_usd_per_request: Option<f64>,
#[cfg_attr(feature = "openapi", schema(example = 5.0))]
pub max_amount_usd_per_turn: Option<f64>,
#[cfg_attr(feature = "openapi", schema(example = 50.0))]
pub max_amount_usd_per_day: Option<f64>,
#[cfg_attr(feature = "openapi", schema(example = 10.0))]
pub require_approval_above_usd: Option<f64>,
pub status: PaymentStatus,
#[cfg_attr(feature = "openapi", schema(example = json!({"created_by": "alex@acme.example"})))]
pub metadata: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = "2026-04-01T10:00:00Z"))]
pub created_at: DateTime<Utc>,
#[cfg_attr(feature = "openapi", schema(example = "2026-05-20T14:00:00Z"))]
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentAttempt {
pub id: PaymentAttemptId,
#[cfg_attr(
feature = "openapi",
schema(example = "org_01933b5a000070008000000000000001")
)]
pub organization_id: String,
pub payment_account_id: Option<PaymentAccountId>,
#[cfg_attr(
feature = "openapi",
schema(example = "session_01933b5a000070008000000000000001")
)]
pub session_id: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "paid_search"))]
pub capability: String,
#[cfg_attr(feature = "openapi", schema(example = "search.query"))]
pub operation: String,
pub rail: Option<PaymentRail>,
#[cfg_attr(feature = "openapi", schema(example = 0.014))]
pub amount_usd: f64,
#[cfg_attr(feature = "openapi", schema(example = "USD"))]
pub currency: String,
#[cfg_attr(
feature = "openapi",
schema(example = "https://api.example.com/v1/search")
)]
pub target_url: String,
#[cfg_attr(
feature = "openapi",
schema(
example = "sha256:9f1e2a4c3d5b6e8a0b2c4d6e8f0a1b3c5d7e9f0a1b2c4d6e8f0a1b2c4d6e8f0a"
)
)]
pub request_hash: Option<String>,
pub status: PaymentStatus,
#[cfg_attr(
feature = "openapi",
schema(example = "rail.insufficient_funds: settled balance below minimum")
)]
pub error_message: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = json!({"tx_hash": "0x4a1c2b3d", "block": 18234567})))]
pub receipt: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = "2026-05-25T10:14:00Z"))]
pub created_at: DateTime<Utc>,
#[cfg_attr(feature = "openapi", schema(example = "2026-05-25T10:14:02Z"))]
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum PaymentMethod {
Get,
Post,
}
impl PaymentMethod {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentMethod::Get => "GET",
PaymentMethod::Post => "POST",
}
}
}
impl std::fmt::Display for PaymentMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl std::str::FromStr for PaymentMethod {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"GET" => Ok(PaymentMethod::Get),
"POST" => Ok(PaymentMethod::Post),
_ => Err(format!("Invalid payment method: {value}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachinePaymentRequest {
pub capability: String,
pub operation: String,
pub method: PaymentMethod,
pub url: String,
pub body: Option<serde_json::Value>,
pub max_amount_usd: f64,
pub rail_preference: Vec<PaymentRail>,
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachinePaymentResponse {
pub attempt_id: Option<PaymentAttemptId>,
pub amount_usd: f64,
pub rail: Option<PaymentRail>,
pub response: serde_json::Value,
pub receipt: serde_json::Value,
}