tap_msg/message/
settle.rs1use crate::error::{Error, Result};
7use crate::TapMessage;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
12#[tap(message_type = "https://tap.rsvp/schema/1.0#Settle", custom_validation)]
13pub struct Settle {
14 #[tap(thread_id)]
16 pub transaction_id: String,
17
18 #[serde(
20 rename = "settlementId",
21 skip_serializing_if = "Option::is_none",
22 default
23 )]
24 pub settlement_id: Option<String>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub amount: Option<String>,
29}
30
31impl Settle {
32 pub fn new(transaction_id: &str, settlement_id: &str) -> Self {
34 Self {
35 transaction_id: transaction_id.to_string(),
36 settlement_id: Some(settlement_id.to_string()),
37 amount: None,
38 }
39 }
40
41 pub fn with_amount(transaction_id: &str, settlement_id: &str, amount: &str) -> Self {
43 Self {
44 transaction_id: transaction_id.to_string(),
45 settlement_id: Some(settlement_id.to_string()),
46 amount: Some(amount.to_string()),
47 }
48 }
49
50 pub fn minimal(transaction_id: &str) -> Self {
52 Self {
53 transaction_id: transaction_id.to_string(),
54 settlement_id: None,
55 amount: None,
56 }
57 }
58}
59
60impl Settle {
61 pub fn validate_settle(&self) -> Result<()> {
63 if self.transaction_id.is_empty() {
64 return Err(Error::Validation(
65 "Transaction ID is required in Settle".to_string(),
66 ));
67 }
68
69 if let Some(ref settlement_id) = self.settlement_id {
72 if settlement_id.is_empty() {
73 return Err(Error::Validation(
74 "Settlement ID cannot be empty when provided".to_string(),
75 ));
76 }
77
78 if settlement_id.starts_with("0x") && !settlement_id.contains(':') {
83 return Err(Error::Validation(
84 "Invalid format for 'settlementId', CAIP-220 block address expected"
85 .to_string(),
86 ));
87 }
88
89 let parts: Vec<&str> = settlement_id.split(':').collect();
90 if parts.len() < 3 {
91 return Err(Error::Validation(
92 "Invalid format for 'settlementId', CAIP-220 block address expected"
93 .to_string(),
94 ));
95 }
96
97 if let Some(tx_part) = parts.get(2) {
99 if !tx_part.contains('/') {
100 return Err(Error::Validation(
101 "Invalid format for 'settlementId', CAIP-220 block address expected"
102 .to_string(),
103 ));
104 }
105 }
106 }
107
108 if let Some(amount) = &self.amount {
109 if amount.is_empty() {
110 return Err(Error::Validation(
111 "Amount must be a valid number".to_string(),
112 ));
113 }
114
115 match amount.parse::<f64>() {
117 Ok(amount) if amount <= 0.0 => {
118 return Err(Error::Validation("Amount must be positive".to_string()));
119 }
120 Err(_) => {
121 return Err(Error::Validation(
122 "Amount must be a valid number".to_string(),
123 ));
124 }
125 _ => {}
126 }
127 }
128
129 Ok(())
130 }
131}